From c2dfa86702b68ab5ad2e2a3f40c80bce78f96822 Mon Sep 17 00:00:00 2001 From: mgechev Date: Fri, 6 Mar 2020 18:12:29 -0800 Subject: [PATCH] refactor(devtools): use internal ivy data structures for extracting component tree --- package.json | 2 +- .../src/lib/component-tree.ts | 54 ++--------------- .../src/lib/lview-transform.ts | 59 +++++++++++++++++++ yarn.lock | 8 +-- 4 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 projects/ng-devtools-backend/src/lib/lview-transform.ts diff --git a/package.json b/package.json index 29b8c95fadf..d1fe40864b1 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "ts-node": "~8.3.0", "tsickle": "^0.35.0", "tslint": "~5.18.0", - "typescript": "~3.6.4", + "typescript": "~3.7.0", "wait-on": "^4.0.0" } } diff --git a/projects/ng-devtools-backend/src/lib/component-tree.ts b/projects/ng-devtools-backend/src/lib/component-tree.ts index 01ab75bd576..6c0b95b8403 100644 --- a/projects/ng-devtools-backend/src/lib/component-tree.ts +++ b/projects/ng-devtools-backend/src/lib/component-tree.ts @@ -10,6 +10,7 @@ import { import { getComponentName } from './highlighter'; import { DebuggingAPI } from './interfaces'; import { IndexedNode } from './observer/identity-tracker'; +import { buildDirectiveTree } from './lview-transform'; const ngDebug = (window as any).ng; @@ -63,56 +64,9 @@ export const getLatestComponentState = (query: ComponentExplorerViewQuery): Dire return result; }; -export const getDirectiveForest = (root: HTMLElement, ngd: DebuggingAPI): ComponentTreeNode[] => - buildDirectiveForest(root, { element: '__ROOT__', component: null, directives: [], children: [] }, ngd); - -const buildDirectiveForest = ( - node: Node, - tree: ComponentTreeNode | undefined, - ngd: DebuggingAPI -): ComponentTreeNode[] => { - if (!node) { - return [tree]; - } - let dirs = []; - if (tree.element !== '__ROOT__') { - // Need to make sure we're in a component tree - // otherwise, ngd.getDirectives will throw without - // a root node. - try { - dirs = ngd.getDirectives(node) || []; - } catch (e) {} - } - const cmp = node instanceof HTMLElement && ngd.getComponent(node); - if (!cmp && !dirs.length) { - Array.from(node.childNodes).forEach(c => buildDirectiveForest(c, tree, ngd)); - return tree.children; - } - const current: ComponentTreeNode = { - element: node.constructor.name, - directives: dirs.map(dir => { - return { - instance: dir, - name: getComponentName(dir), - } as DirectiveInstanceType; - }), - component: null, - children: [], - nativeElement: node, - }; - - const name = node instanceof HTMLElement ? node.tagName.toLowerCase() : node.nodeName.toLowerCase(); - if (cmp) { - current.component = { - instance: cmp, - name, - }; - } else { - current.element = name; - } - tree.children.push(current); - Array.from(node.childNodes).forEach(c => buildDirectiveForest(c, current, ngd)); - return tree.children; +export const getDirectiveForest = (root: HTMLElement, ngd: DebuggingAPI): ComponentTreeNode[] => { + const roots = Array.from(root.querySelectorAll('[ng-version]')).map(el => ngd.getComponent(el).__ngContext__); + return Array.prototype.concat.apply([], roots.map(buildDirectiveTree)); }; // Based on an ElementID we return a specific component node. diff --git a/projects/ng-devtools-backend/src/lib/lview-transform.ts b/projects/ng-devtools-backend/src/lib/lview-transform.ts new file mode 100644 index 00000000000..f0faebe81e8 --- /dev/null +++ b/projects/ng-devtools-backend/src/lib/lview-transform.ts @@ -0,0 +1,59 @@ +import { ComponentTreeNode } from './component-tree'; + +const HEADER_OFFSET = 19; +const TYPE = 1; +const ELEMENT = 0; +const LVIEW_TVIEW = 1; + +export function isLContainer(value: any) { + return Array.isArray(value) && value[TYPE] === true; +} + +const getNode = (lView: any, data: any, idx: number): ComponentTreeNode => { + const directives = []; + let component = null; + const tNode = data[idx]; + for (let i = tNode.directiveStart; i < tNode.directiveEnd; i++) { + const dir = lView[i]; + const dirMeta = data[i]; + if (dirMeta && dirMeta.template) { + component = { + name: dir.constructor.name, + instance: dir, + }; + } else if (dirMeta) { + directives.push({ + name: dir.constructor.name, + instance: dir, + }); + } + } + return { + element: (lView[idx][ELEMENT].tagName || lView[idx][ELEMENT].nodeName).toLowerCase(), + nativeElement: lView[idx][ELEMENT], + directives, + component, + children: [], + }; +}; + +const extractNodes = (lViewOrLContainer: any, nodes = []): ComponentTreeNode[] => { + if (isLContainer(lViewOrLContainer)) { + for (let i = 9; i < lViewOrLContainer.length; i++) { + extractNodes(lViewOrLContainer[i], nodes); + } + return nodes; + } + const lView = lViewOrLContainer; + const tView = lView[LVIEW_TVIEW]; + for (let i = HEADER_OFFSET; i < lView.length; i++) { + if (lView[i] && lView[i][ELEMENT] instanceof Node) { + const node = getNode(lView, tView.data, i); + nodes.push(node); + extractNodes(lView[i], node.children); + } + } + return nodes; +}; + +export const buildDirectiveTree = (lView: any) => extractNodes(lView); diff --git a/yarn.lock b/yarn.lock index 61fc533c295..5f71ddb2ae1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10122,10 +10122,10 @@ typescript@3.6.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d" integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg== -typescript@~3.6.4: - version "3.6.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.5.tgz#dae20114a7b4ff4bd642db9c8c699f2953e8bbdb" - integrity sha512-BEjlc0Z06ORZKbtcxGrIvvwYs5hAnuo6TKdNFL55frVDlB+na3z5bsLhFaIxmT+dPWgBIjMo6aNnTOgHHmHgiQ== +typescript@~3.7.0: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== ultron@~1.1.0: version "1.1.1"