diff --git a/build/globals.webpack.js b/build/globals.webpack.js index 902e0a1d58..b84ce960f0 100644 --- a/build/globals.webpack.js +++ b/build/globals.webpack.js @@ -15,7 +15,7 @@ const const {csp, build, webpack, i18n} = config, {config: pzlr} = require('@pzlr/build-core'), - {collectI18NKeysets} = include('build/helpers'), + {collectI18NKeysets, getLayerName} = include('build/helpers'), {getDSComponentMods, getThemes, getDS} = include('build/ds'); const @@ -58,7 +58,8 @@ module.exports = { return $C(components).to({}).reduce((res, el, key) => { res[key] = { parent: JSON.stringify(el.parent), - dependencies: JSON.stringify(el.dependencies) + dependencies: JSON.stringify(el.dependencies), + layer: JSON.stringify(getLayerName(el.logic ?? el.index)) }; return res; diff --git a/build/graph/graph.js b/build/graph/graph.js index 805a27b2af..b72126b9ec 100644 --- a/build/graph/graph.js +++ b/build/graph/graph.js @@ -40,7 +40,9 @@ const { output, cacheDir, isStandalone, - tracer + tracer, + invokeByRegisterEvent, + getLayerName } = include('build/helpers'); /** @@ -244,12 +246,23 @@ async function buildProjectGraph() { entry = path.resolve(tmpEntries, '../', name); } + const entryPath = getEntryPath(entry); + let importScript; + + const + componentName = component?.name ?? name, + isComponent = /^[bpg]-[\w-]+/.test(componentName); + if (webpack.ssr) { - str += `Object.assign(module.exports, require('${getEntryPath(entry)}'));\n`; + importScript = `Object.assign(module.exports, require('${entryPath}'));\n`; } else { - str += `require('${getEntryPath(entry)}');\n`; + importScript = `require('${entryPath}');\n`; } + + str += isComponent ? + invokeByRegisterEvent(importScript, getLayerName(entry), componentName) : + importScript; } return str; diff --git a/build/helpers/index.js b/build/helpers/index.js index e3786c12cf..4e7717a8bd 100644 --- a/build/helpers/index.js +++ b/build/helpers/index.js @@ -14,5 +14,7 @@ Object.assign( include('build/helpers/webpack'), include('build/helpers/other'), include('build/helpers/i18n'), - include('build/helpers/tracer') + include('build/helpers/tracer'), + include('build/helpers/invoke-by-register-component'), + include('build/helpers/layer-name') ); diff --git a/build/helpers/invoke-by-register-component.js b/build/helpers/invoke-by-register-component.js new file mode 100644 index 0000000000..bf0f89cdf9 --- /dev/null +++ b/build/helpers/invoke-by-register-component.js @@ -0,0 +1,37 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +'use strict'; + +/** + * Function incapsulates script to event handler that is being triggered when component with + * name `componentName` from `layerName` renders on the page. + * + * @param {string} script + * @param {string} layerName + * @param {string} componentName + * @returns {string} + */ +function invokeByRegisterEvent(script, layerName, componentName) { + if (script?.trim()?.length === 0) { + return script; + } + + return `\n + (function () { + const {initEmitter} = require('core/component/event'); + + initEmitter.once('registerComponent.${layerName}.${componentName}', () => { + ${script} + }); + })(); + \n + `; +} + +exports.invokeByRegisterEvent = invokeByRegisterEvent; diff --git a/build/helpers/layer-name.js b/build/helpers/layer-name.js new file mode 100644 index 0000000000..104c4e1d4e --- /dev/null +++ b/build/helpers/layer-name.js @@ -0,0 +1,42 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +'use strict'; + +const { + config, + resolve: {rootDependencies} +} = require('@pzlr/build-core'); + +const + isPathInside = require('is-path-inside'), + fs = require('fs'), + path = require('path'); + +/** + * The function determines the package in which the module is defined and + * returns the name of this package from the `package.json` file + * + * @param {string} filePath + * @returns {string} + */ +function getLayerName(filePath) { + let layer = config.projectName; + + for (let i = 0; i < rootDependencies.length; i++) { + if (isPathInside(fs.realpathSync(filePath), fs.realpathSync(rootDependencies[i]))) { + const pathPackageJson = path.resolve(fs.realpathSync(rootDependencies[i]), '..', 'package.json'); + layer = require(pathPackageJson).name; + break; + } + } + + return layer; +} + +exports.getLayerName = getLayerName; diff --git a/build/monic/attach-component-dependencies.js b/build/monic/attach-component-dependencies.js index 24e1550f60..d3ab6313fe 100644 --- a/build/monic/attach-component-dependencies.js +++ b/build/monic/attach-component-dependencies.js @@ -14,7 +14,8 @@ const const path = require('upath'), - graph = include('build/graph'); + graph = include('build/graph'), + {invokeByRegisterEvent, getLayerName} = include('build/helpers'); const decls = Object.create(null); @@ -132,7 +133,7 @@ module.exports = async function attachComponentDependencies(str, filePath) { expr = `TPLS['${dep}'] = require('${src}')['${dep}'];`; } else { - expr = `require('${src}');`; + expr = invokeByRegisterEvent(`require('${src}');`, getLayerName(filePath), dep); } decl += `try { ${expr} } catch (err) { stderr(err); }`; diff --git a/build/ts-transformers/set-component-layer.js b/build/ts-transformers/set-component-layer.js index 31097ca027..735751d228 100644 --- a/build/ts-transformers/set-component-layer.js +++ b/build/ts-transformers/set-component-layer.js @@ -11,7 +11,9 @@ 'use strict'; const ts = require('typescript'); -const {validators} = require('@pzlr/build-core'); +const {validators, config} = require('@pzlr/build-core'); + +const {getLayerName} = include('build/helpers'); /** * @typedef {import('typescript').TransformationContext} Context @@ -21,7 +23,6 @@ const {validators} = require('@pzlr/build-core'); */ const - pathToRootRgxp = /(?.+)[/\\]src[/\\]/, isComponentPath = new RegExp(`\\/(${validators.blockTypeList.join('|')})-.+?\\/?`); /** @@ -53,12 +54,19 @@ const * ``` */ const setComponentLayerTransformer = (context) => (sourceFile) => { - if (!isInsideComponent(sourceFile.path)) { + const + {factory} = context, + isInitAppFile = sourceFile.path.endsWith('core/init/index.ts'); + + if (!isInsideComponent(sourceFile.path) && !isInitAppFile) { return sourceFile; } - const layer = getLayerName(sourceFile.path); - const {factory} = context; + let layer = getLayerName(sourceFile.path); + + if (isInitAppFile) { + layer = config.projectName; + } /** * A visitor for the AST node @@ -67,10 +75,32 @@ const setComponentLayerTransformer = (context) => (sourceFile) => { * @returns {Node} */ const visitor = (node) => { - if (ts.isDecorator(node) && isComponentCallExpression(node)) { - const - expr = node.expression; + const + expr = node?.expression; + + // Passing the value of the original layer as an argument to + // the createApp function for initializing the root component. + if ( + ts.isCallExpression(node) && + isInitAppFile && + expr.escapedText === 'createApp' + ) { + + const updatedCallExpression = factory.createCallExpression( + factory.createIdentifier(expr.escapedText), + undefined, + [ + ...node.arguments, + factory.createStringLiteral(layer) + ] + ); + return updatedCallExpression; + } + + // Passing the value of the package layer in which the component + // is defined to the @component decorator. + if (ts.isDecorator(node) && isComponentCallExpression(node)) { if (!ts.isCallExpression(expr)) { return node; } @@ -110,18 +140,6 @@ const setComponentLayerTransformer = (context) => (sourceFile) => { // eslint-disable-next-line @v4fire/require-jsdoc module.exports = () => setComponentLayerTransformer; -/** - * The function determines the package in which the module is defined and - * returns the name of this package from the `package.json` file - * - * @param {string} filePath - * @returns {string} - */ -function getLayerName(filePath) { - const pathToRootDir = filePath.match(pathToRootRgxp).groups.path; - return require(`${pathToRootDir}/package.json`).name; -} - /** * Returns true if the specified path is within the context of the component * diff --git a/components-lock.json b/components-lock.json index 84b207e0fe..48ef91fc6e 100644 --- a/components-lock.json +++ b/components-lock.json @@ -1,5 +1,5 @@ { - "hash": "7b16e904c8d6785034161f61a4a4b42111ccf767585d6f643478eec974899aa8", + "hash": "9608b1e90d91053ae7e5f9db2002b150f358069aff02d61e172e1061eed9f959", "data": { "%data": "%data:Map", "%data:Map": [ @@ -2889,6 +2889,7 @@ "b-textarea", "b-select", "b-select-date", + "b-traits-i-observe-dom-dummy", "p-v4-dynamic-page1", "p-v4-dynamic-page2", "p-v4-dynamic-page3" @@ -2938,6 +2939,7 @@ "b-textarea", "b-select", "b-select-date", + "b-traits-i-observe-dom-dummy", "p-v4-dynamic-page1", "p-v4-dynamic-page2", "p-v4-dynamic-page3" diff --git a/fat-html.components-lock.json b/fat-html.components-lock.json index feb4cbac9d..aa7d536e54 100644 --- a/fat-html.components-lock.json +++ b/fat-html.components-lock.json @@ -1,5 +1,9 @@ { +<<<<<<< HEAD + "hash": "848188ca4409395693c58ff85ada48b70cb4709ad07a56f9cbc9f191c8586a10", +======= "hash": "63486c753ebbba9087e1f56ad93e610addf0677a5298daf4109b9ab9a703a942", +>>>>>>> fdd5344add3f10679f37d4bd0f50907fc8747a5c "data": { "%data": "%data:Map", "%data:Map": [ diff --git a/index.d.ts b/index.d.ts index e4a72652f9..966e20c84e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,7 +34,7 @@ declare const MODULE: string; declare const PATH: Dictionary>; declare const PUBLIC_PATH: CanUndef; -declare const COMPONENTS: Dictionary<{parent: string; dependencies: string[]}>; +declare const COMPONENTS: Dictionary<{parent: string; dependencies: string[]; layer: string}>; declare const TPLS: Dictionary>; declare const BLOCK_NAMES: CanUndef; diff --git a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts index cd3dca7998..040a5e0487 100644 --- a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts +++ b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts @@ -18,7 +18,7 @@ import type { AsyncOptions } from 'core/async'; import SyncPromise from 'core/promise/sync'; import type iItems from 'components/traits/i-items/i-items'; -import DOM, { watchForIntersection } from 'components/friends/dom'; +import DOM, { watchForIntersection, appendChild } from 'components/friends/dom'; import VDOM, { create, render } from 'components/friends/vdom'; import iVirtualScrollProps from 'components/base/b-virtual-scroll-new/props'; @@ -75,7 +75,7 @@ export * from 'components/super/i-data/i-data'; const $$ = symbolGenerator(); -DOM.addToPrototype({watchForIntersection}); +DOM.addToPrototype({watchForIntersection, appendChild}); VDOM.addToPrototype({create, render}); interface bVirtualScrollNew extends Trait {} diff --git a/src/components/pages/p-v4-components-demo/index.js b/src/components/pages/p-v4-components-demo/index.js index 040bd7186d..e7005a68cd 100644 --- a/src/components/pages/p-v4-components-demo/index.js +++ b/src/components/pages/p-v4-components-demo/index.js @@ -38,6 +38,8 @@ package('p-v4-components-demo') 'b-select', 'b-select-date', + 'b-traits-i-observe-dom-dummy', + 'p-v4-dynamic-page1', 'p-v4-dynamic-page2', 'p-v4-dynamic-page3' diff --git a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts index 1f7884f7b6..f8e85a801d 100644 --- a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts +++ b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts @@ -13,10 +13,12 @@ import iStaticPage, { component, prop, field, system, hook } from 'components/super/i-static-page/i-static-page'; import VDOM, * as VDOMAPI from 'components/friends/vdom'; +import DataProvider, * as DataProviderAPI from 'components/friends/data-provider'; export * from 'components/super/i-static-page/i-static-page'; VDOM.addToPrototype(VDOMAPI); +DataProvider.addToPrototype(DataProviderAPI); /** * Page with component demos. diff --git a/src/components/super/i-input-text/test/b-super-i-input-text-dummy/b-super-i-input-text-dummy.ts b/src/components/super/i-input-text/test/b-super-i-input-text-dummy/b-super-i-input-text-dummy.ts index 52d99547ba..29c323bf7c 100644 --- a/src/components/super/i-input-text/test/b-super-i-input-text-dummy/b-super-i-input-text-dummy.ts +++ b/src/components/super/i-input-text/test/b-super-i-input-text-dummy/b-super-i-input-text-dummy.ts @@ -8,8 +8,12 @@ import iInputText, { component } from 'components/super/i-input-text/i-input-text'; +import Mask, * as MaskAPI from 'components/super/i-input-text/mask'; + export * from 'components/super/i-input-text/i-input-text'; +Mask.addToPrototype(MaskAPI); + @component({ functional: { functional: true, diff --git a/src/components/super/i-static-page/i-static-page.ts b/src/components/super/i-static-page/i-static-page.ts index c94652c447..23997751c9 100644 --- a/src/components/super/i-static-page/i-static-page.ts +++ b/src/components/super/i-static-page/i-static-page.ts @@ -27,6 +27,7 @@ import type bRouter from 'components/base/b-router/b-router'; import type iBlock from 'components/super/i-block/i-block'; import iPage, { component, field, system, computed, hook, watch } from 'components/super/i-page/i-page'; +import AsyncRender, { iterate } from 'components/friends/async-render'; import createProviderDataStore, { ProviderDataStore } from 'components/super/i-static-page/modules/provider-data-store'; @@ -39,6 +40,8 @@ export * from 'components/super/i-static-page/modules/provider-data-store'; export * from 'components/super/i-static-page/interface'; +AsyncRender.addToPrototype({iterate}); + const $$ = symbolGenerator(); diff --git a/src/core/component/functional/test/unit/getters.ts b/src/core/component/functional/test/unit/getters.ts index 0fe8f2a221..5d3fbfbf50 100644 --- a/src/core/component/functional/test/unit/getters.ts +++ b/src/core/component/functional/test/unit/getters.ts @@ -20,7 +20,6 @@ test.describe('functional component getters', () => { test.beforeEach(async ({demoPage, page}) => { await demoPage.goto(); - await Promise.all([ Component.waitForComponentTemplate(page, 'b-functional-dummy'), Component.waitForComponentTemplate(page, 'b-functional-button-dummy') diff --git a/src/core/component/functional/test/unit/main.ts b/src/core/component/functional/test/unit/main.ts index b457a0bed1..8554a39326 100644 --- a/src/core/component/functional/test/unit/main.ts +++ b/src/core/component/functional/test/unit/main.ts @@ -28,6 +28,7 @@ test.describe('functional component', () => { ]); target = await Component.createComponent(page, 'b-functional-dummy', {stage: 'main'}); + text = page.getByText(/Counter/); button = page.getByRole('button'); }); diff --git a/src/core/component/init/component.ts b/src/core/component/init/component.ts index fae4173e5f..b3e699732f 100644 --- a/src/core/component/init/component.ts +++ b/src/core/component/init/component.ts @@ -5,11 +5,14 @@ * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import config from 'config'; import { isComponent, componentRegInitializers, componentParams, components } from 'core/component/const'; import type { ComponentMeta } from 'core/component/interface'; -import type { ComponentConstructorInfo } from 'core/component/reflect'; +import { ComponentConstructorInfo, isSmartComponent } from 'core/component/reflect'; + +import { initEmitter } from 'core/component/event'; /** * Registers parent components for the given one. @@ -70,6 +73,20 @@ export function registerComponent(name: CanUndef): CanNull): CanNull, opts: CreateAppOptions, - state: State + state: State, + layer?: string ): Promise { + initEmitter.emit(`registerComponent.${layer}.${rootComponentName}`); + const rootComponentParams = await getRootComponentParams(rootComponentName); opts.setup?.(Object.cast(rootComponentParams));