From fd8ab61584ec98cff82539095a1a9af127061569 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 2 May 2024 23:20:32 +0700 Subject: [PATCH 01/12] fix(p-components): fix ComponentData type import --- packages/devtools-core/src/pages/p-components/p-components.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools-core/src/pages/p-components/p-components.ts b/packages/devtools-core/src/pages/p-components/p-components.ts index dfa6b97..12d30f1 100644 --- a/packages/devtools-core/src/pages/p-components/p-components.ts +++ b/packages/devtools-core/src/pages/p-components/p-components.ts @@ -10,7 +10,7 @@ import symbolGenerator from 'core/symbol'; import iDynamicPage, { component, field, watch } from 'components/super/i-dynamic-page/i-dynamic-page'; import type { Item } from 'components/base/b-tree/b-tree'; -import type { ComponentData } from 'features/components/b-components-panel'; +import type { ComponentData } from 'features/components/b-components-panel/b-components-panel'; import type bComponentsTree from 'features/components/b-components-tree/b-components-tree'; import type bComponentsPanel from 'features/components/b-components-panel/b-components-panel'; From 2ad90575d1d5c0c6de7308e0a914b0fcca644635 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Thu, 2 May 2024 23:21:06 +0700 Subject: [PATCH 02/12] docs(devtools-core): add class diagram --- packages/devtools-core/README.md | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/devtools-core/README.md b/packages/devtools-core/README.md index 0575a8d..8cf62a6 100644 --- a/packages/devtools-core/README.md +++ b/packages/devtools-core/README.md @@ -9,6 +9,7 @@ exclusive APIs', such as: `chrome.runtime`, `chrome.devtools`, etc. ## Table of contents - [Quick Start](#quick-start) +- [Interaction with the inspected application](#interaction-with-the-inspected-application) ## Quick Start @@ -17,3 +18,60 @@ exclusive APIs', such as: `chrome.runtime`, `chrome.devtools`, etc. 3. `yarn dev` 4. Open new terminal and run `yarn start` 5. Open browser at `http://localhost:3333` + +## Interaction with the inspected application + +```mermaid +classDiagram + direction TB + %% V4Fire Types + class ComponentInterface + <>ComponentInterface + + %% Devtools Types + class ComponentData { + string componentId + string componentName + Dictionary values + string[] hierarchy + %% other props are omitted + } + + class DevtoolsHandle~Target~ { + -Nullable~string~ id + evaluate~T~((Target ctx) => T) Promise~T~ + dispose() Promise~void~ + } + + class ComponentHandle~ComponentInterface~ { + highlight(boolean autoHide) void + getData() Promise~ComponentData~ + setMod(string key, string value) Promise~boolean~ + subscribe(Function callback) Function + } + + ComponentHandle o-- ComponentInterface + ComponentHandle --|> DevtoolsHandle + ComponentHandle o-- ComponentData + + class TreeItem { + string value + string label + string componentName + number renderCounterProp + boolean isFunctionalProp + TreeItem[] children + } + + class InspectedApp { + Components components + } + <> InspectedApp + + class Components { + tree() Promise~TreeItem[]~ + } + + Components o-- TreeItem + InspectedApp *-- Components +``` From c6ba3cbf4df1a5646bc611b3ed149e047eda66bc Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 3 May 2024 17:59:34 +0700 Subject: [PATCH 03/12] chore(devtools-eval): allow only sync functions in devtoolsEval --- .../src/core/browser-api/devtools/devtools-eval.ts | 8 +++++++- packages/devtools-extension/src/pages/p-devtools/init.ts | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/devtools-extension/src/core/browser-api/devtools/devtools-eval.ts b/packages/devtools-extension/src/core/browser-api/devtools/devtools-eval.ts index abba1ee..7454fbc 100644 --- a/packages/devtools-extension/src/core/browser-api/devtools/devtools-eval.ts +++ b/packages/devtools-extension/src/core/browser-api/devtools/devtools-eval.ts @@ -13,10 +13,16 @@ import { browserAPI } from 'core/browser-api'; * @param func * @param args */ -function devtoolsEval any>(func: F, args?: Parameters): Promise>; +function devtoolsEval JSONLikeValue | Array> | void>( + func: F, + args?: Parameters +): Promise>; /** * Promisified version of `chrome.devtools.inspectedWindow.eval` + * Prefer using the content scripts instead of devtools eval. + * + * @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/devtools/inspectedWindow/eval * @param expression */ function devtoolsEval(expression: string): Promise; diff --git a/packages/devtools-extension/src/pages/p-devtools/init.ts b/packages/devtools-extension/src/pages/p-devtools/init.ts index 439fdcf..099c756 100644 --- a/packages/devtools-extension/src/pages/p-devtools/init.ts +++ b/packages/devtools-extension/src/pages/p-devtools/init.ts @@ -77,7 +77,8 @@ function mountDevToolsWhenV4FireHasLoaded() { } if (shouldUpdateRoot) { - devtoolsEval(() => new Promise((resolve) => globalThis.requestIdleCallback(resolve))) + // TODO: Wait for requestIdleCallback in page context + devtoolsEval(() => undefined) .then(() => { setRootPlaceholder(null); shouldUpdateRoot = false; From 1541c981c3445a7e7fcbd1eba6f64e8b323e7d51 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 3 May 2024 18:00:20 +0700 Subject: [PATCH 04/12] feat(devtools-backend): create handles map --- packages/devtools-backend/src/handles.ts | 12 ++++++++++++ packages/devtools-backend/src/index.ts | 1 + 2 files changed, 13 insertions(+) create mode 100644 packages/devtools-backend/src/handles.ts diff --git a/packages/devtools-backend/src/handles.ts b/packages/devtools-backend/src/handles.ts new file mode 100644 index 0000000..dcd32f6 --- /dev/null +++ b/packages/devtools-backend/src/handles.ts @@ -0,0 +1,12 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +/** + * This map is used by `DevtoolsHandle` + */ +export const handles = new Map(); diff --git a/packages/devtools-backend/src/index.ts b/packages/devtools-backend/src/index.ts index 768e5a5..b4535ca 100644 --- a/packages/devtools-backend/src/index.ts +++ b/packages/devtools-backend/src/index.ts @@ -10,3 +10,4 @@ export * from './serialize'; export * from './search'; export * from './ui'; export * from './interface'; +export * from './handles'; From 038cd930ccaa74ef3e9ee4296885038e14a3b050 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 3 May 2024 18:01:01 +0700 Subject: [PATCH 05/12] chore(devtools-core): declare backend var --- packages/devtools-core/index.d.ts | 7 +++++++ packages/devtools-extension/index.d.ts | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/devtools-core/index.d.ts b/packages/devtools-core/index.d.ts index e3e60e9..93940a3 100644 --- a/packages/devtools-core/index.d.ts +++ b/packages/devtools-core/index.d.ts @@ -6,4 +6,11 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ +/* eslint-disable no-var */ + /// + +/** + * Devtools backend API - available only in the inspected window context + */ +declare var __V4FIRE_DEVTOOLS_BACKEND__: typeof import('@v4fire/devtools-backend'); diff --git a/packages/devtools-extension/index.d.ts b/packages/devtools-extension/index.d.ts index e89bbdd..2bde60c 100644 --- a/packages/devtools-extension/index.d.ts +++ b/packages/devtools-extension/index.d.ts @@ -6,14 +6,7 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ -/* eslint-disable no-var */ - /// declare const browser: typeof chrome; - -/** - * Devtools backend API - available only in the inspected window context - */ -declare var __V4FIRE_DEVTOOLS_BACKEND__: typeof import('@v4fire/devtools-backend'); From 7e6d933fd194106a5b1230878bae111c7e58e8da Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 3 May 2024 18:03:53 +0700 Subject: [PATCH 06/12] feat(devtools-core): create DevtoolsHandle class This class is similar to JSHandle in Playwright --- .../src/core/inspect/devtools-handle/class.ts | 41 +++++++++++++++++++ .../inspect/devtools-handle/engines/index.ts | 9 ++++ .../engines/loopback-engine.ts | 14 +++++++ .../src/core/inspect/devtools-handle/index.ts | 11 +++++ .../core/inspect/devtools-handle/interface.ts | 26 ++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 packages/devtools-core/src/core/inspect/devtools-handle/class.ts create mode 100644 packages/devtools-core/src/core/inspect/devtools-handle/engines/index.ts create mode 100644 packages/devtools-core/src/core/inspect/devtools-handle/engines/loopback-engine.ts create mode 100644 packages/devtools-core/src/core/inspect/devtools-handle/index.ts create mode 100644 packages/devtools-core/src/core/inspect/devtools-handle/interface.ts diff --git a/packages/devtools-core/src/core/inspect/devtools-handle/class.ts b/packages/devtools-core/src/core/inspect/devtools-handle/class.ts new file mode 100644 index 0000000..db61342 --- /dev/null +++ b/packages/devtools-core/src/core/inspect/devtools-handle/class.ts @@ -0,0 +1,41 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import { disposeEngine, evaluateEngine } from 'core/inspect/devtools-handle/engines'; +import type { PageFunctionWithCtx } from 'core/inspect/devtools-handle/interface'; + +export default class DevtoolsHandle { + /** + * The id of the handle. This id is used as the key to reference the handled object + */ + protected readonly id!: string; + + constructor(id: string) { + this.id = id; + } + + /** + * Returns the return value of the `pageFunction` + * + * @param pageFunction + * @param arg + */ + evaluate( + pageFunction: PageFunctionWithCtx, + arg?: Arg + ): Promise { + return evaluateEngine(pageFunction, arg, this.id); + } + + /** + * The method stops referencing the handled object + */ + dispose(): Promise { + return disposeEngine(this.id); + } +} diff --git a/packages/devtools-core/src/core/inspect/devtools-handle/engines/index.ts b/packages/devtools-core/src/core/inspect/devtools-handle/engines/index.ts new file mode 100644 index 0000000..6541553 --- /dev/null +++ b/packages/devtools-core/src/core/inspect/devtools-handle/engines/index.ts @@ -0,0 +1,9 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +export * from 'core/inspect/devtools-handle/engines/loopback-engine'; diff --git a/packages/devtools-core/src/core/inspect/devtools-handle/engines/loopback-engine.ts b/packages/devtools-core/src/core/inspect/devtools-handle/engines/loopback-engine.ts new file mode 100644 index 0000000..1a57193 --- /dev/null +++ b/packages/devtools-core/src/core/inspect/devtools-handle/engines/loopback-engine.ts @@ -0,0 +1,14 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ +import type { EvaluateEngine, DisposeEngine } from 'core/inspect/devtools-handle/interface'; + +// eslint-disable-next-line @v4fire/require-jsdoc +export const evaluateEngine: EvaluateEngine = () => Object.cast(Promise.resolve(undefined)); + +// eslint-disable-next-line @v4fire/require-jsdoc +export const disposeEngine: DisposeEngine = () => Promise.resolve(); diff --git a/packages/devtools-core/src/core/inspect/devtools-handle/index.ts b/packages/devtools-core/src/core/inspect/devtools-handle/index.ts new file mode 100644 index 0000000..1a2000b --- /dev/null +++ b/packages/devtools-core/src/core/inspect/devtools-handle/index.ts @@ -0,0 +1,11 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +export { default } from 'core/inspect/devtools-handle/class'; +export * from 'core/inspect/devtools-handle/engines'; +export * from 'core/inspect/devtools-handle/interface'; diff --git a/packages/devtools-core/src/core/inspect/devtools-handle/interface.ts b/packages/devtools-core/src/core/inspect/devtools-handle/interface.ts new file mode 100644 index 0000000..676c7c7 --- /dev/null +++ b/packages/devtools-core/src/core/inspect/devtools-handle/interface.ts @@ -0,0 +1,26 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ +export interface PageFunction { + (arg: Arg): T; +} + +export interface PageFunctionWithCtx { + (ctx: Ctx, arg: Arg): T; +} + +export interface EvaluateEngine { + ( + pageFunction: PageFunction | PageFunctionWithCtx, + arg?: Arg, + handleId?: string + ): Promise; +} + +export interface DisposeEngine { + (handleId: string): Promise; +} From c5c989a3b73621b1d53291073b0c84df291fe1ff Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 3 May 2024 18:06:36 +0700 Subject: [PATCH 07/12] feat(devtools-core): create ComponentHandle class This is a special handle to interact with the component on the page --- .../core/inspect/component-handle/class.ts | 193 ++++++++++++++++++ .../core/inspect/component-handle/index.ts | 9 + .../devtools-core/src/core/inspect/index.ts | 10 + .../src/core/inspect/interface.ts | 29 +++ 4 files changed, 241 insertions(+) create mode 100644 packages/devtools-core/src/core/inspect/component-handle/class.ts create mode 100644 packages/devtools-core/src/core/inspect/component-handle/index.ts create mode 100644 packages/devtools-core/src/core/inspect/index.ts create mode 100644 packages/devtools-core/src/core/inspect/interface.ts diff --git a/packages/devtools-core/src/core/inspect/component-handle/class.ts b/packages/devtools-core/src/core/inspect/component-handle/class.ts new file mode 100644 index 0000000..867e07a --- /dev/null +++ b/packages/devtools-core/src/core/inspect/component-handle/class.ts @@ -0,0 +1,193 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ +import SyncPromise from 'core/promise/sync'; +import { ComponentQuery, deserialize } from '@v4fire/devtools-backend'; + +// FIXME: This import confronts with the V4Fire restrictions @see https://github.com/V4Fire/Client/issues/1111 +import type iBlock from 'components/super/i-block/i-block'; +import type { ComponentElement } from 'core/component'; + +import DevtoolsHandle, { PageFunctionWithCtx, evaluateEngine } from 'core/inspect/devtools-handle'; +import type { ComponentData } from 'features/components/b-components-panel/interface'; + +export default class ComponentHandle extends DevtoolsHandle { + /** + * Component query + */ + private readonly query: ComponentQuery; + + /** + * Promise of component handle readiness + */ + private isReadyStore?: SyncPromise; + + constructor(query: ComponentQuery) { + const id = `component-${query.componentId}`; + super(id); + this.query = query; + } + + /** + * Returns the component id + */ + get componentId(): string { + return this.query.componentId; + } + + /** + * Returns the component name + */ + get componentName(): string | undefined { + return this.query.componentName; + } + + override evaluate( + pageFunction: PageFunctionWithCtx, + arg?: Arg | undefined + ): Promise { + return this.isReady.then(super.evaluate.bind(this, pageFunction, arg)); + } + + /** + * Highlights the component + * @param autoHide + */ + highlight(autoHide: boolean): void { + this.evaluate((ctx, autoHide) => { + const backend = globalThis.__V4FIRE_DEVTOOLS_BACKEND__; + const node = backend.findComponentNode(ctx); + // @ts-expect-error Non-standard API + node?.scrollIntoViewIfNeeded(false); + + backend.componentHighlight.show(ctx); + + if (autoHide) { + backend.componentHighlight.hide({delay: 1500, animate: true}); + } + }, autoHide).catch(stderr); + } + + /** + * Returns the data of the component + */ + async getData(): Promise { + const serializedData = await this.evaluate((ctx) => { + const backend = globalThis.__V4FIRE_DEVTOOLS_BACKEND__; + + const restricted = new Set([ + 'r', + 'self', + 'unsafe', + 'router', + 'LANG_PACKS' + ]); + + const node = backend.findComponentNode(ctx); + + if (node == null) { + return null; + } + + const {component} = node; + + if (component == null) { + throw new Error('DOM node doesn\'t have component property'); + } + + const {componentName, props, fields, computedFields, systemFields, mods} = component.unsafe.meta; + + const values = {}; + + [props, fields, computedFields, systemFields].forEach((dict) => { + Object.keys(dict).forEach((key) => { + if (!restricted.has(key)) { + values[key] = component[key]; + } + }); + }); + + const hierarchy: string[] = []; + + let parent = component.unsafe.meta.parentMeta; + while (parent != null) { + hierarchy.push(parent.componentName); + parent = parent.parentMeta; + } + + const result = { + componentId: ctx.componentId, + componentName, + props, + fields, + computedFields, + systemFields, + mods, + hierarchy, + values + }; + + return backend.serialize( + result, + (key, value) => key.startsWith('$') || restricted.has(key) || value === globalThis || value === document || value === console + ); + }); + + return serializedData != null ? deserialize(serializedData) : null; + } + + /** + * Sets a component modifier by the specified name + * + * @param name - the modifier name + * @param value - the modifier value + */ + setMod(name: string, value?: unknown): Promise { + return this.evaluate((ctx, {name, value}) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ctx.setMod(name, value); + }, {name, value}); + } + + /** + * Subscribes to component changes. The `callback` is triggered each time + * when something changes in the component + * + * @param _callback + */ + subscribe(_callback: () => void): () => void { + return Object.throw(); + } + + /** + * Resolves when component handle is ready to be evaluated + */ + private get isReady(): Promise { + const {id, query} = this; + + if (this.isReadyStore == null) { + const handleCreated = evaluateEngine(({id, query}) => { + const backend = globalThis.__V4FIRE_DEVTOOLS_BACKEND__; + const node = backend.findComponentNode(query); + + if (node?.component != null) { + // FIXME: encapsulation of devtool-handle is violated + backend.handles.set(id, node.component); + + node.component.unsafe.$once('hook:beforeDestroy', () => { + backend.handles.delete(id); + }); + } + }, {id, query}); + + this.isReadyStore = SyncPromise.resolve(handleCreated); + } + + return this.isReadyStore; + } +} + diff --git a/packages/devtools-core/src/core/inspect/component-handle/index.ts b/packages/devtools-core/src/core/inspect/component-handle/index.ts new file mode 100644 index 0000000..70ff0bc --- /dev/null +++ b/packages/devtools-core/src/core/inspect/component-handle/index.ts @@ -0,0 +1,9 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +export { default } from 'core/inspect/component-handle/class'; diff --git a/packages/devtools-core/src/core/inspect/index.ts b/packages/devtools-core/src/core/inspect/index.ts new file mode 100644 index 0000000..7ce90f3 --- /dev/null +++ b/packages/devtools-core/src/core/inspect/index.ts @@ -0,0 +1,10 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +export { default as DevtoolsHandle } from 'core/inspect/devtools-handle'; +export { default as ComponentHandle } from 'core/inspect/component-handle'; diff --git a/packages/devtools-core/src/core/inspect/interface.ts b/packages/devtools-core/src/core/inspect/interface.ts new file mode 100644 index 0000000..5405cd6 --- /dev/null +++ b/packages/devtools-core/src/core/inspect/interface.ts @@ -0,0 +1,29 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +import type { ComponentMeta } from 'core/component'; + +export type ComponentData = { + /** + * Component's id + */ + componentId: string; + + /** + * Component's values for props, fields, etc. + */ + values: Dictionary; + + /** + * Inheritance hierarchy starts from closest parent + */ + hierarchy: string[]; + +} & Pick< + ComponentMeta, 'componentName' | 'props' | 'fields' | 'computedFields' | 'systemFields' | 'mods' +>; From 0ee0ee48bf37b6944e385ea93d727607f535b4fd Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 3 May 2024 18:08:13 +0700 Subject: [PATCH 08/12] chore(devtools-extension): create extension engine for DevtoolsHandle --- .../engines/extension-engine.ts | 49 +++++++++++++++++++ .../inspect/devtools-handle/engines/index.ts | 9 ++++ 2 files changed, 58 insertions(+) create mode 100644 packages/devtools-extension/src/core/inspect/devtools-handle/engines/extension-engine.ts create mode 100644 packages/devtools-extension/src/core/inspect/devtools-handle/engines/index.ts diff --git a/packages/devtools-extension/src/core/inspect/devtools-handle/engines/extension-engine.ts b/packages/devtools-extension/src/core/inspect/devtools-handle/engines/extension-engine.ts new file mode 100644 index 0000000..9f3d6f4 --- /dev/null +++ b/packages/devtools-extension/src/core/inspect/devtools-handle/engines/extension-engine.ts @@ -0,0 +1,49 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ +import { devtoolsEval } from 'core/browser-api'; +import type { DisposeEngine, EvaluateEngine } from 'core/inspect/devtools-handle/interface'; + +// eslint-disable-next-line @v4fire/require-jsdoc +export const evaluateEngine: EvaluateEngine = async (pageFunction, arg, handleId) => { + return devtoolsEval(evalPageFunction, [pageFunction.toString(), arg, handleId]); + + function evalPageFunction( + serializedPageFunction: string, + arg: unknown, + handleId?: string + ) { + const target = handleId != null ? globalThis.__V4FIRE_DEVTOOLS_BACKEND__.handles.get(handleId) : null; + + if (handleId != null && target == null) { + throw new Error('Handle target has been destroyed'); + } + + let func: Function; + if (target != null) { + // eslint-disable-next-line no-new-func + func = new Function('arg', 'ctx', `return (${serializedPageFunction})(ctx, arg)`); + + } else { + // eslint-disable-next-line no-new-func + func = new Function('arg', `return (${serializedPageFunction})(arg)`); + } + + try { + const result = func(arg, target); + + return result; + } catch (error) { + throw new Error('Failed to evaluate the function', {cause: error}); + } + } +}; + +// eslint-disable-next-line @v4fire/require-jsdoc +export const disposeEngine: DisposeEngine = async (handleId) => devtoolsEval((handleId) => { + globalThis.__V4FIRE_DEVTOOLS_BACKEND__.handles.delete(handleId); +}, [handleId]); diff --git a/packages/devtools-extension/src/core/inspect/devtools-handle/engines/index.ts b/packages/devtools-extension/src/core/inspect/devtools-handle/engines/index.ts new file mode 100644 index 0000000..d314b67 --- /dev/null +++ b/packages/devtools-extension/src/core/inspect/devtools-handle/engines/index.ts @@ -0,0 +1,9 @@ +/*! + * V4Fire DevTools + * https://github.com/V4Fire/DevTools + * + * Released under the MIT license + * https://github.com/V4Fire/DevTools/blob/main/LICENSE + */ + +export * from 'core/inspect/devtools-handle/engines/extension-engine'; From f738d6bc61ac668ef8856cc32909d3ac50bc1861 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 3 May 2024 18:08:59 +0700 Subject: [PATCH 09/12] chore: use component handles instead of devtoolsEval --- .../b-components-panel/b-components-panel.ts | 38 +++++++-- .../b-components-tree/b-components-tree.ts | 52 ++++++++++-- .../src/pages/p-components/p-components.ss | 3 +- .../src/pages/p-components/p-components.ts | 26 ++++-- .../devtools-extension/components-lock.json | 4 +- .../b-components-panel/b-components-panel.ts | 69 ---------------- .../b-components-tree/b-components-tree.ts | 54 +------------ .../src/pages/p-components/p-components.ts | 80 +------------------ 8 files changed, 102 insertions(+), 224 deletions(-) delete mode 100644 packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts diff --git a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts index 98f4dae..58c9219 100644 --- a/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts +++ b/packages/devtools-core/src/features/components/b-components-panel/b-components-panel.ts @@ -6,6 +6,8 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ +import type { ComponentHandle } from 'core/inspect'; + import iBlock, { component, prop, field, computed } from 'components/super/i-block/i-block'; import type bTree from 'components/base/b-tree/b-tree'; @@ -22,6 +24,12 @@ export default class bComponentsPanel extends iBlock { tree?: bTree; }; + /** + * Component's handle + */ + @prop(Object) + componentHandle!: ComponentHandle; + /** * Component's data */ @@ -95,11 +103,11 @@ export default class bComponentsPanel extends iBlock { /** * Change selected component mod * - * @param _key - * @param _value + * @param key + * @param value */ - protected onItemChangeMod(_key: string, _value: unknown): void { - // TODO: use inspected app + protected onItemChangeMod(key: string, value: unknown): void { + this.componentHandle.setMod(key, value).catch(stderr); } /** @@ -113,6 +121,26 @@ export default class bComponentsPanel extends iBlock { * Inspect component's node */ protected onInspect(): void { - // TODO: use inspected app + // FIXME: think for the better encapsulation + this.componentHandle.evaluate((ctx) => { + // eslint-disable-next-line @typescript-eslint/method-signature-style + const {inspect} = <{inspect?: (el: Element) => void} & Global>globalThis; + + if (typeof inspect !== 'function') { + // eslint-disable-next-line no-alert + alert('Browser doesn\'t provide inspect util'); + return; + } + + const node = globalThis.__V4FIRE_DEVTOOLS_BACKEND__.findComponentNode(ctx); + + if (node != null) { + inspect(node); + + } else { + // eslint-disable-next-line no-alert + alert('Component\'s node not found'); + } + }).catch(stderr); } } diff --git a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts index 7529764..63c8604 100644 --- a/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts +++ b/packages/devtools-core/src/features/components/b-components-tree/b-components-tree.ts @@ -8,8 +8,9 @@ import { debounce } from 'core/functools'; import { derive } from 'core/functools/trait'; +import { ComponentHandle } from 'core/inspect'; -import iBlock, { component, prop, field, system, watch } from 'components/super/i-block/i-block'; +import iBlock, { component, prop, field, system, watch, hook } from 'components/super/i-block/i-block'; import Search, { SearchDirection, SearchMatch } from 'components/traits/i-search/search'; import iSearch from 'components/traits/i-search/i-search'; @@ -60,6 +61,32 @@ class bComponentsTree extends iBlock implements iSearch { search!: Search; + @system((o) => o.sync.link>('items', (value) => { + const stack = [...value]; + const handles = new Map(); + + while (stack.length > 0) { + // NOTE: order is not important + const item = stack.pop()!; + const {value: componentId, componentName} = item; + handles.set(componentId, new ComponentHandle({componentId, componentName})); + + if (item.children != null) { + stack.push(...item.children); + } + } + + return handles; + })) + + componentHandles!: Map; + + /** + * Id of highlighted component + */ + @system() + highlightedComponentId: string | null = null; + /** * Makes item active and scrolls to it if needed * @@ -156,13 +183,14 @@ class bComponentsTree extends iBlock implements iSearch { } /** + * Listens for change in component tree * * @param _ * @param componentId */ @watch('?$refs.tree:change') protected onTreeChange(_: unknown, componentId: string): void { - this.emit('change', componentId); + this.emit('change', this.componentHandles.get(componentId)); } /** @@ -180,23 +208,31 @@ class bComponentsTree extends iBlock implements iSearch { } } + @hook('mounted') + protected init(): void { + this.async.on(this.selfEmitter, 'change', (_: unknown, component: ComponentHandle) => { + component.highlight(this.highlightedComponentId !== component.componentId); + }); + } + /** * Handle item mouseenter event * * @param item - * @param event + * @param _event */ - protected onItemMouseEnter(item: Item, event: MouseEvent): void { - // TODO: use engine to highlight the component node + protected onItemMouseEnter(item: Item, _event: MouseEvent): void { + this.highlightedComponentId = item.value; + this.componentHandles.get(item.value)?.highlight(false); } /** * Handle item mouseleave event * - * @param item - * @param event + * @param _item + * @param _event */ - protected onItemMouseLeave(item: Item, event: MouseEvent): void { + protected onItemMouseLeave(_item: Item, _event: MouseEvent): void { // TODO: use engine to remove highlight from the component node } } diff --git a/packages/devtools-core/src/pages/p-components/p-components.ss b/packages/devtools-core/src/pages/p-components/p-components.ss index 6177ab4..fae644f 100644 --- a/packages/devtools-core/src/pages/p-components/p-components.ss +++ b/packages/devtools-core/src/pages/p-components/p-components.ss @@ -9,10 +9,11 @@ ref = components | :items = components . - < template v-if = selectedComponentId + < template v-if = selectedComponent < template v-if = selectedComponentData != null < b-components-panel & ref = panel | + :componentHandle = selectedComponent | :componentData = selectedComponentData . < . v-else diff --git a/packages/devtools-core/src/pages/p-components/p-components.ts b/packages/devtools-core/src/pages/p-components/p-components.ts index 12d30f1..f2e6e60 100644 --- a/packages/devtools-core/src/pages/p-components/p-components.ts +++ b/packages/devtools-core/src/pages/p-components/p-components.ts @@ -7,6 +7,7 @@ */ import symbolGenerator from 'core/symbol'; +import type { ComponentHandle } from 'core/inspect'; import iDynamicPage, { component, field, watch } from 'components/super/i-dynamic-page/i-dynamic-page'; import type { Item } from 'components/base/b-tree/b-tree'; @@ -32,10 +33,10 @@ export default class pComponents extends iDynamicPage { components: Item[] = []; /** - * Selected component id + * Selected component handle */ @field() - selectedComponentId: string | null = null; + selectedComponent: ComponentHandle | null = null; /** * Selected component meta @@ -47,18 +48,19 @@ export default class pComponents extends iDynamicPage { * Handle component select * * @param _ - * @param componentId + * @param component */ @watch('?$refs.components:change') - onComponentSelect(_: unknown, componentId: string): void { - this.selectedComponentId = componentId; + onComponentSelect(_: unknown, component: ComponentHandle): void { + this.selectedComponent = component; this.selectedComponentData = null; const load = this.async.throttle(async () => { try { await this.loadSelectedComponentData(); } catch (error) { - // TODO: show alert + // eslint-disable-next-line no-alert + globalThis.alert(`Failed to load data, reason: ${error.message}`); stderr(error); } }, 1000, {label: $$.loadSelectedComponentMeta}); @@ -69,8 +71,16 @@ export default class pComponents extends iDynamicPage { /** * Loads selected component data */ - loadSelectedComponentData(): CanPromise { - // Override + async loadSelectedComponentData(): Promise { + const data = await this.selectedComponent?.getData(); + + if (data == null) { + // eslint-disable-next-line no-alert + globalThis.alert('No data'); + + } else { + this.selectedComponentData = data; + } } } diff --git a/packages/devtools-extension/components-lock.json b/packages/devtools-extension/components-lock.json index 2d249b4..5bebffc 100644 --- a/packages/devtools-extension/components-lock.json +++ b/packages/devtools-extension/components-lock.json @@ -223,7 +223,7 @@ }, "type": "block", "mixin": false, - "logic": "src/features/components/b-components-panel/b-components-panel.ts", + "logic": "node_modules/@v4fire/devtools-core/src/features/components/b-components-panel/b-components-panel.ts", "styles": [ "node_modules/@v4fire/devtools-core/src/features/components/b-components-panel/b-components-panel.styl" ], @@ -3179,4 +3179,4 @@ ] ] } -} \ No newline at end of file +} diff --git a/packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts b/packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts deleted file mode 100644 index d65c45a..0000000 --- a/packages/devtools-extension/src/features/components/b-components-panel/b-components-panel.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * V4Fire DevTools - * https://github.com/V4Fire/DevTools - * - * Released under the MIT license - * https://github.com/V4Fire/DevTools/blob/main/LICENSE - */ - -import type { ComponentQuery } from '@v4fire/devtools-backend'; - -import { devtoolsEval } from 'core/browser-api'; - -import iBlock, { component, ComponentElement } from 'components/super/i-block/i-block'; - -import Super from '@super/features/components/b-components-panel/b-components-panel'; - -export * from 'features/components/b-components-panel/interface'; - -@component() -export default class bComponentsPanel extends Super { - protected override onItemChangeMod(key: string, value: unknown): void { - const {componentId, componentName} = this.componentData; - - devtoolsEval(evalSetComponentMod, [key, value, {componentId, componentName}]) - .catch(stderr); - } - - protected override onInspect(): void { - const {componentId, componentName} = this.componentData; - - devtoolsEval(evalInspect, [{componentId, componentName}]) - .catch(stderr); - } -} - -function evalInspect(query: ComponentQuery): void { - // eslint-disable-next-line @typescript-eslint/method-signature-style - const {inspect} = <{inspect?: (el: Element) => void} & Global>globalThis; - - if (typeof inspect !== 'function') { - // eslint-disable-next-line no-alert - alert('Browser doesn\'t provide inspect util'); - return; - } - - const node = globalThis.__V4FIRE_DEVTOOLS_BACKEND__.findComponentNode(query); - - if (node != null) { - inspect(node); - - } else { - // eslint-disable-next-line no-alert - alert('Component\'s node not found'); - } -} - -function evalSetComponentMod(key: string, value: unknown, query: ComponentQuery) { - const node = globalThis.__V4FIRE_DEVTOOLS_BACKEND__.findComponentNode(query); - - if (node == null) { - return; - } - - const {component} = >node; - - if (component != null) { - void component.setMod(key, value); - } -} diff --git a/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts b/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts index cbc00f6..0da6f96 100644 --- a/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts +++ b/packages/devtools-extension/src/features/components/b-components-tree/b-components-tree.ts @@ -6,49 +6,16 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ -import type { ComponentQuery } from '@v4fire/devtools-backend'; import { devtoolsEval } from 'core/browser-api'; -import { component, hook, system } from 'components/super/i-block/i-block'; +import { component } from 'components/super/i-block/i-block'; -import Super, { Item } from '@super/features/components/b-components-tree/b-components-tree'; +import Super from '@super/features/components/b-components-tree/b-components-tree'; export * from '@super/features/components/b-components-tree/b-components-tree'; @component() export default class bComponentsTree extends Super { - - /** - * Id of highlighted component - */ - @system() - highlightedComponentId: string | null = null; - - @hook('mounted') - init(): void { - this.async.on(this.selfEmitter, 'change', (_: unknown, componentId: string) => { - const item = >this.$refs.tree?.getItemByValue(componentId); - - if (item != null) { - devtoolsEval( - evalHighlightActive, - [this.highlightedComponentId !== componentId, {componentId, componentName: item.componentName}] - ) - .catch(stderr); - } - }); - } - - /** - * Show highlight for the component on mouseenter - * - * @param item - */ - protected override onItemMouseEnter(item: Item): void { - this.highlightedComponentId = item.value; - devtoolsEval(evalShowComponentHighlight, [{componentId: item.value, componentName: item.componentName}]).catch(stderr); - } - /** * Hide highlight for the component */ @@ -57,23 +24,6 @@ export default class bComponentsTree extends Super { } } -function evalHighlightActive(autoHide: boolean, query: ComponentQuery): void { - const backend = globalThis.__V4FIRE_DEVTOOLS_BACKEND__; - const node = backend.findComponentNode(query); - // @ts-expect-error Non-standard API - node?.scrollIntoViewIfNeeded(false); - - backend.componentHighlight.show(query); - - if (autoHide) { - backend.componentHighlight.hide({delay: 1500, animate: true}); - } -} - -function evalShowComponentHighlight(query: ComponentQuery): void { - globalThis.__V4FIRE_DEVTOOLS_BACKEND__.componentHighlight.show(query); -} - function evalHideComponentHighlight(): void { globalThis.__V4FIRE_DEVTOOLS_BACKEND__.componentHighlight.hide(); } diff --git a/packages/devtools-extension/src/pages/p-components/p-components.ts b/packages/devtools-extension/src/pages/p-components/p-components.ts index 1d8c56a..c735315 100644 --- a/packages/devtools-extension/src/pages/p-components/p-components.ts +++ b/packages/devtools-extension/src/pages/p-components/p-components.ts @@ -6,12 +6,11 @@ * https://github.com/V4Fire/DevTools/blob/main/LICENSE */ -import { deserialize } from '@v4fire/devtools-backend'; import { devtoolsEval } from 'core/browser-api'; import type iBlock from 'components/super/i-block/i-block'; -import Super, { component, hook, ComponentInterface } from '@super/pages/p-components/p-components'; +import Super, { component, hook } from '@super/pages/p-components/p-components'; import type { Item } from 'features/components/b-components-tree/b-components-tree'; @@ -28,23 +27,6 @@ export default class pComponents extends Super { globalThis.alert(error.message); }); } - - override async loadSelectedComponentData(): Promise { - const value = this.selectedComponentId!; - // FIXME: bad encapsulation - const item = this.$refs.components?.$refs.tree?.getItemByValue(value); - - const serializedData = await devtoolsEval(evalComponentMeta, [value, item?.componentName]); - - if (serializedData == null) { - // TODO: show custom toast or alert in devtools - // eslint-disable-next-line no-alert - globalThis.alert('No data'); - return; - } - - this.selectedComponentData = deserialize(serializedData); - } } function evalComponentsTree(): Item[] { @@ -131,63 +113,3 @@ function evalComponentsTree(): Item[] { return root != null ? [root] : []; } - -// TODO: create container type -function evalComponentMeta(value: string, name?: string): Nullable { - const restricted = new Set([ - 'r', - 'self', - 'unsafe', - 'router', - 'LANG_PACKS' - ]); - - const node = globalThis.__V4FIRE_DEVTOOLS_BACKEND__.findComponentNode({componentId: value, componentName: name}); - - if (node == null) { - return null; - } - - const {component} = <{component?: ComponentInterface} & Element>node; - - if (component == null) { - throw new Error('DOM node doesn\'t have component property'); - } - - const {componentName, props, fields, computedFields, systemFields, mods} = component.unsafe.meta; - - const values = {}; - - [props, fields, computedFields, systemFields].forEach((dict) => { - Object.keys(dict).forEach((key) => { - if (!restricted.has(key)) { - values[key] = component[key]; - } - }); - }); - - const hierarchy: string[] = []; - - let parent = component.unsafe.meta.parentMeta; - while (parent != null) { - hierarchy.push(parent.componentName); - parent = parent.parentMeta; - } - - const result = { - componentId: value, - componentName, - props, - fields, - computedFields, - systemFields, - mods, - hierarchy, - values - }; - - return globalThis.__V4FIRE_DEVTOOLS_BACKEND__.serialize( - result, - (key, value) => key.startsWith('$') || restricted.has(key) || value === globalThis || value === document || value === console - ); -} From 3a4bd7cfaddf056dab4416bced600d8e2227f721 Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk <46344555+shining-mind@users.noreply.github.com> Date: Mon, 6 May 2024 15:22:33 +0700 Subject: [PATCH 10/12] Update packages/devtools-core/src/core/inspect/component-handle/class.ts Co-authored-by: Magomed Chemurziev --- .../devtools-core/src/core/inspect/component-handle/class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools-core/src/core/inspect/component-handle/class.ts b/packages/devtools-core/src/core/inspect/component-handle/class.ts index 867e07a..5f6c6f5 100644 --- a/packages/devtools-core/src/core/inspect/component-handle/class.ts +++ b/packages/devtools-core/src/core/inspect/component-handle/class.ts @@ -96,7 +96,7 @@ export default class ComponentHandle extends DevtoolsHandle { const {component} = node; if (component == null) { - throw new Error('DOM node doesn\'t have component property'); + throw new Error("DOM node doesn't have component property"); } const {componentName, props, fields, computedFields, systemFields, mods} = component.unsafe.meta; From 23beb0c9a362e14a4e4b695dd835b7cc2895798a Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Mon, 10 Jun 2024 18:56:17 +0700 Subject: [PATCH 11/12] feat(p-components): store selected component's ctx in global var $c --- packages/devtools-core/src/pages/p-components/p-components.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/devtools-core/src/pages/p-components/p-components.ts b/packages/devtools-core/src/pages/p-components/p-components.ts index f2e6e60..1c6325e 100644 --- a/packages/devtools-core/src/pages/p-components/p-components.ts +++ b/packages/devtools-core/src/pages/p-components/p-components.ts @@ -55,6 +55,10 @@ export default class pComponents extends iDynamicPage { this.selectedComponent = component; this.selectedComponentData = null; + void component.evaluate((ctx) => { + globalThis.$c = ctx; + }); + const load = this.async.throttle(async () => { try { await this.loadSelectedComponentData(); From ca4f024350651d8df3971d4d7e818ddbda3ac96d Mon Sep 17 00:00:00 2001 From: Artem Shinkaruk Date: Fri, 9 Aug 2024 18:34:10 +0700 Subject: [PATCH 12/12] feat(p-components): get render counter for v4fire@3.0.0 --- packages/devtools-core/components-lock.json | 82 +++++++++++++++++-- .../src/pages/p-components/p-components.ts | 23 ++++-- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/packages/devtools-core/components-lock.json b/packages/devtools-core/components-lock.json index 45ff562..75f41c2 100644 --- a/packages/devtools-core/components-lock.json +++ b/packages/devtools-core/components-lock.json @@ -1,5 +1,5 @@ { - "hash": "b1f092ba867cc6f75520d31a06c0b387336890ccc7bbe045022f86c3856f3fa9", + "hash": "016ae34d4dca9e70c1af1c98fe7f9109c708eb2e0bbbb8f26e7eeed588125187", "data": { "%data": "%data:Map", "%data:Map": [ @@ -11,19 +11,27 @@ "name": "b-bottom-slide", "parent": "i-block", "dependencies": [], - "libs": [] + "libs": [ + "components/directives/on-resize" + ] }, "name": "b-bottom-slide", "parent": "i-block", "dependencies": [], - "libs": [], + "libs": [ + "components/directives/on-resize" + ], "resolvedLibs": { "%data": "%data:Set", - "%data:Set": [] + "%data:Set": [ + "components/directives/on-resize" + ] }, "resolvedOwnLibs": { "%data": "%data:Set", - "%data:Set": [] + "%data:Set": [ + "components/directives/on-resize" + ] }, "type": "block", "mixin": false, @@ -111,6 +119,38 @@ "etpl": null } ], + [ + "b-component-interface-dummy", + { + "index": "node_modules/@v4fire/client/src/core/component/interface/component/test/b-component-interface-dummy/index.js", + "declaration": { + "name": "b-component-interface-dummy", + "parent": "i-block", + "dependencies": [], + "libs": [] + }, + "name": "b-component-interface-dummy", + "parent": "i-block", + "dependencies": [], + "libs": [], + "resolvedLibs": { + "%data": "%data:Set", + "%data:Set": [] + }, + "resolvedOwnLibs": { + "%data": "%data:Set", + "%data:Set": [] + }, + "type": "block", + "mixin": false, + "logic": "node_modules/@v4fire/client/src/core/component/interface/component/test/b-component-interface-dummy/b-component-interface-dummy.ts", + "styles": [ + "node_modules/@v4fire/client/src/core/component/interface/component/test/b-component-interface-dummy/b-component-interface-dummy.styl" + ], + "tpl": "node_modules/@v4fire/client/src/core/component/interface/component/test/b-component-interface-dummy/b-component-interface-dummy.ss", + "etpl": null + } + ], [ "b-components-actions", { @@ -1247,6 +1287,38 @@ "etpl": null } ], + [ + "b-scroll-element-dummy", + { + "index": "node_modules/@v4fire/client/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/index.js", + "declaration": { + "name": "b-scroll-element-dummy", + "parent": "b-dummy", + "dependencies": [], + "libs": [] + }, + "name": "b-scroll-element-dummy", + "parent": "b-dummy", + "dependencies": [], + "libs": [], + "resolvedLibs": { + "%data": "%data:Set", + "%data:Set": [] + }, + "resolvedOwnLibs": { + "%data": "%data:Set", + "%data:Set": [] + }, + "type": "block", + "mixin": false, + "logic": "node_modules/@v4fire/client/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/b-scroll-element-dummy.ts", + "styles": [ + "node_modules/@v4fire/client/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/b-scroll-element-dummy.styl" + ], + "tpl": "node_modules/@v4fire/client/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/b-scroll-element-dummy.ss", + "etpl": null + } + ], [ "b-select", { diff --git a/packages/devtools-extension/src/pages/p-components/p-components.ts b/packages/devtools-extension/src/pages/p-components/p-components.ts index c735315..469a6ce 100644 --- a/packages/devtools-extension/src/pages/p-components/p-components.ts +++ b/packages/devtools-extension/src/pages/p-components/p-components.ts @@ -39,17 +39,18 @@ function evalComponentsTree(): Item[] { let minRenderCounter = Number.MAX_SAFE_INTEGER; nodes.forEach(({component}) => { - const {$renderCounter} = component.unsafe; + const renderCounter = getRenderCounter(component); - if ($renderCounter < minRenderCounter) { - minRenderCounter = $renderCounter; + if (renderCounter < minRenderCounter) { + minRenderCounter = renderCounter; } }); const map = new Map(); const createDescriptor = (component: iBlock) => { - const {meta, $renderCounter} = component.unsafe; + const {meta} = component.unsafe; + const renderCounter = getRenderCounter(component); const descriptor: Item = { value: component.componentId, @@ -58,9 +59,9 @@ function evalComponentsTree(): Item[] { // Specific props componentName: meta.componentName, - renderCounterProp: $renderCounter, + renderCounterProp: renderCounter, isFunctionalProp: component.isFunctional, - showWarning: $renderCounter > minRenderCounter + showWarning: renderCounter > minRenderCounter }; return descriptor; @@ -112,4 +113,14 @@ function evalComponentsTree(): Item[] { const root = map.values().next().value; return root != null ? [root] : []; + + /** + * Get render counter of the component with support for the v4fire@3.0.0 + * @param component + */ + function getRenderCounter(component: iBlock): number { + const {$renderCounter, renderCounter} = <{$renderCounter?: number; renderCounter?: number}>component.unsafe; + + return $renderCounter ?? renderCounter ?? 0; + } }