From 9e6af3b217e65b530df4fc8fb10c2c47b427176c Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 22 Dec 2023 21:52:14 +0700 Subject: [PATCH 01/21] chore: create memory leak example --- .../p-v4-components-demo.ss | 23 + .../p-v4-components-demo.ts | 1026 +++++++++++++++++ .../p-v4-dynamic-page1/p-v4-dynamic-page1.ss | 9 + .../p-v4-dynamic-page2/p-v4-dynamic-page2.ss | 9 + .../p-v4-dynamic-page3/p-v4-dynamic-page3.ss | 9 + src/routes/index.ts | 17 +- 6 files changed, 1091 insertions(+), 2 deletions(-) diff --git a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss index 2988d4c196..4fbe0a5395 100644 --- a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss +++ b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss @@ -11,3 +11,26 @@ - include 'components/super/i-static-page/i-static-page.component.ss'|b as placeholder - template index() extends ['i-static-page.component'].index + - block helpers + - block router + < b-router & + v-once | + :initialRoute = "page1" + . + + - block body + < h1 + Active page: {{ activePage }} + + < hr + + < b-button :type = 'link' | :href = '/page1' + Page1 + < b-button :type = 'link' | :href = '/page2' + Page2 + < b-button :type = 'link' | :href = '/page3' + Page3 + + < hr + + < b-dynamic-page 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 33b4bd13d4..cc5b0039c3 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,6 +13,7 @@ import iStaticPage, { component, prop, field, system } from 'components/super/i-static-page/i-static-page'; import VDOM, * as VDOMAPI from 'components/friends/vdom'; +import type { Item } from 'components/traits/i-active-items/interface'; export * from 'components/super/i-static-page/i-static-page'; @@ -21,6 +22,8 @@ VDOM.addToPrototype(VDOMAPI); // eslint-disable-next-line no-console console.time('Initializing'); +let items: Item[] = createItems(); + /** * Page with component demos. * Basically it uses with component tests. @@ -46,10 +49,1033 @@ export default class pV4ComponentsDemo extends iStaticPage { @field() someField: unknown = 'foo'; + @system() + items: Item[] = items; + protected beforeCreate(): void { //#unless runtime has storybook // eslint-disable-next-line no-console console.time('Render'); //#endunless } + + protected updateSomeField(): void { + this.someField = Math.random(); + } +} + + +function createItems() { + return [ + { + "label": "Props", + "folded": false, + "children": [ + { + "label": "selfDispatchingProp", + "data": false + }, + { + "label": "iPage", + "folded": true, + "children": [ + { + "label": "hideIfOffline", + "data": false + }, + { + "label": "pageTitleProp", + "data": "" + }, + { + "label": "pageDescriptionProp", + "data": "" + }, + { + "label": "stagePageTitles" + } + ] + }, + { + "label": "iData", + "folded": true, + "children": [ + { + "label": "dataProviderProp" + }, + { + "label": "dataProviderOptions" + }, + { + "label": "request" + }, + { + "label": "dbConverter" + }, + { + "label": "componentConverter" + }, + { + "label": "defaultRequestFilter" + }, + { + "label": "suspendedRequestsProp", + "data": false + }, + { + "label": "offlineReload", + "data": false + }, + { + "label": "checkDBEquality", + "data": true + } + ] + }, + { + "label": "iBlock", + "folded": true, + "children": [ + { + "label": "componentIdProp" + }, + { + "label": "globalName" + }, + { + "label": "rootTag", + "data": "div" + }, + { + "label": "verbose", + "data": false + }, + { + "label": "stageProp" + }, + { + "label": "modsProp" + }, + { + "label": "activatedProp", + "data": true + }, + { + "label": "forceActivation", + "data": false + }, + { + "label": "reloadOnActivation", + "data": true + }, + { + "label": "renderOnActivation", + "data": false + }, + { + "label": "dependenciesProp" + }, + { + "label": "ssrRendering", + "data": true + }, + { + "label": "wait" + }, + { + "label": "remoteProvider", + "data": false + }, + { + "label": "dontWaitRemoteProvidersProp" + }, + { + "label": "syncRouterStoreOnInit", + "data": true + }, + { + "label": "routerStateUpdateMethod", + "data": "push" + }, + { + "label": "watchProp" + }, + { + "label": "proxyCall", + "data": false + }, + { + "label": "dispatching", + "data": false + }, + { + "label": "p" + }, + { + "label": "classes" + }, + { + "label": "styles" + }, + { + "label": "renderComponentId", + "data": true + }, + { + "label": "getRoot" + }, + { + "label": "getParent" + } + ] + } + ] + }, + { + "label": "Fields", + "folded": false, + "children": [ + { + "label": "someField", + "data": "foo" + }, + { + "label": "iStaticPage", + "folded": true, + "children": [ + { + "label": "isAuth", + "data": false + }, + { + "label": "isOnline", + "data": true + }, + { + "label": "shouldMountTeleports", + "data": true + }, + { + "label": "routeStore" + }, + { + "label": "localeStore", + "data": "en" + } + ] + }, + { + "label": "iData", + "folded": true, + "children": [ + { + "label": "dbStore" + } + ] + }, + { + "label": "iBlock", + "folded": true, + "children": [ + { + "label": "reactiveTmp", + "data": "{}", + "children": [] + }, + { + "label": "rootAttrsStore", + "data": "{}", + "children": [] + }, + { + "label": "reactiveModsStore", + "data": "{}", + "children": [] + }, + { + "label": "componentStatusStore", + "data": "beforeReady" + }, + { + "label": "stageStore" + } + ] + } + ] + }, + { + "label": "ComputedFields", + "folded": false, + "children": [ + { + "label": "iStaticPage", + "folded": true, + "children": [ + { + "label": "activePage" + }, + { + "label": "locale", + "data": "en" + } + ] + }, + { + "label": "iPage", + "folded": true, + "children": [ + { + "label": "scrollToProxy", + "data": { + "declaration": "(...args) => {\n this.async.setImmediate(() => this.scrollTo(...args), {\n label: $$.scrollTo\n });\n }" + } + } + ] + }, + { + "label": "iData", + "folded": true, + "children": [ + { + "label": "db" + } + ] + }, + { + "label": "iBlock", + "folded": true, + "children": [ + { + "label": "rootAttrs", + "data": "{}", + "children": [] + }, + { + "label": "sharedMods", + "data": null + }, + { + "label": "m", + "data": "{\"hidden\":\"false\"}", + "children": [ + { + "label": "hidden", + "data": "false" + } + ] + } + ] + } + ] + }, + { + "label": "SystemFields", + "folded": false, + "children": [ + { + "label": "console" + }, + { + "label": "rootParam" + }, + { + "label": "iStaticPage", + "folded": true, + "children": [ + { + "label": "pageMetaData", + "data": "PageMetaData" + }, + { + "label": "providerDataStore", + "data": "RestrictedCache" + }, + { + "label": "theme", + "data": null + }, + { + "label": "lastOnlineDate" + }, + { + "label": "initialRoute" + }, + { + "label": "globalEnv", + "data": "{}", + "children": [] + }, + { + "label": "routerStore" + }, + { + "label": "rootMods", + "data": "{\"p-v4-components-demo_active\"...}", + "children": [ + { + "label": "p-v4-components-demo_active", + "data": "{\"name\":\"active\",\"value\":\"true...}", + "children": [ + { + "label": "name", + "data": "active" + }, + { + "label": "value", + "data": "true" + }, + { + "label": "class", + "data": "p-v4-components-demo-active-true" + }, + { + "label": "component", + "data": "Object", + "children": [ + { + "label": "$root", + "data": "*restricted*" + }, + { + "label": "$children", + "data": "*restricted*" + }, + { + "label": "rootAttrs", + "data": "{}", + "children": [] + }, + { + "label": "m", + "data": "{\"hidden\":\"false\"}", + "children": [ + { + "label": "hidden", + "data": "false" + } + ] + }, + { + "label": "scrollToProxy", + "data": { + "declaration": "(...args) => {\n this.async.setImmediate(() => this.scrollTo(...args), {\n label: $$.scrollTo\n });\n }" + } + }, + { + "label": "locale", + "data": "en" + }, + { + "label": "isFunctional", + "data": false + }, + { + "label": "self", + "data": "*restricted*" + }, + { + "label": "r", + "data": "*restricted*" + }, + { + "label": "i18n", + "data": { + "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" + } + }, + { + "label": "t", + "data": { + "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" + } + }, + { + "label": "randomGenerator", + "data": "Xor128" + }, + { + "label": "remoteState", + "data": "{\"isAuth\":false,\"isOnline\":tru...}", + "children": [ + { + "label": "isAuth", + "data": false + }, + { + "label": "isOnline", + "data": true + }, + { + "label": "globalEnv", + "data": "{}", + "children": [] + } + ] + }, + { + "label": "componentStatus", + "data": "ready" + }, + { + "label": "isReady", + "data": true + }, + { + "label": "stageGroup", + "data": "stage.undefined" + }, + { + "label": "isRelatedToSSR", + "data": false + }, + { + "label": "hook", + "data": "mounted" + }, + { + "label": "router", + "data": "*restricted*" + }, + { + "label": "pageTitle", + "data": "Default app" + }, + { + "label": "$fields", + "data": "*restricted*" + }, + { + "label": "$watch", + "data": "*restricted*" + }, + { + "label": "$set", + "data": "*restricted*" + }, + { + "label": "$delete", + "data": "*restricted*" + }, + { + "label": "$systemFields", + "data": "*restricted*" + }, + { + "label": "pageTitleStore", + "data": "" + } + ] + } + ] + }, + { + "label": "p-v4-components-demo_online", + "data": "{\"name\":\"online\",\"value\":\"true...}", + "children": [ + { + "label": "name", + "data": "online" + }, + { + "label": "value", + "data": "true" + }, + { + "label": "class", + "data": "p-v4-components-demo-online-true" + }, + { + "label": "component", + "data": "Object", + "children": [ + { + "label": "$root", + "data": "*restricted*" + }, + { + "label": "$children", + "data": "*restricted*" + }, + { + "label": "rootAttrs", + "data": "{}", + "children": [] + }, + { + "label": "m", + "data": "{\"hidden\":\"false\"}", + "children": [ + { + "label": "hidden", + "data": "false" + } + ] + }, + { + "label": "scrollToProxy", + "data": { + "declaration": "(...args) => {\n this.async.setImmediate(() => this.scrollTo(...args), {\n label: $$.scrollTo\n });\n }" + } + }, + { + "label": "locale", + "data": "en" + }, + { + "label": "isFunctional", + "data": false + }, + { + "label": "self", + "data": "*restricted*" + }, + { + "label": "r", + "data": "*restricted*" + }, + { + "label": "i18n", + "data": { + "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" + } + }, + { + "label": "t", + "data": { + "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" + } + }, + { + "label": "randomGenerator", + "data": "Xor128" + }, + { + "label": "remoteState", + "data": "{\"isAuth\":false,\"isOnline\":tru...}", + "children": [ + { + "label": "isAuth", + "data": false + }, + { + "label": "isOnline", + "data": true + }, + { + "label": "globalEnv", + "data": "{}", + "children": [] + } + ] + }, + { + "label": "componentStatus", + "data": "ready" + }, + { + "label": "isReady", + "data": true + }, + { + "label": "stageGroup", + "data": "stage.undefined" + }, + { + "label": "isRelatedToSSR", + "data": false + }, + { + "label": "hook", + "data": "mounted" + }, + { + "label": "router", + "data": "*restricted*" + }, + { + "label": "pageTitle", + "data": "Default app" + }, + { + "label": "$fields", + "data": "*restricted*" + }, + { + "label": "$watch", + "data": "*restricted*" + }, + { + "label": "$set", + "data": "*restricted*" + }, + { + "label": "$delete", + "data": "*restricted*" + }, + { + "label": "$systemFields", + "data": "*restricted*" + }, + { + "label": "pageTitleStore", + "data": "" + } + ] + } + ] + } + ] + } + ] + }, + { + "label": "iPage", + "folded": true, + "children": [ + { + "label": "pageTitleStore", + "data": "" + }, + { + "label": "pageDescriptionStore", + "data": "" + } + ] + }, + { + "label": "iData", + "folded": true, + "children": [ + { + "label": "dataProvider" + }, + { + "label": "dbConverters", + "data": "[]", + "children": [] + }, + { + "label": "componentConverters", + "data": "[]", + "children": [] + }, + { + "label": "suspendedRequests", + "data": false + }, + { + "label": "requestParams", + "data": "{\"get\":{}}", + "children": [ + { + "label": "get", + "data": "{}", + "children": [] + } + ] + } + ] + }, + { + "label": "iBlock", + "folded": true, + "children": [ + { + "label": "provide", + "data": "Provide" + }, + { + "label": "infoRender", + "data": "InfoRender" + }, + { + "label": "field", + "data": "Field" + }, + { + "label": "analytics", + "data": "Analytics" + }, + { + "label": "sync", + "data": "Sync" + }, + { + "label": "asyncRender", + "data": "AsyncRender" + }, + { + "label": "vdom", + "data": "VDOM" + }, + { + "label": "lfc", + "data": "Lfc" + }, + { + "label": "daemons", + "data": "Daemons" + }, + { + "label": "block", + "data": "Block" + }, + { + "label": "dom", + "data": "DOM" + }, + { + "label": "storage", + "data": "Storage" + }, + { + "label": "state", + "data": "State" + }, + { + "label": "moduleLoader", + "data": "ModuleLoader" + }, + { + "label": "ifOnceStore", + "data": "{}", + "children": [] + }, + { + "label": "opt", + "data": "Opt" + }, + { + "label": "browser", + "data": "{\"is\":{\"Chrome\":[\"Chrome\",[116...}", + "children": [ + { + "label": "is", + "data": "{\"Chrome\":[\"Chrome\",[116,0,584...}", + "children": [ + { + "label": "Chrome", + "data": "Array(2)", + "children": [ + { + "label": "0", + "data": "Chrome" + }, + { + "label": "1", + "data": "Array(4)", + "children": [ + { + "label": "0", + "data": 116 + }, + { + "label": "1", + "data": 0 + }, + { + "label": "2", + "data": 5845 + }, + { + "label": "3", + "data": 2319 + } + ] + } + ] + }, + { + "label": "Firefox", + "data": false + }, + { + "label": "Android", + "data": false + }, + { + "label": "BlackBerry", + "data": false + }, + { + "label": "iOS", + "data": false + }, + { + "label": "OperaMini", + "data": false + }, + { + "label": "WindowsMobile", + "data": false + }, + { + "label": "Safari", + "data": false + }, + { + "label": "mobile", + "data": false + } + ] + }, + { + "label": "match", + "data": { + "declaration": "function match(pattern) {\n var _a, _b;\n if (typeof navigator === 'undefined') {\n return false;\n }\n const { userAgent } = navigator;\n let name, version;\n if (Object.isFunction(pattern)) {\n [name, version] = (_a = pattern(userAgent)) !== null && _a !== void 0 ? _a : [];\n }\n else {\n const rgxp = Object.isString(pattern) ? new RegExp(`(${pattern})(?:[ \\\\/-]([0-9._]*))?`, 'i') : pattern;\n [, name, version] = (_b = rgxp.exec(userAgent)) !== null && _b !== void 0 ? _b : [];\n }\n const versionParts = version != null && version.length !== 0 ?\n version.split(/[._]/).map(map) :\n null;\n if (name != null) {\n return [name, versionParts];\n }\n return false;\n function map(el) {\n const v = parseInt(el, 10);\n return Object.isTruly(v) ? v : 0;\n }\n}" + } + }, + { + "label": "test", + "data": { + "declaration": "function test(platform, operation, version) {\n const val = core_browser_const__WEBPACK_IMPORTED_MODULE_1__.is[platform];\n if (val === false) {\n return false;\n }\n if (operation == null || version == null) {\n return true;\n }\n if (val[1] == null) {\n return false;\n }\n return (0,core_semver__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(val[1].join('.'), version, operation);\n}" + } + } + ] + }, + { + "label": "presets", + "data": "{\"loopback\":{}}", + "children": [ + { + "label": "loopback", + "data": "{}", + "children": [] + } + ] + }, + { + "label": "h", + "data": "{\"NBSP\":\" \"}", + "children": [ + { + "label": "NBSP", + "data": " " + } + ] + }, + { + "label": "location", + "data": "Location" + }, + { + "label": "global", + "data": "Window" + }, + { + "label": "componentId", + "data": "u11b99ae2e0e3b2" + }, + { + "label": "isActivated", + "data": true + }, + { + "label": "isVirtualTpl", + "data": false + }, + { + "label": "beforeReadyListeners", + "data": 0 + }, + { + "label": "blockReadyListeners", + "data": "[]", + "children": [] + }, + { + "label": "tmp", + "data": "{}", + "children": [] + }, + { + "label": "watchCache", + "data": "{}", + "children": [] + }, + { + "label": "componentI18nKeysets", + "data": "Array(5)", + "children": [ + { + "label": "0", + "data": "p-v4-components-demo" + }, + { + "label": "1", + "data": "i-static-page" + }, + { + "label": "2", + "data": "i-page" + }, + { + "label": "3", + "data": "i-data" + }, + { + "label": "4", + "data": "i-block" + } + ] + }, + { + "label": "selfEmitter", + "data": "{}", + "children": [] + }, + { + "label": "localEmitter", + "data": "EventEmitter" + }, + { + "label": "parentEmitter", + "data": "{}", + "children": [] + }, + { + "label": "rootEmitter", + "data": "{}", + "children": [] + }, + { + "label": "globalEmitter", + "data": "EventEmitter" + }, + { + "label": "mods", + "data": "{\"hidden\":\"false\"}", + "children": [ + { + "label": "hidden", + "data": "false" + } + ] + }, + { + "label": "dependencies", + "data": "[]", + "children": [] + }, + { + "label": "isReadyOnce", + "data": true + }, + { + "label": "shadowComponentStatusStore", + "data": "ready" + }, + { + "label": "dontWaitRemoteProviders", + "data": false + }, + { + "label": "selfDispatching", + "data": false + } + ] + } + ] + } + ] } diff --git a/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss b/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss index bf471f2d17..5d93148642 100644 --- a/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss +++ b/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss @@ -17,3 +17,12 @@ < b-button.&__button-func Functional button + + < b-tree & + :items = r.items | + :theme = 'demo' | + :folded = false | + :cancelable = true + . + < template #default = {item} + {{item.label}}: {{item.data}} diff --git a/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss b/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss index 20a01b59f5..fdd26a18f6 100644 --- a/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss +++ b/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss @@ -17,3 +17,12 @@ < b-button.&__button-func Functional button + + < b-tree & + :items = r.items | + :theme = 'demo' | + :folded = false | + :cancelable = true + . + < template #default = {item} + {{item.label}}: {{item.data}} diff --git a/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss b/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss index 64a129d467..c609e330f5 100644 --- a/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss +++ b/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss @@ -17,3 +17,12 @@ < b-button.&__button-func Functional button + + < b-tree & + :items = r.items | + :theme = 'demo' | + :folded = false | + :cancelable = true + . + < template #default = {item} + {{item.label}}: {{item.data}} diff --git a/src/routes/index.ts b/src/routes/index.ts index 806f4be9d6..86a643c4cf 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -5,7 +5,20 @@ * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import type { StaticRoutes } from 'components/base/b-router/b-router'; -export default { - +export default { + page1: { + component: 'p-v4-dynamic-page1', + path: 'page1', + default: true + }, + page2: { + component: 'p-v4-dynamic-page2', + path: 'page2', + }, + page3: { + component: 'p-v4-dynamic-page3', + path: 'page3', + } }; From 953f02329a645bd9c9f20f0c52cd37955d858310 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 12 Jan 2024 16:45:26 +0300 Subject: [PATCH 02/21] fix: fixed memory leaks --- .../friends/async-render/iterate.ts | 2 +- src/components/friends/vdom/render.ts | 11 ++-- src/components/super/i-block/event/index.ts | 12 ++++- src/core/component/engines/vue3/render.ts | 14 +++-- src/core/component/event/component.ts | 24 ++++++--- .../interface/component/component.ts | 51 +++++++++++++++---- src/core/component/interface/engine.ts | 4 +- 7 files changed, 90 insertions(+), 28 deletions(-) diff --git a/src/components/friends/async-render/iterate.ts b/src/components/friends/async-render/iterate.ts index 491f3dbf5c..c9f155f20c 100644 --- a/src/components/friends/async-render/iterate.ts +++ b/src/components/friends/async-render/iterate.ts @@ -310,7 +310,7 @@ export function iterate( renderedVnode = Object.cast(vnode.el); } else { - renderedVnode = render.call(that, Object.cast(vnode)); + renderedVnode = render.call(that, Object.cast(vnode), group); } const diff --git a/src/components/friends/vdom/render.ts b/src/components/friends/vdom/render.ts index 51c730fa46..8c14bb0176 100644 --- a/src/components/friends/vdom/render.ts +++ b/src/components/friends/vdom/render.ts @@ -16,6 +16,7 @@ import type { RenderFactory, RenderFn } from 'components/friends/vdom/interface' * Renders the specified VNode and returns the result * * @param vnode + * @param [group] - the name of the async group within which rendering takes place * * @example * ```js @@ -25,12 +26,13 @@ import type { RenderFactory, RenderFn } from 'components/friends/vdom/interface' * console.log(div.classList.contains('foo')); // true * ``` */ -export function render(this: Friend, vnode: VNode): Node; +export function render(this: Friend, vnode: VNode, group?: string): Node; /** * Renders the specified list of VNodes and returns the result * * @param vnodes + * @param [group] - the name of the async group within which rendering takes place * * @example * ```js @@ -43,9 +45,10 @@ export function render(this: Friend, vnode: VNode): Node; * console.log(div[1].classList.contains('bar')); // true * ``` */ -export function render(this: Friend, vnodes: VNode[]): Node[]; -export function render(this: Friend, vnode: CanArray): CanArray { - return this.ctx.$renderEngine.r.render(Object.cast(vnode), this.ctx); +export function render(this: Friend, vnodes: VNode[], group?: string): Node[]; + +export function render(this: Friend, vnode: CanArray, group?: string): CanArray { + return this.ctx.$renderEngine.r.render(Object.cast(vnode), this.ctx, group); } /** diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index 3d6de54ecf..5bd009130c 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -17,7 +17,7 @@ import { EventEmitter2 as EventEmitter } from 'eventemitter2'; import SyncPromise from 'core/promise/sync'; import type Async from 'core/async'; -import type { AsyncOptions, EventEmitterWrapper, ReadonlyEventEmitterWrapper } from 'core/async'; +import type { AsyncOptions, EventEmitterWrapper, ReadonlyEventEmitterWrapper, EventId } from 'core/async'; import { component, globalEmitter } from 'core/component'; @@ -91,7 +91,15 @@ export default abstract class iBlockEvent extends iBlockBase { init: (o, d) => (d.async).wrapEventEmitter({ on: (event: string, handler: Function) => o.$on(normalizeEventName(event), handler), once: (event: string, handler: Function) => o.$once(normalizeEventName(event), handler), - off: o.$off.bind(o), + + off: (eventOrLink: string | EventId, handler: Function) => { + if (Object.isString(eventOrLink)) { + return o.$off(normalizeEventName(eventOrLink), handler); + } + + return o.$off(eventOrLink); + }, + emit: o.emit.bind(o), strictEmit: o.emit.bind(o) }) diff --git a/src/core/component/engines/vue3/render.ts b/src/core/component/engines/vue3/render.ts index c871ca7312..9305b31423 100644 --- a/src/core/component/engines/vue3/render.ts +++ b/src/core/component/engines/vue3/render.ts @@ -133,10 +133,12 @@ export const * * @param vnode * @param [parent] - the parent component + * @param [group] - the name of the async group within which rendering takes place */ export function render( vnode: VNode, - parent?: ComponentInterface + parent?: ComponentInterface, + group?: string ): Node; /** @@ -144,13 +146,15 @@ export function render( * * @param vnodes * @param [parent] - the parent component + * @param [group] - the name of the async group within which rendering takes place */ export function render( vnodes: VNode[], - parent?: ComponentInterface + parent?: ComponentInterface, + group?: string ): Node[]; -export function render(vnode: CanArray, parent?: ComponentInterface): CanArray { +export function render(vnode: CanArray, parent?: ComponentInterface, group?: string): CanArray { const vue = new Vue({ render: () => vnode, @@ -171,6 +175,10 @@ export function render(vnode: CanArray, parent?: ComponentInterface): Can writable: true, value: root }); + + parent.unsafe.async.worker(() => { + vue.unmount(); + }, {group}); } } }); diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index da3f085137..74aa9f7c68 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -6,6 +6,8 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import type { EventId } from 'core/async'; + import { EventEmitter2 as EventEmitter } from 'eventemitter2'; import type { UnsafeComponentInterface } from 'core/component/interface'; @@ -55,8 +57,6 @@ export function resetComponents(type?: ComponentResetType): void { * @param component */ export function implementEventEmitterAPI(component: object): void { - /* eslint-disable @typescript-eslint/typedef */ - const ctx = Object.cast(component); @@ -74,7 +74,7 @@ export function implementEventEmitterAPI(component: object): void { enumerable: false, writable: false, - value(event: string, ...args) { + value(event: string, ...args: unknown[]) { if (!event.startsWith('[[')) { nativeEmit?.(event, ...args); } @@ -106,16 +106,28 @@ export function implementEventEmitterAPI(component: object): void { }); function getMethod(method: 'on' | 'once' | 'off') { - return function wrapper(this: unknown, event, cb) { + return function wrapper(this: unknown, event: CanArray, cb?: Function) { + const + links: EventId[] = [], + isOnLike = method !== 'off'; + Array.concat([], event).forEach((event) => { - if (method === 'off' && cb == null) { + if (isOnLike && cb == null) { $e.removeAllListeners(event); } else { - $e[method](Object.cast(event), Object.cast(cb)); + const link = $e[method](Object.cast(event), Object.cast(cb)); + + if (isOnLike) { + links.push(Object.cast(link)); + } } }); + if (isOnLike) { + return Object.isArray(event) ? links : links[0]; + } + return this; }; } diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index 4861ce47a3..1e68228472 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -9,7 +9,7 @@ /* eslint-disable @typescript-eslint/unified-signatures */ import type Async from 'core/async'; -import type { BoundFn, ProxyCb } from 'core/async'; +import type { BoundFn, ProxyCb, EventId } from 'core/async'; import type { State } from 'core/component/state'; import type { HydrationStore } from 'core/component/hydration'; @@ -438,30 +438,61 @@ export abstract class ComponentInterface { /** * Attaches a listener to the specified component's event * - * @param _event - * @param _handler + * @param event + * @param handler */ - protected $on(_event: CanArray, _handler: ProxyCb): this { + protected $on(event: string, handler: ProxyCb): EventId; + + /** + * Attaches a listener to the specified component's events + * + * @param events + * @param handler + */ + protected $on(events: string[], handler: ProxyCb): EventId[]; + + protected $on(_event: CanArray, _handler: ProxyCb): CanArray { return Object.throw(); } /** * Attaches a disposable listener to the specified component's event * - * @param _event - * @param _handler + * @param event + * @param handler + */ + protected $once(event: string, handler: ProxyCb): EventId; + + /** + * Attaches a disposable listener to the specified component's event + * + * @param events + * @param handler */ - protected $once(_event: string, _handler: ProxyCb): this { + protected $once(events: string[], handler: ProxyCb): EventId[]; + + protected $once( + _event: CanArray, + _handler: ProxyCb + ): CanArray { return Object.throw(); } + /** + * Detaches the specified event listeners from the component + * @param [link] + */ + protected $off(link: CanArray): this; + /** * Detaches the specified event listeners from the component * - * @param [_event] - * @param [_handler] + * @param [event] + * @param [handler] */ - protected $off(_event?: CanArray, _handler?: Function): this { + protected $off(event?: CanArray, handler?: Function): this; + + protected $off(_event?: CanArray, _handler?: Function): this { return Object.throw(); } diff --git a/src/core/component/interface/engine.ts b/src/core/component/interface/engine.ts index bc001caa37..cdbd0c2b05 100644 --- a/src/core/component/interface/engine.ts +++ b/src/core/component/interface/engine.ts @@ -99,8 +99,8 @@ export type ProxyGetterType = 'mounted'; export interface RenderAPI { - render(vnode: VNode, parent?: ComponentInterface): Node; - render(vnode: VNode[], parent?: ComponentInterface): Node[]; + render(vnode: VNode, parent?: ComponentInterface, group?: string): Node; + render(vnode: VNode[], parent?: ComponentInterface, group?: string): Node[]; getCurrentInstance: typeof getCurrentInstance; From 584e3f23b7d84ef6835ee992b9e52087dc7d6ab3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 12 Jan 2024 16:45:38 +0300 Subject: [PATCH 03/21] chore: updated dependencies --- yarn.lock | 76 +++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0f89e468fc..b4084fca41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -61,13 +61,13 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/code-frame@npm:7.23.4" +"@babel/code-frame@npm:^7.23.5": + version: 7.23.5 + resolution: "@babel/code-frame@npm:7.23.5" dependencies: "@babel/highlight": "npm:^7.23.4" chalk: "npm:^2.4.2" - checksum: 5a210e42b0c3138f3870e452c7b6d06ddcfc43cba824231ef3023fffd1cb0613d00ea07c7d87d0718e14e830f891b86de56aac5cd034d41128383919c84ff4f6 + checksum: 44e58529c9d93083288dc9e649c553c5ba997475a7b0758cc3ddc4d77b8a7d985dbe78cc39c9bbc61f26d50af6da1ddf0a3427eae8cc222a9370619b671ed8f5 languageName: node linkType: hard @@ -159,15 +159,15 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/generator@npm:7.23.4" +"@babel/generator@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/generator@npm:7.23.6" dependencies: - "@babel/types": "npm:^7.23.4" + "@babel/types": "npm:^7.23.6" "@jridgewell/gen-mapping": "npm:^0.3.2" "@jridgewell/trace-mapping": "npm:^0.3.17" jsesc: "npm:^2.5.1" - checksum: 7b45b64505bfb3ddbdeaae01288d2814e0e8d1299b3485983f4abc6563d6c10837979f00021308c78c33564d33e6d715e63aed64ac407ed8440b76f6eeb79019 + checksum: 864090d5122c0aa3074471fd7b79d8a880c1468480cbd28925020a3dcc7eb6e98bedcdb38983df299c12b44b166e30915b8085a7bc126e68fa7e2aadc7bd1ac5 languageName: node linkType: hard @@ -530,12 +530,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.16.7, @babel/parser@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/parser@npm:7.23.4" +"@babel/parser@npm:^7.16.7, @babel/parser@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/parser@npm:7.23.6" bin: parser: ./bin/babel-parser.js - checksum: 73c0172d2784c93455cb72a4669af5711a8f0421812d0c93e3be46bc7aee50e9215f61df90f94daf0555736ca2236f284462218f6bbc6bc804ebd94a59324f72 + checksum: 6be3a63d3c9d07b035b5a79c022327cb7e16cbd530140ecb731f19a650c794c315a72c699a22413ebeafaff14aa8f53435111898d59e01a393d741b85629fa7d languageName: node linkType: hard @@ -2068,20 +2068,20 @@ __metadata: linkType: hard "@babel/traverse@npm:^7.16.7": - version: 7.23.4 - resolution: "@babel/traverse@npm:7.23.4" + version: 7.23.7 + resolution: "@babel/traverse@npm:7.23.7" dependencies: - "@babel/code-frame": "npm:^7.23.4" - "@babel/generator": "npm:^7.23.4" + "@babel/code-frame": "npm:^7.23.5" + "@babel/generator": "npm:^7.23.6" "@babel/helper-environment-visitor": "npm:^7.22.20" "@babel/helper-function-name": "npm:^7.23.0" "@babel/helper-hoist-variables": "npm:^7.22.5" "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.23.4" - "@babel/types": "npm:^7.23.4" - debug: "npm:^4.1.0" + "@babel/parser": "npm:^7.23.6" + "@babel/types": "npm:^7.23.6" + debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 0ff190a793d94c8ee3ff24bbe7d086c6401a84fa16f97d3c695c31aa42270916d937ae5994e315ba797e8f3728840e4d68866ad4d82a01132312d07ac45ca9d0 + checksum: 3215e59429963c8dac85c26933372cdd322952aa9930e4bc5ef2d0e4bd7a1510d1ecf8f8fd860ace5d4d9fe496d23805a1ea019a86410aee4111de5f63ee84f9 languageName: node linkType: hard @@ -2132,14 +2132,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.16.7, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/types@npm:7.23.4" +"@babel/types@npm:^7.16.7, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.6": + version: 7.23.6 + resolution: "@babel/types@npm:7.23.6" dependencies: "@babel/helper-string-parser": "npm:^7.23.4" "@babel/helper-validator-identifier": "npm:^7.22.20" to-fast-properties: "npm:^2.0.0" - checksum: acf791ead82bb220f35cc0cd53c852d96f3fbad14b20964719bae884737b6bb227bfe28c4d16274bee0c8cf0cf3c4c1882d20d894ffc9667dda6eb197ccb4262 + checksum: 07e70bb94d30b0231396b5e9a7726e6d9227a0a62e0a6830c0bd3232f33b024092e3d5a7d1b096a65bbf2bb43a9ab4c721bf618e115bfbb87b454fa060f88cbf languageName: node linkType: hard @@ -5640,8 +5640,8 @@ __metadata: linkType: hard "@v4fire/core@github:V4Fire/Core#v4": - version: 4.0.0-alpha.14 - resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=4496d6e0b33ff872556ebb4730a88eb3069eafa7" + version: 4.0.0-alpha.19 + resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=2ab18049421b9b78811f1ecae7a3f8f712b8e99b" dependencies: "@babel/core": "npm:7.17.5" "@babel/helper-module-transforms": "npm:7.16.7" @@ -5785,7 +5785,7 @@ __metadata: optional: true xhr2: optional: true - checksum: 57c1789417a0dca9289dda4465352727fdd656ab62b70c4b938a233dba61a714ee3d43bd3c929a44d554b581338eab2ffd8179019377688c480c31b29af7484d + checksum: 0588d359c980d39de770f832742395faf50ec2c36566acfd537cdf9e524300020c0d47bb9356866d0fabd20bebcb01143ca4b844a7068171f30854c48011cf9f languageName: node linkType: hard @@ -6282,9 +6282,9 @@ __metadata: linkType: hard "acorn-walk@npm:^8.1.1": - version: 8.3.0 - resolution: "acorn-walk@npm:8.3.0" - checksum: 7673f342db939adc16ac3596c374a56be33e6ef84e01dfb3a0b50cc87cf9b8e46d84c337dcd7d5644f75bf219ad5a36bf33795e9f1af15298e6bceacf46c5f1f + version: 8.3.2 + resolution: "acorn-walk@npm:8.3.2" + checksum: 57dbe2fd8cf744f562431775741c5c087196cd7a65ce4ccb3f3981cdfad25cd24ad2bad404997b88464ac01e789a0a61e5e355b2a84876f13deef39fb39686ca languageName: node linkType: hard @@ -6316,11 +6316,11 @@ __metadata: linkType: hard "acorn@npm:^8.4.1, acorn@npm:^8.5.0": - version: 8.11.2 - resolution: "acorn@npm:8.11.2" + version: 8.11.3 + resolution: "acorn@npm:8.11.3" bin: acorn: bin/acorn - checksum: ff559b891382ad4cd34cc3c493511d0a7075a51f5f9f02a03440e92be3705679367238338566c5fbd3521ecadd565d29301bc8e16cb48379206bffbff3d72500 + checksum: b688e7e3c64d9bfb17b596e1b35e4da9d50553713b3b3630cf5690f2b023a84eac90c56851e6912b483fe60e8b4ea28b254c07e92f17ef83d72d78745a8352dd languageName: node linkType: hard @@ -9428,9 +9428,9 @@ __metadata: linkType: hard "core-js@npm:^3.4": - version: 3.33.3 - resolution: "core-js@npm:3.33.3" - checksum: 77b4c9abaf22ae9c60966121b4b2a4a388cebd067d4cf6ae0f22762b2e8060f301eaacebb781e598ba5f43fe2e53fc88489b013faefdfcecadbf12e242263a50 + version: 3.35.0 + resolution: "core-js@npm:3.35.0" + checksum: 0815fce6bcc91d79d4b28885975453b0faa4d17fc2230635102b4f3832cd621035e4032aa3307e1dbe0ee14d5e34bcb64b507fd89bd8f567aedaf29538522e6a languageName: node linkType: hard @@ -9959,7 +9959,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:*, debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:*, debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: From b1817a0cd6af32ceaea30cd6b1d4317f940a025f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 16 Jan 2024 10:54:06 +0300 Subject: [PATCH 04/21] fix: fixed memory leak --- src/core/component/hook/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index b962de6da2..ece057744f 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -16,9 +16,6 @@ import QueueEmitter from 'core/component/queue-emitter'; import type { Hook, ComponentHook, ComponentInterface } from 'core/component/interface'; -const - resolvedPromise = SyncPromise.resolve(); - /** * Runs a hook on the specified component instance. * The function returns a promise that is resolved when all hook handlers are executed. @@ -114,5 +111,5 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } } - return resolvedPromise; + return SyncPromise.resolve(); } From cd128eeaf94ae8ae6a817d65310163f5428dd85a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 16 Jan 2024 10:57:39 +0300 Subject: [PATCH 05/21] chore(ts): no implicit any --- src/core/component/reflect/mod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/reflect/mod.ts b/src/core/component/reflect/mod.ts index 3f1481fc85..732351b65f 100644 --- a/src/core/component/reflect/mod.ts +++ b/src/core/component/reflect/mod.ts @@ -64,7 +64,7 @@ export function getComponentMods(component: ComponentConstructorInfo): ModsDecl cache = new Map(); let - active; + active: CanUndef; modDecl.forEach((modVal) => { if (Object.isArray(modVal)) { From 9feb3152662376e7727594f8f986cdfe97421419 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 16 Jan 2024 10:59:03 +0300 Subject: [PATCH 06/21] fix: fixed memory leak --- .../friends/async-render/helpers/render.ts | 4 +- src/core/component/accessor/index.ts | 43 +++++++----- src/core/component/context/README.md | 8 +++ src/core/component/context/index.ts | 38 +++++++++-- src/core/component/engines/vue3/render.ts | 66 ++++++++++++++++++- src/core/component/functional/context.ts | 3 + src/core/component/init/states/activated.ts | 4 ++ .../component/init/states/before-destroy.ts | 28 +++++++- src/core/component/init/states/created.ts | 4 ++ src/core/component/init/states/deactivated.ts | 4 ++ src/core/component/init/states/destroyed.ts | 4 ++ src/core/component/interface/engine.ts | 1 + src/core/component/meta/create.ts | 12 +++- src/core/component/watch/create.ts | 5 +- 14 files changed, 192 insertions(+), 32 deletions(-) diff --git a/src/components/friends/async-render/helpers/render.ts b/src/components/friends/async-render/helpers/render.ts index b6efe71910..537493ca0d 100644 --- a/src/components/friends/async-render/helpers/render.ts +++ b/src/components/friends/async-render/helpers/render.ts @@ -70,8 +70,6 @@ export function addRenderTask( * @param [childComponentEls] - root elements of the child components */ export function destroyNode(this: Friend, node: Node, childComponentEls: Element[] = []): void { - node.parentNode?.removeChild(node); - childComponentEls.forEach((child) => { try { (>child).component?.unsafe.$destroy(); @@ -87,4 +85,6 @@ export function destroyNode(this: Friend, node: Node, childComponentEls: Element } catch (err) { stderr(err); } + + node.parentNode?.removeChild(node); } diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 4e8715fb2b..71023da8c1 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -63,6 +63,8 @@ import type { ComponentInterface } from 'core/component/interface'; */ export function attachAccessorsFromMeta(component: ComponentInterface): void { const { + async: $a, + meta, // eslint-disable-next-line deprecation/deprecation meta: {params: {deprecatedProps}} @@ -71,7 +73,28 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const isFunctional = meta.params.functional === true; - Object.entries(meta.computedFields).forEach(([name, computed]) => { + Object.entries(meta.accessors).forEach(([name, accessor]) => { + const canSkip = + accessor == null || + component[name] != null || + !SSR && isFunctional && accessor.functional === false; + + if (canSkip) { + return; + } + + Object.defineProperty(component, name, { + configurable: true, + enumerable: true, + get: accessor.get, + set: accessor.set + }); + }); + + const + computedFields = Object.entries(meta.computedFields); + + computedFields.forEach(([name, computed]) => { const canSkip = component[name] != null || computed == null || computed.cache === 'auto' || @@ -126,21 +149,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { }); }); - Object.entries(meta.accessors).forEach(([name, accessor]) => { - const canSkip = - accessor == null || - component[name] != null || - !SSR && isFunctional && accessor.functional === false; - - if (canSkip) { - return; - } - - Object.defineProperty(component, name, { - configurable: true, - enumerable: true, - get: accessor.get, - set: accessor.set + $a.worker(() => { + computedFields.forEach(([name]) => { + delete component[name]?.[cacheStatus]; }); }); diff --git a/src/core/component/context/README.md b/src/core/component/context/README.md index ecb960c7a6..1bbde93a11 100644 --- a/src/core/component/context/README.md +++ b/src/core/component/context/README.md @@ -22,3 +22,11 @@ Returns a wrapped component context object based on the passed one. This function allows developers to override component properties and methods without altering the original object. Essentially, override creates a new object that contains the original object as its prototype, allowing for the addition, modification, or removal of properties and methods without affecting the original object. + +### saveRawComponentContext + +Stores a reference to the "raw" component context in the main context. + +### dropRawComponentContext + +Drops a reference to the "raw" component context from the main context. diff --git a/src/core/component/context/index.ts b/src/core/component/context/index.ts index 68d033b698..48ea2a44a1 100644 --- a/src/core/component/context/index.ts +++ b/src/core/component/context/index.ts @@ -25,16 +25,40 @@ export * from 'core/component/context/const'; * @param component */ export function getComponentContext(component: object): Dictionary & ComponentInterface['unsafe'] { - component = toRaw in component ? component[toRaw] : component; + if (toRaw in component) { + return Object.cast(component); + } let - v = wrappedContexts.get(component); + wrappedCtx = wrappedContexts.get(component); + + if (wrappedCtx == null) { + wrappedCtx = Object.create(component); + saveRawComponentContext(wrappedCtx, component); + wrappedContexts.set(component, wrappedCtx); + } + + return wrappedCtx; +} + +/** + * Stores a reference to the "raw" component context in the main context + * + * @param ctx - the main context object + * @param rawCtx - the raw context object to be stored + */ +export function saveRawComponentContext(ctx: object, rawCtx: object): void { + Object.defineProperty(ctx, toRaw, {configurable: true, value: rawCtx}); +} - if (v == null) { - v = Object.create(component); - Object.defineProperty(v, toRaw, {value: component}); - wrappedContexts.set(component, v); +/** + * Drops a reference to the "raw" component context from the main context + * @param ctx - the main context object + */ +export function dropRawComponentContext(ctx: object): void { + if (toRaw in ctx) { + wrappedContexts.delete(ctx[toRaw]); } - return v; + delete ctx[toRaw]; } diff --git a/src/core/component/engines/vue3/render.ts b/src/core/component/engines/vue3/render.ts index 9305b31423..486e0884e9 100644 --- a/src/core/component/engines/vue3/render.ts +++ b/src/core/component/engines/vue3/render.ts @@ -25,7 +25,9 @@ import { withDirectives as superWithDirectives, resolveDirective as superResolveDirective, - VNode + VNode, + VNodeChild, + VNodeArrayChildren } from 'vue'; @@ -209,3 +211,65 @@ export function render(vnode: CanArray, parent?: ComponentInterface, grou return node?.nodeType === 3 && node.textContent === ''; } } + +/** + * Deletes the specified node and frees up memory + * @param node + */ +export function destroy(node: VNode | Node): void { + if (node instanceof Node) { + if (('__vnode' in node)) { + removeVNode(node['__vnode']); + } + + node.parentNode?.removeChild(node); + + if (node instanceof Element) { + node.innerHTML = ''; + } + + } else { + removeVNode(node); + } + + function removeVNode(vnode: Nullable) { + if (vnode == null || Object.isPrimitive(vnode)) { + return; + } + + if (Object.isArray(vnode)) { + vnode.forEach(removeVNode); + return; + } + + if (Object.isArray(vnode.children)) { + vnode.children.forEach(removeVNode); + } + + if (Object.isArray(vnode['dynamicChildren'])) { + vnode['dynamicChildren'].forEach((vnode) => removeVNode(Object.cast(vnode))); + } + + if (Object.isArray(vnode.dirs)) { + vnode.dirs.forEach((binding) => { + binding.dir.beforeUnmount?.(vnode.el, binding, vnode, null); + binding.dir.unmounted?.(vnode.el, binding, vnode, null); + }); + } + + if (vnode.component != null) { + vnode.component.effect.stop(); + vnode.component = null; + } + + vnode.props = {}; + + ['dirs', 'children', 'dynamicChildren', 'dynamicProps'].forEach((key) => { + vnode[key] = []; + }); + + ['el', 'ctx', 'ref', 'virtualComponent', 'virtualContext'].forEach((key) => { + vnode[key] = null; + }); + } +} diff --git a/src/core/component/functional/context.ts b/src/core/component/functional/context.ts index 1117c438a4..2958f7d4c1 100644 --- a/src/core/component/functional/context.ts +++ b/src/core/component/functional/context.ts @@ -8,6 +8,7 @@ import * as init from 'core/component/init'; +import { saveRawComponentContext } from 'core/component/context'; import { forkMeta, ComponentMeta } from 'core/component/meta'; import { initProps } from 'core/component/prop'; @@ -132,6 +133,8 @@ export function createVirtualContext( } }); + saveRawComponentContext(virtualCtx, virtualCtx); + initProps(virtualCtx, { from: $props, store: virtualCtx, diff --git a/src/core/component/init/states/activated.ts b/src/core/component/init/states/activated.ts index d834216124..993bd91a3c 100644 --- a/src/core/component/init/states/activated.ts +++ b/src/core/component/init/states/activated.ts @@ -16,6 +16,10 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function activatedState(component: ComponentInterface): void { + if (component.hook === 'activated') { + return; + } + runHook('activated', component).catch(stderr); callMethodFromComponent(component, 'activated'); } diff --git a/src/core/component/init/states/before-destroy.ts b/src/core/component/init/states/before-destroy.ts index 97a57f37cf..d44ba36908 100644 --- a/src/core/component/init/states/before-destroy.ts +++ b/src/core/component/init/states/before-destroy.ts @@ -6,6 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import { dropRawComponentContext } from 'core/component/context'; import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; @@ -16,14 +17,35 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function beforeDestroyState(component: ComponentInterface): void { + if (component.hook === 'beforeDestroy' || component.hook === 'destroyed') { + return; + } + runHook('beforeDestroy', component).catch(stderr); callMethodFromComponent(component, 'beforeDestroy'); - const - {unsafe} = component; + const { + unsafe, + unsafe: {$el} + } = component; unsafe.async.clearAll().locked = true; unsafe.$async.clearAll().locked = true; - delete unsafe.$el?.component; + if ($el != null) { + delete $el.component; + } + + setTimeout(() => { + if ($el != null) { + unsafe.$renderEngine.r.destroy($el); + } + + Object.getOwnPropertyNames(unsafe).forEach((key) => { + delete unsafe[key]; + }) + + Object.setPrototypeOf(unsafe, null); + dropRawComponentContext(unsafe); + }, 0); } diff --git a/src/core/component/init/states/created.ts b/src/core/component/init/states/created.ts index e419a96bfc..c7f9185738 100644 --- a/src/core/component/init/states/created.ts +++ b/src/core/component/init/states/created.ts @@ -21,6 +21,10 @@ const * @param component */ export function createdState(component: ComponentInterface): void { + if (component.hook !== 'beforeDataCreate') { + return; + } + const { unsafe, unsafe: { diff --git a/src/core/component/init/states/deactivated.ts b/src/core/component/init/states/deactivated.ts index e86e26a462..39957d33f8 100644 --- a/src/core/component/init/states/deactivated.ts +++ b/src/core/component/init/states/deactivated.ts @@ -16,6 +16,10 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function deactivatedState(component: ComponentInterface): void { + if (component.hook === 'deactivated') { + return; + } + runHook('deactivated', component).catch(stderr); callMethodFromComponent(component, 'deactivated'); } diff --git a/src/core/component/init/states/destroyed.ts b/src/core/component/init/states/destroyed.ts index ec0ebc9840..313e52461b 100644 --- a/src/core/component/init/states/destroyed.ts +++ b/src/core/component/init/states/destroyed.ts @@ -16,6 +16,10 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function destroyedState(component: ComponentInterface): void { + if (component.hook === 'destroyed') { + return; + } + runHook('destroyed', component).then(() => { callMethodFromComponent(component, 'destroyed'); }).catch(stderr); diff --git a/src/core/component/interface/engine.ts b/src/core/component/interface/engine.ts index cdbd0c2b05..eb55c63f48 100644 --- a/src/core/component/interface/engine.ts +++ b/src/core/component/interface/engine.ts @@ -101,6 +101,7 @@ export type ProxyGetterType = export interface RenderAPI { render(vnode: VNode, parent?: ComponentInterface, group?: string): Node; render(vnode: VNode[], parent?: ComponentInterface, group?: string): Node[]; + destroy(vnode: Node | VNode): void; getCurrentInstance: typeof getCurrentInstance; diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index afbcc8b2ff..dae411f87c 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -78,7 +78,8 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { }; const - cache = new WeakMap(); + label = Symbol('Render cache'), + cache = new Map(); meta.component[SSR ? 'ssrRender' : 'render'] = Object.cast((ctx: object, ...args: unknown[]) => { const @@ -95,7 +96,14 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { return render; } - cache.set(ctx, render); + if (unsafe.meta.params.functional !== true) { + cache.set(ctx, render); + + unsafe.$async.worker(() => { + cache.delete(ctx); + }, {label}); + } + return render(); }); diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 692d90aa7c..0cfdf9393d 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -522,7 +522,10 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface // Every worker that passed to Async have a counter with a number of consumers of this worker, // but in this case this behaviour is redundant and can produce an error, // that why we wrap original destructor with a new function - component.unsafe.$async.worker(() => destructor()); + component.unsafe.$async.worker(() => { + watchCache.clear(); + return destructor(); + }); } return destructor; From f03c6a1c5cb835d7c1227fc1032e697ccfd5f042 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 11:26:54 +0300 Subject: [PATCH 07/21] feat: now `group` can be provided as a function --- src/components/friends/async-render/README.md | 1 + src/components/friends/async-render/interface/task.ts | 3 ++- src/components/friends/async-render/iterate.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/friends/async-render/README.md b/src/components/friends/async-render/README.md index b32bcc334b..df35d37f8c 100644 --- a/src/components/friends/async-render/README.md +++ b/src/components/friends/async-render/README.md @@ -190,6 +190,7 @@ This can optimize the browser rendering process. A group name for manually clearing pending tasks via the `async` module. Providing this value disables the automatic cleanup of render tasks on the `update` hook. +If this parameter is set as a function, the group name will be dynamically calculated on each new iteration. ``` < .container v-async-target diff --git a/src/components/friends/async-render/interface/task.ts b/src/components/friends/async-render/interface/task.ts index a416bd97c2..adc290dbab 100644 --- a/src/components/friends/async-render/interface/task.ts +++ b/src/components/friends/async-render/interface/task.ts @@ -32,6 +32,7 @@ export interface TaskOptions { /** * A group name to manual clearing of pending tasks via the [[Async]] module. * Providing this value disables automatically cleanup of render tasks on the `update` hook. + * If this parameter is set as a function, the group name will be dynamically calculated on each new iteration. * * @example * ``` @@ -45,7 +46,7 @@ export interface TaskOptions { * Cancel rendering * ``` */ - group?: string; + group?: string | (() => string); /** * A function to filter elements to render. diff --git a/src/components/friends/async-render/iterate.ts b/src/components/friends/async-render/iterate.ts index c9f155f20c..e680779068 100644 --- a/src/components/friends/async-render/iterate.ts +++ b/src/components/friends/async-render/iterate.ts @@ -132,7 +132,7 @@ export function iterate( // eslint-disable-next-line no-constant-condition rendering: while (true) { if (opts.group != null) { - group = `asyncComponents:${opts.group}:${chunkI}`; + group = `asyncComponents:${Object.isFunction(opts.group) ? opts.group() : opts.group}:${chunkI}`; } let From 54f06c65daee161003c8e6843cf7239b0e004ef4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 13:23:55 +0300 Subject: [PATCH 08/21] fix: fixed memory leaks --- .../base/b-dynamic-page/b-dynamic-page.ss | 3 +- .../base/b-dynamic-page/b-dynamic-page.ts | 115 +++++++++++------- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ss b/src/components/base/b-dynamic-page/b-dynamic-page.ss index 28a1a530e8..f645901bc4 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ss +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ss @@ -23,7 +23,7 @@ ? delete attrs[':keepAlive'] ? delete attrs[':dispatching'] - < template v-for = el in asyncRender.iterate(renderIterator, {filter: renderFilter, group: registerRenderingGroup()}) + < template v-for = el in asyncRender.iterate(renderIterator, {filter: renderFilter, group: registerRenderGroup}) < component.&__component & v-if = !pageTakenFromCache | ref = component | @@ -32,5 +32,6 @@ :dispatching = true | :renderComponentId = true | + v-attrs = {'@hook:destroyed': createPageDestructor()} | ${attrs} . diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index 01145c1413..f2ae9e5ffd 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -95,25 +95,23 @@ export default class bDynamicPage extends iDynamicPage { readonly pageGetter!: PageGetter; /** - * If true, then when moving from one page to another, the old page is saved in the cache under its own name. - * When you return to this page, it will be restored. This helps to optimize switching between pages, but increases - * memory consumption. + * If set to true, the previous pages will be cached under their own names, allowing them to be restored when revisited. + * This optimization helps improve page switching but may increase memory usage. * - * Note that when a page is switched, it will be deactivated by calling `deactivate`. - * When the page is restored, it will be activated by calling `activate`. + * Please note that when a page is switched, it will be deactivated through the `deactivate` function. + * Similarly, when the page is restored, it will be activated using the `activate` function. */ @prop(Boolean) readonly keepAlive: boolean = false; /** - * The maximum number of pages in the `keepAlive` global cache + * The maximum number of pages that can be stored in the global cache of `keepAlive` */ @prop(Number) readonly keepAliveSize: number = 10; /** - * A dictionary of `keepAlive` caches. - * The keys represent cache groups (the default is `global`). + * A dictionary of `keepAlive` caches, where the keys represent cache groups (with the default being `global`) */ @system((o) => o.sync.link('keepAliveSize', (size: number) => ({ ...o.keepAliveCache, @@ -127,15 +125,17 @@ export default class bDynamicPage extends iDynamicPage { keepAliveCache!: Dictionary>; /** - * A predicate to include pages in `keepAlive` caching: if not specified, all loaded pages will be cached. - * It can be defined as: + * A predicate to determine which pages should be included in `keepAlive` caching. + * If not specified, all loaded pages will be cached. * - * 1. a component name (or a list of names); - * 2. a regular expression; - * 3. a function that takes a component name and returns: - * * `true` (include), `false` (does not include); - * * a string key for caching (used instead of the component name); - * * or a special object with information about the caching strategy being used. + * The predicate can be defined in three ways: + * 1. As a component name or a list of component names. + * 2. As a regular expression. + * 3. As a function that takes a component name and returns one of the following: + * - `true` (to include the page in caching). + * - `false` (to exclude the page from caching). + * - A string key to be used for caching instead of the component name. + * - A special object with information about the caching strategy being used. */ @prop({ type: [String, Array, RegExp, Function], @@ -145,9 +145,11 @@ export default class bDynamicPage extends iDynamicPage { readonly include?: Include; /** - * A predicate to exclude some pages from `keepAlive` caching. - * It can be defined as a component name (or a list of names), regular expression, - * or a function that takes a component name and returns `true` (exclude) or `false` (does not exclude). + * A predicate to exclude certain pages from `keepAlive` caching can be defined in three ways: + * 1. As a component name or a list of component names. + * 2. As a regular expression. + * 3. As a function that takes a component name and returns `true` to exclude the page from caching, + * or `false` to include the page in caching. */ @prop({ type: [String, Array, RegExp, Function], @@ -163,7 +165,7 @@ export default class bDynamicPage extends iDynamicPage { readonly emitter?: EventEmitterLike; /** - * Page switching event name + * The page switching event name */ @prop({ type: String, @@ -179,22 +181,25 @@ export default class bDynamicPage extends iDynamicPage { @computed({cache: false, dependencies: ['page']}) get component(): CanPromise { const - c = this.$refs.component; + that = this, + componentRef = this.$refs.component; - const getComponent = () => { + if (componentRef != null && (!Object.isArray(componentRef) || componentRef.length > 0)) { + return getComponent(); + } + + return this.waitRef('component').then(getComponent); + + function getComponent() { const - c = this.$refs.component!; + componentRef = that.$refs.component!; - if (Object.isArray(c)) { - return c[0]; + if (Object.isArray(componentRef)) { + return componentRef[0]; } - return c; - }; - - return c != null && (!Object.isArray(c) || c.length > 0) ? - getComponent() : - this.waitRef('component').then(getComponent); + return componentRef; + } } override get unsafe(): UnsafeGetter> { @@ -230,10 +235,17 @@ export default class bDynamicPage extends iDynamicPage { * Registered groups of asynchronous render tasks */ @system() - protected renderingGroups: Set = new Set(); + protected renderGroups: Set = new Set(); + + /** + * The name of the current rendering group + */ + protected get currentRenderGroup() { + return `pageRendering-${this.renderCounter}`; + } /** - * Render loop iterator (used with `asyncRender`) + * The render loop iterator for `asyncRender` */ protected get renderIterator(): CanPromise { if (SSR) { @@ -289,14 +301,28 @@ export default class bDynamicPage extends iDynamicPage { /** * Registers a new group for asynchronous rendering and returns it */ - protected registerRenderingGroup(): string { - const group = `pageRendering-${this.renderCounter++}`; - this.renderingGroups.add(group); - return group; + protected registerRenderGroup(): string { + this.renderCounter++; + this.renderGroups.add(this.currentRenderGroup); + return this.currentRenderGroup; } /** - * Render loop filter (used with `asyncRender`) + * Creates a page destructor function + */ + protected createPageDestructor() { + const + group = this.currentRenderGroup, + groupRgxp = new RegExp(RegExp.escape(group)); + + return () => { + this.async.clearAll({group: groupRgxp}); + this.renderGroups.delete(group); + }; + } + + /** + * The render loop filter for `asyncRender` */ protected renderFilter(): CanPromise { const canPass = @@ -310,20 +336,17 @@ export default class bDynamicPage extends iDynamicPage { return true; } - const - {unsafe, route} = this; + const { + unsafe, + route + } = this; return new SyncPromise((resolve) => { - [...this.renderingGroups].slice(0, -2).forEach((group) => { - this.async.clearAll({group: new RegExp(RegExp.escape(group))}); - this.renderingGroups.delete(group); - }); - this.onPageChange = onPageChange(resolve, this.route); }); function onPageChange( - resolve: Function, + resolve: (status: boolean) => void, currentRoute: typeof route ): AnyFunction { return (newPage: CanUndef, currentPage: CanUndef) => { From 88ea685905f2332a81000818f90f4e51e7de9822 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 13:25:31 +0300 Subject: [PATCH 09/21] fix: added memoization for handlers --- build/snakeskin/default-filters.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/snakeskin/default-filters.js b/build/snakeskin/default-filters.js index 5a7bd1e1c6..5ab9da3495 100644 --- a/build/snakeskin/default-filters.js +++ b/build/snakeskin/default-filters.js @@ -88,8 +88,8 @@ function tagFilter({name, attrs = {}}, tplName, cursor) { attrs[':componentIdProp'] = [`componentId + ${JSON.stringify(id)}`]; } - attrs[':getRoot'] = ["() => ('getRoot' in self ? self.getRoot?.() : null) ?? self.$root"]; - attrs[':getParent'] = ["() => typeof $restArgs !== 'undefined' ? $restArgs?.ctx ?? self : self"]; + attrs[':getRoot'] = ["self.__getRoot__ ??= () => ('getRoot' in self ? self.getRoot?.() : null) ?? self.$root"]; + attrs[':getParent'] = ["self.__getParent__ ??= () => typeof $restArgs !== 'undefined' ? $restArgs?.ctx ?? self : self"]; if (component.inheritMods !== false && !attrs[':modsProp']) { attrs[':modsProp'] = ['sharedMods']; From 3ac944717bef70b622e794b6e41d061fd126fd44 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 13:31:01 +0300 Subject: [PATCH 10/21] fix: clear only functional vnodes --- src/core/component/init/states/before-destroy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/component/init/states/before-destroy.ts b/src/core/component/init/states/before-destroy.ts index d44ba36908..3cefa9ac31 100644 --- a/src/core/component/init/states/before-destroy.ts +++ b/src/core/component/init/states/before-destroy.ts @@ -37,13 +37,13 @@ export function beforeDestroyState(component: ComponentInterface): void { } setTimeout(() => { - if ($el != null) { + if ($el != null && unsafe.meta.params.functional === true) { unsafe.$renderEngine.r.destroy($el); } Object.getOwnPropertyNames(unsafe).forEach((key) => { delete unsafe[key]; - }) + }); Object.setPrototypeOf(unsafe, null); dropRawComponentContext(unsafe); From f8e4c951af60d1bc8b863a5241be44f97671ae13 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 13:43:46 +0300 Subject: [PATCH 11/21] chore: fixed TS --- src/components/friends/async-render/helpers/render.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/friends/async-render/helpers/render.ts b/src/components/friends/async-render/helpers/render.ts index 537493ca0d..64a1464909 100644 --- a/src/components/friends/async-render/helpers/render.ts +++ b/src/components/friends/async-render/helpers/render.ts @@ -30,7 +30,7 @@ export function addRenderTask( ): Promise { const $a = this.async, - group = opts.group ?? 'asyncComponents'; + group = (Object.isFunction(opts.group) ? opts.group() : opts.group) ?? 'asyncComponents'; return new SyncPromise((resolve, reject) => { const taskDesc = { From 759281b9f29032de1cc46ecf6f39fc8dc03e5f97 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 14:13:22 +0300 Subject: [PATCH 12/21] chore: removed code for debugging --- .../p-v4-components-demo.ss | 23 - .../p-v4-components-demo.ts | 1026 ----------------- .../p-v4-dynamic-page1/p-v4-dynamic-page1.ss | 9 - .../p-v4-dynamic-page2/p-v4-dynamic-page2.ss | 9 - .../p-v4-dynamic-page3/p-v4-dynamic-page3.ss | 9 - src/routes/index.ts | 15 +- 6 files changed, 2 insertions(+), 1089 deletions(-) diff --git a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss index 4fbe0a5395..2988d4c196 100644 --- a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss +++ b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ss @@ -11,26 +11,3 @@ - include 'components/super/i-static-page/i-static-page.component.ss'|b as placeholder - template index() extends ['i-static-page.component'].index - - block helpers - - block router - < b-router & - v-once | - :initialRoute = "page1" - . - - - block body - < h1 - Active page: {{ activePage }} - - < hr - - < b-button :type = 'link' | :href = '/page1' - Page1 - < b-button :type = 'link' | :href = '/page2' - Page2 - < b-button :type = 'link' | :href = '/page3' - Page3 - - < hr - - < b-dynamic-page 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 cc5b0039c3..33b4bd13d4 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,7 +13,6 @@ import iStaticPage, { component, prop, field, system } from 'components/super/i-static-page/i-static-page'; import VDOM, * as VDOMAPI from 'components/friends/vdom'; -import type { Item } from 'components/traits/i-active-items/interface'; export * from 'components/super/i-static-page/i-static-page'; @@ -22,8 +21,6 @@ VDOM.addToPrototype(VDOMAPI); // eslint-disable-next-line no-console console.time('Initializing'); -let items: Item[] = createItems(); - /** * Page with component demos. * Basically it uses with component tests. @@ -49,1033 +46,10 @@ export default class pV4ComponentsDemo extends iStaticPage { @field() someField: unknown = 'foo'; - @system() - items: Item[] = items; - protected beforeCreate(): void { //#unless runtime has storybook // eslint-disable-next-line no-console console.time('Render'); //#endunless } - - protected updateSomeField(): void { - this.someField = Math.random(); - } -} - - -function createItems() { - return [ - { - "label": "Props", - "folded": false, - "children": [ - { - "label": "selfDispatchingProp", - "data": false - }, - { - "label": "iPage", - "folded": true, - "children": [ - { - "label": "hideIfOffline", - "data": false - }, - { - "label": "pageTitleProp", - "data": "" - }, - { - "label": "pageDescriptionProp", - "data": "" - }, - { - "label": "stagePageTitles" - } - ] - }, - { - "label": "iData", - "folded": true, - "children": [ - { - "label": "dataProviderProp" - }, - { - "label": "dataProviderOptions" - }, - { - "label": "request" - }, - { - "label": "dbConverter" - }, - { - "label": "componentConverter" - }, - { - "label": "defaultRequestFilter" - }, - { - "label": "suspendedRequestsProp", - "data": false - }, - { - "label": "offlineReload", - "data": false - }, - { - "label": "checkDBEquality", - "data": true - } - ] - }, - { - "label": "iBlock", - "folded": true, - "children": [ - { - "label": "componentIdProp" - }, - { - "label": "globalName" - }, - { - "label": "rootTag", - "data": "div" - }, - { - "label": "verbose", - "data": false - }, - { - "label": "stageProp" - }, - { - "label": "modsProp" - }, - { - "label": "activatedProp", - "data": true - }, - { - "label": "forceActivation", - "data": false - }, - { - "label": "reloadOnActivation", - "data": true - }, - { - "label": "renderOnActivation", - "data": false - }, - { - "label": "dependenciesProp" - }, - { - "label": "ssrRendering", - "data": true - }, - { - "label": "wait" - }, - { - "label": "remoteProvider", - "data": false - }, - { - "label": "dontWaitRemoteProvidersProp" - }, - { - "label": "syncRouterStoreOnInit", - "data": true - }, - { - "label": "routerStateUpdateMethod", - "data": "push" - }, - { - "label": "watchProp" - }, - { - "label": "proxyCall", - "data": false - }, - { - "label": "dispatching", - "data": false - }, - { - "label": "p" - }, - { - "label": "classes" - }, - { - "label": "styles" - }, - { - "label": "renderComponentId", - "data": true - }, - { - "label": "getRoot" - }, - { - "label": "getParent" - } - ] - } - ] - }, - { - "label": "Fields", - "folded": false, - "children": [ - { - "label": "someField", - "data": "foo" - }, - { - "label": "iStaticPage", - "folded": true, - "children": [ - { - "label": "isAuth", - "data": false - }, - { - "label": "isOnline", - "data": true - }, - { - "label": "shouldMountTeleports", - "data": true - }, - { - "label": "routeStore" - }, - { - "label": "localeStore", - "data": "en" - } - ] - }, - { - "label": "iData", - "folded": true, - "children": [ - { - "label": "dbStore" - } - ] - }, - { - "label": "iBlock", - "folded": true, - "children": [ - { - "label": "reactiveTmp", - "data": "{}", - "children": [] - }, - { - "label": "rootAttrsStore", - "data": "{}", - "children": [] - }, - { - "label": "reactiveModsStore", - "data": "{}", - "children": [] - }, - { - "label": "componentStatusStore", - "data": "beforeReady" - }, - { - "label": "stageStore" - } - ] - } - ] - }, - { - "label": "ComputedFields", - "folded": false, - "children": [ - { - "label": "iStaticPage", - "folded": true, - "children": [ - { - "label": "activePage" - }, - { - "label": "locale", - "data": "en" - } - ] - }, - { - "label": "iPage", - "folded": true, - "children": [ - { - "label": "scrollToProxy", - "data": { - "declaration": "(...args) => {\n this.async.setImmediate(() => this.scrollTo(...args), {\n label: $$.scrollTo\n });\n }" - } - } - ] - }, - { - "label": "iData", - "folded": true, - "children": [ - { - "label": "db" - } - ] - }, - { - "label": "iBlock", - "folded": true, - "children": [ - { - "label": "rootAttrs", - "data": "{}", - "children": [] - }, - { - "label": "sharedMods", - "data": null - }, - { - "label": "m", - "data": "{\"hidden\":\"false\"}", - "children": [ - { - "label": "hidden", - "data": "false" - } - ] - } - ] - } - ] - }, - { - "label": "SystemFields", - "folded": false, - "children": [ - { - "label": "console" - }, - { - "label": "rootParam" - }, - { - "label": "iStaticPage", - "folded": true, - "children": [ - { - "label": "pageMetaData", - "data": "PageMetaData" - }, - { - "label": "providerDataStore", - "data": "RestrictedCache" - }, - { - "label": "theme", - "data": null - }, - { - "label": "lastOnlineDate" - }, - { - "label": "initialRoute" - }, - { - "label": "globalEnv", - "data": "{}", - "children": [] - }, - { - "label": "routerStore" - }, - { - "label": "rootMods", - "data": "{\"p-v4-components-demo_active\"...}", - "children": [ - { - "label": "p-v4-components-demo_active", - "data": "{\"name\":\"active\",\"value\":\"true...}", - "children": [ - { - "label": "name", - "data": "active" - }, - { - "label": "value", - "data": "true" - }, - { - "label": "class", - "data": "p-v4-components-demo-active-true" - }, - { - "label": "component", - "data": "Object", - "children": [ - { - "label": "$root", - "data": "*restricted*" - }, - { - "label": "$children", - "data": "*restricted*" - }, - { - "label": "rootAttrs", - "data": "{}", - "children": [] - }, - { - "label": "m", - "data": "{\"hidden\":\"false\"}", - "children": [ - { - "label": "hidden", - "data": "false" - } - ] - }, - { - "label": "scrollToProxy", - "data": { - "declaration": "(...args) => {\n this.async.setImmediate(() => this.scrollTo(...args), {\n label: $$.scrollTo\n });\n }" - } - }, - { - "label": "locale", - "data": "en" - }, - { - "label": "isFunctional", - "data": false - }, - { - "label": "self", - "data": "*restricted*" - }, - { - "label": "r", - "data": "*restricted*" - }, - { - "label": "i18n", - "data": { - "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" - } - }, - { - "label": "t", - "data": { - "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" - } - }, - { - "label": "randomGenerator", - "data": "Xor128" - }, - { - "label": "remoteState", - "data": "{\"isAuth\":false,\"isOnline\":tru...}", - "children": [ - { - "label": "isAuth", - "data": false - }, - { - "label": "isOnline", - "data": true - }, - { - "label": "globalEnv", - "data": "{}", - "children": [] - } - ] - }, - { - "label": "componentStatus", - "data": "ready" - }, - { - "label": "isReady", - "data": true - }, - { - "label": "stageGroup", - "data": "stage.undefined" - }, - { - "label": "isRelatedToSSR", - "data": false - }, - { - "label": "hook", - "data": "mounted" - }, - { - "label": "router", - "data": "*restricted*" - }, - { - "label": "pageTitle", - "data": "Default app" - }, - { - "label": "$fields", - "data": "*restricted*" - }, - { - "label": "$watch", - "data": "*restricted*" - }, - { - "label": "$set", - "data": "*restricted*" - }, - { - "label": "$delete", - "data": "*restricted*" - }, - { - "label": "$systemFields", - "data": "*restricted*" - }, - { - "label": "pageTitleStore", - "data": "" - } - ] - } - ] - }, - { - "label": "p-v4-components-demo_online", - "data": "{\"name\":\"online\",\"value\":\"true...}", - "children": [ - { - "label": "name", - "data": "online" - }, - { - "label": "value", - "data": "true" - }, - { - "label": "class", - "data": "p-v4-components-demo-online-true" - }, - { - "label": "component", - "data": "Object", - "children": [ - { - "label": "$root", - "data": "*restricted*" - }, - { - "label": "$children", - "data": "*restricted*" - }, - { - "label": "rootAttrs", - "data": "{}", - "children": [] - }, - { - "label": "m", - "data": "{\"hidden\":\"false\"}", - "children": [ - { - "label": "hidden", - "data": "false" - } - ] - }, - { - "label": "scrollToProxy", - "data": { - "declaration": "(...args) => {\n this.async.setImmediate(() => this.scrollTo(...args), {\n label: $$.scrollTo\n });\n }" - } - }, - { - "label": "locale", - "data": "en" - }, - { - "label": "isFunctional", - "data": false - }, - { - "label": "self", - "data": "*restricted*" - }, - { - "label": "r", - "data": "*restricted*" - }, - { - "label": "i18n", - "data": { - "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" - } - }, - { - "label": "t", - "data": { - "declaration": "function i18n(value, params) {\n var _a, _b;\n if (Object.isArray(value) && value.length !== 1) {\n throw new SyntaxError('Using i18n with template literals is allowed only without variables');\n }\n const key = Object.isString(value) ? value : value[0], correctKeyset = keysetNames.find((keysetName) => { var _a, _b; return (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[keysetName]) === null || _b === void 0 ? void 0 : _b[key]; }), translateValue = (_b = (_a = lang__WEBPACK_IMPORTED_MODULE_2__[\"default\"][resolvedLocale]) === null || _a === void 0 ? void 0 : _a[correctKeyset !== null && correctKeyset !== void 0 ? correctKeyset : '']) === null || _b === void 0 ? void 0 : _b[key];\n if (translateValue != null && translateValue !== '') {\n return resolveTemplate(translateValue, params);\n }\n logger.error('Translation for the given key is not found', `Key: ${key}, KeysetNames: ${keysetNames.join(', ')}, LocaleName: ${resolvedLocale}`);\n return resolveTemplate(key, params);\n }" - } - }, - { - "label": "randomGenerator", - "data": "Xor128" - }, - { - "label": "remoteState", - "data": "{\"isAuth\":false,\"isOnline\":tru...}", - "children": [ - { - "label": "isAuth", - "data": false - }, - { - "label": "isOnline", - "data": true - }, - { - "label": "globalEnv", - "data": "{}", - "children": [] - } - ] - }, - { - "label": "componentStatus", - "data": "ready" - }, - { - "label": "isReady", - "data": true - }, - { - "label": "stageGroup", - "data": "stage.undefined" - }, - { - "label": "isRelatedToSSR", - "data": false - }, - { - "label": "hook", - "data": "mounted" - }, - { - "label": "router", - "data": "*restricted*" - }, - { - "label": "pageTitle", - "data": "Default app" - }, - { - "label": "$fields", - "data": "*restricted*" - }, - { - "label": "$watch", - "data": "*restricted*" - }, - { - "label": "$set", - "data": "*restricted*" - }, - { - "label": "$delete", - "data": "*restricted*" - }, - { - "label": "$systemFields", - "data": "*restricted*" - }, - { - "label": "pageTitleStore", - "data": "" - } - ] - } - ] - } - ] - } - ] - }, - { - "label": "iPage", - "folded": true, - "children": [ - { - "label": "pageTitleStore", - "data": "" - }, - { - "label": "pageDescriptionStore", - "data": "" - } - ] - }, - { - "label": "iData", - "folded": true, - "children": [ - { - "label": "dataProvider" - }, - { - "label": "dbConverters", - "data": "[]", - "children": [] - }, - { - "label": "componentConverters", - "data": "[]", - "children": [] - }, - { - "label": "suspendedRequests", - "data": false - }, - { - "label": "requestParams", - "data": "{\"get\":{}}", - "children": [ - { - "label": "get", - "data": "{}", - "children": [] - } - ] - } - ] - }, - { - "label": "iBlock", - "folded": true, - "children": [ - { - "label": "provide", - "data": "Provide" - }, - { - "label": "infoRender", - "data": "InfoRender" - }, - { - "label": "field", - "data": "Field" - }, - { - "label": "analytics", - "data": "Analytics" - }, - { - "label": "sync", - "data": "Sync" - }, - { - "label": "asyncRender", - "data": "AsyncRender" - }, - { - "label": "vdom", - "data": "VDOM" - }, - { - "label": "lfc", - "data": "Lfc" - }, - { - "label": "daemons", - "data": "Daemons" - }, - { - "label": "block", - "data": "Block" - }, - { - "label": "dom", - "data": "DOM" - }, - { - "label": "storage", - "data": "Storage" - }, - { - "label": "state", - "data": "State" - }, - { - "label": "moduleLoader", - "data": "ModuleLoader" - }, - { - "label": "ifOnceStore", - "data": "{}", - "children": [] - }, - { - "label": "opt", - "data": "Opt" - }, - { - "label": "browser", - "data": "{\"is\":{\"Chrome\":[\"Chrome\",[116...}", - "children": [ - { - "label": "is", - "data": "{\"Chrome\":[\"Chrome\",[116,0,584...}", - "children": [ - { - "label": "Chrome", - "data": "Array(2)", - "children": [ - { - "label": "0", - "data": "Chrome" - }, - { - "label": "1", - "data": "Array(4)", - "children": [ - { - "label": "0", - "data": 116 - }, - { - "label": "1", - "data": 0 - }, - { - "label": "2", - "data": 5845 - }, - { - "label": "3", - "data": 2319 - } - ] - } - ] - }, - { - "label": "Firefox", - "data": false - }, - { - "label": "Android", - "data": false - }, - { - "label": "BlackBerry", - "data": false - }, - { - "label": "iOS", - "data": false - }, - { - "label": "OperaMini", - "data": false - }, - { - "label": "WindowsMobile", - "data": false - }, - { - "label": "Safari", - "data": false - }, - { - "label": "mobile", - "data": false - } - ] - }, - { - "label": "match", - "data": { - "declaration": "function match(pattern) {\n var _a, _b;\n if (typeof navigator === 'undefined') {\n return false;\n }\n const { userAgent } = navigator;\n let name, version;\n if (Object.isFunction(pattern)) {\n [name, version] = (_a = pattern(userAgent)) !== null && _a !== void 0 ? _a : [];\n }\n else {\n const rgxp = Object.isString(pattern) ? new RegExp(`(${pattern})(?:[ \\\\/-]([0-9._]*))?`, 'i') : pattern;\n [, name, version] = (_b = rgxp.exec(userAgent)) !== null && _b !== void 0 ? _b : [];\n }\n const versionParts = version != null && version.length !== 0 ?\n version.split(/[._]/).map(map) :\n null;\n if (name != null) {\n return [name, versionParts];\n }\n return false;\n function map(el) {\n const v = parseInt(el, 10);\n return Object.isTruly(v) ? v : 0;\n }\n}" - } - }, - { - "label": "test", - "data": { - "declaration": "function test(platform, operation, version) {\n const val = core_browser_const__WEBPACK_IMPORTED_MODULE_1__.is[platform];\n if (val === false) {\n return false;\n }\n if (operation == null || version == null) {\n return true;\n }\n if (val[1] == null) {\n return false;\n }\n return (0,core_semver__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(val[1].join('.'), version, operation);\n}" - } - } - ] - }, - { - "label": "presets", - "data": "{\"loopback\":{}}", - "children": [ - { - "label": "loopback", - "data": "{}", - "children": [] - } - ] - }, - { - "label": "h", - "data": "{\"NBSP\":\" \"}", - "children": [ - { - "label": "NBSP", - "data": " " - } - ] - }, - { - "label": "location", - "data": "Location" - }, - { - "label": "global", - "data": "Window" - }, - { - "label": "componentId", - "data": "u11b99ae2e0e3b2" - }, - { - "label": "isActivated", - "data": true - }, - { - "label": "isVirtualTpl", - "data": false - }, - { - "label": "beforeReadyListeners", - "data": 0 - }, - { - "label": "blockReadyListeners", - "data": "[]", - "children": [] - }, - { - "label": "tmp", - "data": "{}", - "children": [] - }, - { - "label": "watchCache", - "data": "{}", - "children": [] - }, - { - "label": "componentI18nKeysets", - "data": "Array(5)", - "children": [ - { - "label": "0", - "data": "p-v4-components-demo" - }, - { - "label": "1", - "data": "i-static-page" - }, - { - "label": "2", - "data": "i-page" - }, - { - "label": "3", - "data": "i-data" - }, - { - "label": "4", - "data": "i-block" - } - ] - }, - { - "label": "selfEmitter", - "data": "{}", - "children": [] - }, - { - "label": "localEmitter", - "data": "EventEmitter" - }, - { - "label": "parentEmitter", - "data": "{}", - "children": [] - }, - { - "label": "rootEmitter", - "data": "{}", - "children": [] - }, - { - "label": "globalEmitter", - "data": "EventEmitter" - }, - { - "label": "mods", - "data": "{\"hidden\":\"false\"}", - "children": [ - { - "label": "hidden", - "data": "false" - } - ] - }, - { - "label": "dependencies", - "data": "[]", - "children": [] - }, - { - "label": "isReadyOnce", - "data": true - }, - { - "label": "shadowComponentStatusStore", - "data": "ready" - }, - { - "label": "dontWaitRemoteProviders", - "data": false - }, - { - "label": "selfDispatching", - "data": false - } - ] - } - ] - } - ] } diff --git a/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss b/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss index 5d93148642..bf471f2d17 100644 --- a/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss +++ b/src/components/pages/p-v4-dynamic-page1/p-v4-dynamic-page1.ss @@ -17,12 +17,3 @@ < b-button.&__button-func Functional button - - < b-tree & - :items = r.items | - :theme = 'demo' | - :folded = false | - :cancelable = true - . - < template #default = {item} - {{item.label}}: {{item.data}} diff --git a/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss b/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss index fdd26a18f6..20a01b59f5 100644 --- a/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss +++ b/src/components/pages/p-v4-dynamic-page2/p-v4-dynamic-page2.ss @@ -17,12 +17,3 @@ < b-button.&__button-func Functional button - - < b-tree & - :items = r.items | - :theme = 'demo' | - :folded = false | - :cancelable = true - . - < template #default = {item} - {{item.label}}: {{item.data}} diff --git a/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss b/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss index c609e330f5..64a129d467 100644 --- a/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss +++ b/src/components/pages/p-v4-dynamic-page3/p-v4-dynamic-page3.ss @@ -17,12 +17,3 @@ < b-button.&__button-func Functional button - - < b-tree & - :items = r.items | - :theme = 'demo' | - :folded = false | - :cancelable = true - . - < template #default = {item} - {{item.label}}: {{item.data}} diff --git a/src/routes/index.ts b/src/routes/index.ts index 86a643c4cf..df78e824b2 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -5,20 +5,9 @@ * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ + import type { StaticRoutes } from 'components/base/b-router/b-router'; export default { - page1: { - component: 'p-v4-dynamic-page1', - path: 'page1', - default: true - }, - page2: { - component: 'p-v4-dynamic-page2', - path: 'page2', - }, - page3: { - component: 'p-v4-dynamic-page3', - path: 'page3', - } + }; From 4b2450dcb980850918f286b83516f9af57997a0f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 14:22:27 +0300 Subject: [PATCH 13/21] chore: fixed ESLint warnings --- src/components/base/b-dynamic-page/b-dynamic-page.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index f2ae9e5ffd..81831c3579 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -95,7 +95,8 @@ export default class bDynamicPage extends iDynamicPage { readonly pageGetter!: PageGetter; /** - * If set to true, the previous pages will be cached under their own names, allowing them to be restored when revisited. + * If set to true, the previous pages will be cached under their own names, + * allowing them to be restored when revisited. * This optimization helps improve page switching but may increase memory usage. * * Please note that when a page is switched, it will be deactivated through the `deactivate` function. @@ -240,7 +241,7 @@ export default class bDynamicPage extends iDynamicPage { /** * The name of the current rendering group */ - protected get currentRenderGroup() { + protected get currentRenderGroup(): string { return `pageRendering-${this.renderCounter}`; } @@ -310,7 +311,7 @@ export default class bDynamicPage extends iDynamicPage { /** * Creates a page destructor function */ - protected createPageDestructor() { + protected createPageDestructor(): Function { const group = this.currentRenderGroup, groupRgxp = new RegExp(RegExp.escape(group)); From 74bbd8f59d3c2f71054626c688c068a9dd5d3f78 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 15:42:16 +0300 Subject: [PATCH 14/21] fix: prefer tmp insteadof self --- build/snakeskin/default-filters.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/snakeskin/default-filters.js b/build/snakeskin/default-filters.js index 5ab9da3495..c388dcebb4 100644 --- a/build/snakeskin/default-filters.js +++ b/build/snakeskin/default-filters.js @@ -88,8 +88,8 @@ function tagFilter({name, attrs = {}}, tplName, cursor) { attrs[':componentIdProp'] = [`componentId + ${JSON.stringify(id)}`]; } - attrs[':getRoot'] = ["self.__getRoot__ ??= () => ('getRoot' in self ? self.getRoot?.() : null) ?? self.$root"]; - attrs[':getParent'] = ["self.__getParent__ ??= () => typeof $restArgs !== 'undefined' ? $restArgs?.ctx ?? self : self"]; + attrs[':getRoot'] = ["self.tmp.__getRoot__ ??= () => ('getRoot' in self ? self.getRoot?.() : null) ?? self.$root"]; + attrs[':getParent'] = ["self.tmp.__getParent__ ??= () => typeof $restArgs !== 'undefined' ? $restArgs?.ctx ?? self : self"]; if (component.inheritMods !== false && !attrs[':modsProp']) { attrs[':modsProp'] = ['sharedMods']; From 4f2af69d054889647e1f3eea57c07bfc6d2dfbad Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 15:42:39 +0300 Subject: [PATCH 15/21] fix: fixed off without providing a callback --- src/core/component/event/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index 74aa9f7c68..8534530dba 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -112,7 +112,7 @@ export function implementEventEmitterAPI(component: object): void { isOnLike = method !== 'off'; Array.concat([], event).forEach((event) => { - if (isOnLike && cb == null) { + if (method === 'off' && cb == null) { $e.removeAllListeners(event); } else { From 6ef8f7e9c4b39845c85e1b88d51575dc23fda9df Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 15:42:52 +0300 Subject: [PATCH 16/21] fix: fixed specs --- .../b-friends-async-render-dummy.ss | 4 +--- src/components/friends/async-render/test/unit/main.ts | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/friends/async-render/test/b-friends-async-render-dummy/b-friends-async-render-dummy.ss b/src/components/friends/async-render/test/b-friends-async-render-dummy/b-friends-async-render-dummy.ss index 02fbaa76db..5548ac986c 100644 --- a/src/components/friends/async-render/test/b-friends-async-render-dummy/b-friends-async-render-dummy.ss +++ b/src/components/friends/async-render/test/b-friends-async-render-dummy/b-friends-async-render-dummy.ss @@ -55,14 +55,12 @@ < template v-if = stage === 'updating the parent component state' < .&__result v-async-target - {{ void(tmp.oldRefs = $refs.btn) }} - < template v-for = el in asyncRender.iterate(2) < b-button ref = btn | v-func = false < template #default = {ctx} Element: {{ el }}; Hook: {{ ctx.hook }}; - < button.&__update @click = $forceUpdate + < button.&__update @click = tmp.oldRefs=$refs.btn.slice(), $forceUpdate() Update state < template v-if = stage === 'clearing by the specified group name' diff --git a/src/components/friends/async-render/test/unit/main.ts b/src/components/friends/async-render/test/unit/main.ts index b1d00208db..3342d72b8d 100644 --- a/src/components/friends/async-render/test/unit/main.ts +++ b/src/components/friends/async-render/test/unit/main.ts @@ -180,8 +180,7 @@ test.describe('friends/async-render', () => { const hooks = await target.evaluate((ctx) => { const oldRefs = ctx.unsafe.tmp.oldRefs; - - return [oldRefs[0].hook, oldRefs[1].hook]; + return [oldRefs[0].hook, oldRefs[1]?.hook]; }); test.expect(hooks).toEqual([ From 35a0272186281a46576ec1f62cf75909a4862248 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 15:43:46 +0300 Subject: [PATCH 17/21] fix: preserve some meta after removal --- src/core/component/init/states/before-destroy.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/component/init/states/before-destroy.ts b/src/core/component/init/states/before-destroy.ts index 3cefa9ac31..572280b922 100644 --- a/src/core/component/init/states/before-destroy.ts +++ b/src/core/component/init/states/before-destroy.ts @@ -41,11 +41,19 @@ export function beforeDestroyState(component: ComponentInterface): void { unsafe.$renderEngine.r.destroy($el); } + const destroyedComponent = { + componentId: unsafe.componentId, + componentName: unsafe.componentName, + hook: unsafe.hook + }; + Object.getOwnPropertyNames(unsafe).forEach((key) => { delete unsafe[key]; }); - Object.setPrototypeOf(unsafe, null); + Object.assign(unsafe, destroyedComponent); + Object.setPrototypeOf(unsafe, destroyedComponent); + dropRawComponentContext(unsafe); }, 0); } From 118dd561dc72ab03b07097d8cc6671afd7610894 Mon Sep 17 00:00:00 2001 From: Andrey Kobets Date: Wed, 17 Jan 2024 17:00:53 +0300 Subject: [PATCH 18/21] Update src/core/component/watch/create.ts Co-authored-by: Artem Shinkaruk <46344555+shining-mind@users.noreply.github.com> --- src/core/component/watch/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 0cfdf9393d..ca83720b01 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -519,7 +519,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface function wrapDestructor(destructor: T): T { if (Object.isFunction(destructor)) { - // Every worker that passed to Async have a counter with a number of consumers of this worker, + // Every worker passed to Async has a counter with a number of consumers of this worker, // but in this case this behaviour is redundant and can produce an error, // that why we wrap original destructor with a new function component.unsafe.$async.worker(() => { From 8a13878186e8004378df31407669d9b4da3091e5 Mon Sep 17 00:00:00 2001 From: Andrey Kobets Date: Wed, 17 Jan 2024 17:01:01 +0300 Subject: [PATCH 19/21] Update src/core/component/watch/create.ts Co-authored-by: Artem Shinkaruk <46344555+shining-mind@users.noreply.github.com> --- src/core/component/watch/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index ca83720b01..581f9b1578 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -521,7 +521,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface if (Object.isFunction(destructor)) { // Every worker passed to Async has a counter with a number of consumers of this worker, // but in this case this behaviour is redundant and can produce an error, - // that why we wrap original destructor with a new function + // that's why we wrap original destructor with a new function component.unsafe.$async.worker(() => { watchCache.clear(); return destructor(); From 9d5e6a2f4567ddb1403dcdf1d095a6c8497ebdd3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 17:01:46 +0300 Subject: [PATCH 20/21] chore: fixes for review --- src/components/friends/async-render/README.md | 2 +- src/components/friends/async-render/interface/task.ts | 2 +- src/core/component/accessor/index.ts | 1 + src/core/component/engines/vue3/render.ts | 1 + src/core/component/functional/context.ts | 4 ++++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/friends/async-render/README.md b/src/components/friends/async-render/README.md index df35d37f8c..58d0b8b6dc 100644 --- a/src/components/friends/async-render/README.md +++ b/src/components/friends/async-render/README.md @@ -190,7 +190,7 @@ This can optimize the browser rendering process. A group name for manually clearing pending tasks via the `async` module. Providing this value disables the automatic cleanup of render tasks on the `update` hook. -If this parameter is set as a function, the group name will be dynamically calculated on each new iteration. +If this parameter is set as a function, the group name will be dynamically calculated on each iteration. ``` < .container v-async-target diff --git a/src/components/friends/async-render/interface/task.ts b/src/components/friends/async-render/interface/task.ts index 137da4a71b..8fde42a1f4 100644 --- a/src/components/friends/async-render/interface/task.ts +++ b/src/components/friends/async-render/interface/task.ts @@ -32,7 +32,7 @@ export interface TaskOptions { /** * A group name to manual clearing of pending tasks via the [[Async]] module. * Providing this value disables automatically cleanup of render tasks on the `update` hook. - * If this parameter is set as a function, the group name will be dynamically calculated on each new iteration. + * If this parameter is set as a function, the group name will be dynamically calculated on each iteration. * * @example * ``` diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 71023da8c1..82fc2bc6b8 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -149,6 +149,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { }); }); + // Register a worker to clean up memory upon component destruction $a.worker(() => { computedFields.forEach(([name]) => { delete component[name]?.[cacheStatus]; diff --git a/src/core/component/engines/vue3/render.ts b/src/core/component/engines/vue3/render.ts index 486e0884e9..653e481254 100644 --- a/src/core/component/engines/vue3/render.ts +++ b/src/core/component/engines/vue3/render.ts @@ -178,6 +178,7 @@ export function render(vnode: CanArray, parent?: ComponentInterface, grou value: root }); + // Register a worker to clean up memory upon component destruction parent.unsafe.async.worker(() => { vue.unmount(); }, {group}); diff --git a/src/core/component/functional/context.ts b/src/core/component/functional/context.ts index 2958f7d4c1..afb1759915 100644 --- a/src/core/component/functional/context.ts +++ b/src/core/component/functional/context.ts @@ -133,6 +133,10 @@ export function createVirtualContext( } }); + // When extending the context of the original component (e.g., Vue), to avoid conflicts, + // we create an object with the original context in the prototype: `V4Context<__proto__: OriginalContext>`. + // However, for functional components, this approach is redundant and can lead to memory leaks. + // Instead, we simply assign a reference to the raw context, which points to the original context. saveRawComponentContext(virtualCtx, virtualCtx); initProps(virtualCtx, { From 8fedae4d0e267c1bb1dc8bf44f275c6c1a130bf3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 17 Jan 2024 17:55:47 +0300 Subject: [PATCH 21/21] chore: :up: 4.0.0-beta.49 --- CHANGELOG.md | 13 +++++++++++++ build/snakeskin/CHANGELOG.md | 6 ++++++ package.json | 2 +- src/components/base/b-dynamic-page/CHANGELOG.md | 6 ++++++ src/components/friends/vdom/CHANGELOG.md | 6 ++++++ src/core/component/engines/vue3/CHANGELOG.md | 6 ++++++ src/core/component/init/CHANGELOG.md | 6 ++++++ 7 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bdea4eee3..64fe880c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,19 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ +## v4.0.0-beta.49 (2024-01-17) + +#### :rocket: New Feature + +* Now the `render` method can accept the name of an asynchronous group to control the invocation of destructors `components/friends/vdom` + +#### :bug: Bug Fix + +* Fixed memory leaks when switching pages `bDynamicPage` +* Fixed a memory leak when creating dynamic components via the VDOM API `core/component/engines/vue3` +* Fixed memory leaks when removing components `core/component/init` +* Added memoization for the `getParent` and `getRoot` props to prevent unnecessary re-renders `build/snakeskin` + ## v4.0.0-beta.48 (2024-01-17) #### :boom: Breaking Change diff --git a/build/snakeskin/CHANGELOG.md b/build/snakeskin/CHANGELOG.md index 5862e341b5..e0894dc125 100644 --- a/build/snakeskin/CHANGELOG.md +++ b/build/snakeskin/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.49 (2024-01-17) + +#### :bug: Bug Fix + +* Added memoization for the `getParent` and `getRoot` props to prevent unnecessary re-renders + ## v4.0.0-beta.38 (2023-11-15) #### :bug: Bug Fix diff --git a/package.json b/package.json index 356e4411f0..437d629072 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "packageManager": "yarn@4.0.2", "typings": "index.d.ts", "license": "MIT", - "version": "4.0.0-beta.48", + "version": "4.0.0-beta.49", "author": { "name": "kobezzza", "email": "kobezzza@gmail.com", diff --git a/src/components/base/b-dynamic-page/CHANGELOG.md b/src/components/base/b-dynamic-page/CHANGELOG.md index 5aa8b915a1..eeb3184575 100644 --- a/src/components/base/b-dynamic-page/CHANGELOG.md +++ b/src/components/base/b-dynamic-page/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.49 (2024-01-17) + +#### :bug: Bug Fix + +* Fixed memory leaks when switching pages + ## v4.0.0-alpha.1 (2022-12-14) #### :boom: Breaking Change diff --git a/src/components/friends/vdom/CHANGELOG.md b/src/components/friends/vdom/CHANGELOG.md index bdc31dba6a..09ea43bde3 100644 --- a/src/components/friends/vdom/CHANGELOG.md +++ b/src/components/friends/vdom/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.49 (2024-01-17) + +#### :rocket: New Feature + +* Now the `render` method can accept the name of an asynchronous group to control the invocation of destructors + ## v4.0.0-alpha.1 (2022-12-14) #### :boom: Breaking Change diff --git a/src/core/component/engines/vue3/CHANGELOG.md b/src/core/component/engines/vue3/CHANGELOG.md index 651a4fe3fa..a7e6e30036 100644 --- a/src/core/component/engines/vue3/CHANGELOG.md +++ b/src/core/component/engines/vue3/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.49 (2024-01-17) + +#### :bug: Bug Fix + +* Fixed a memory leak when creating dynamic components via the VDOM API + ## v4.0.0-beta.23 (2023-09-18) #### :bug: Bug Fix diff --git a/src/core/component/init/CHANGELOG.md b/src/core/component/init/CHANGELOG.md index 95acd5c002..7cc94a4548 100644 --- a/src/core/component/init/CHANGELOG.md +++ b/src/core/component/init/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.49 (2024-01-17) + +#### :bug: Bug Fix + +* Fixed memory leaks when removing components + ## v4.0.0-alpha.1 (2022-12-14) #### :boom: Breaking Change