diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts index bad8698ce5f..bab8993f057 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts @@ -267,6 +267,7 @@ const insertElementProfile = ( let lastFrame: ElementProfile = { children: [], directives: [], + type: 'element', }; if (frames[lastIdx]) { lastFrame = frames[lastIdx]; @@ -290,6 +291,8 @@ const prepareInitialFrame = (source: string, duration: number) => { position = directiveForestHooks.getDirectivePosition(node.component.instance); } else if (node.directives[0]) { position = directiveForestHooks.getDirectivePosition(node.directives[0].instance); + } else if (node.defer) { + position = directiveForestHooks.getDirectivePosition(node.defer); } if (position === undefined) { @@ -313,9 +316,10 @@ const prepareInitialFrame = (source: string, duration: number) => { name: getDirectiveName(node.component.instance), }); } - const result = { + const result: ElementProfile = { children: [], directives, + type: node.defer ? 'defer' : 'element', }; children[position[position.length - 1]] = result; node.children.forEach((n) => traverse(n, result.children)); diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts index 6b7089d16fa..cfe43a84845 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts @@ -96,6 +96,9 @@ export class IdentityTracker { this.isComponent.set(dir.instance, false); this._indexNode(dir.instance, node.position, newNodes); }); + if (node.defer) { + this._indexNode(node.defer, node.position, newNodes); + } node.children.forEach((child) => this._index(child, parent, newNodes, allNodes)); } @@ -124,21 +127,12 @@ const indexTree = { const position = parentPosition.concat([idx]); - - // Not every node represents a DOM element (ex @defer blocks), we shouldn't account for them - const children: IndexedNode[] = []; - node.children.forEach((n, i) => { - if (n.nativeElement) { - children.push(indexTree(n, i, position)); - } - }); - return { position, element: node.element, component: node.component, directives: node.directives.map((d) => ({position, ...d})), - children, + children: node.children.map((n, i) => indexTree(n, i, position)), nativeElement: node.nativeElement, hydration: node.hydration, defer: node.defer, @@ -147,5 +141,4 @@ const indexTree = >( forest: T[], - // Not every node represents a DOM element (ex @defer blocks), we shouldn't account for them -): IndexedNode[] => forest.filter((n) => n.nativeElement).map((n, i) => indexTree(n, i)); +): IndexedNode[] => forest.map((n, i) => indexTree(n, i)); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts index 40ea39fbb9f..17fa2193a22 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/flamegraph-formatter/flamegraph-formatter.ts @@ -38,6 +38,7 @@ export class FlamegraphFormatter extends RecordFormatter { original: { children: [], directives: [], + type: 'element', }, }; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.spec.ts index 5eb4013477b..b7e19ab5bc3 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ProfilerFrame} from 'protocol'; +import {ElementProfile, ProfilerFrame} from 'protocol'; import {mergeFrames} from './frame-merger'; @@ -16,7 +16,7 @@ describe('mergeFrames', () => { }); it('should work with a single frame', () => { - const frame = { + const frame: ProfilerFrame = { directives: [ { children: [], @@ -29,6 +29,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], duration: 5, @@ -43,7 +44,7 @@ describe('mergeFrames', () => { }); it('should merge frames when nesting matches', () => { - const frame = { + const frame: ProfilerFrame = { directives: [ { children: [], @@ -57,6 +58,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], duration: 5, @@ -78,6 +80,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], duration: 10, @@ -86,7 +89,7 @@ describe('mergeFrames', () => { }); it('should merge frames when nesting does not match', () => { - const frame = { + const frame: ProfilerFrame = { directives: [ { children: [], @@ -100,6 +103,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], duration: 5, @@ -122,6 +126,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], directives: [ @@ -134,6 +139,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], duration: 5, @@ -157,6 +163,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], directives: [ @@ -169,6 +176,7 @@ describe('mergeFrames', () => { name: 'Foo', }, ], + type: 'element', }, ], duration: 10, diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.ts index fbba10d2fac..10af236c62a 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/frame-merger.ts @@ -32,6 +32,7 @@ const mergeDirectives = (mergeIn: ElementProfile[], second: ElementProfile[]) => mergeIn[i] = { children: [], directives: [], + type: 'element', }; } second[i].directives.forEach((d, idx) => { diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter-spec-constants.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter-spec-constants.ts index 176a5f9263e..7a4b21e9dbd 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter-spec-constants.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter-spec-constants.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {ElementProfile} from 'protocol'; import {FlamegraphNode} from './flamegraph-formatter'; export const SIMPLE_RECORD = [ @@ -33,6 +34,7 @@ export const SIMPLE_RECORD = [ changeDetected: true, }, ], + type: 'element' as ElementProfile['type'], }, ], directives: [ @@ -46,6 +48,7 @@ export const SIMPLE_RECORD = [ changeDetection: 0, }, ], + type: 'element' as ElementProfile['type'], }, ]; export const SIMPLE_FORMATTED_FLAMEGRAPH_RECORD = [ @@ -84,7 +87,7 @@ export const SIMPLE_FORMATTED_TREE_MAP_RECORD = [ original: SIMPLE_RECORD[0], }), ]; -export const NESTED_RECORD = [ +export const NESTED_RECORD: ElementProfile[] = [ { children: [ { @@ -121,6 +124,7 @@ export const NESTED_RECORD = [ name: 'TodoComponent', }, ], + type: 'element', }, { children: [], @@ -142,6 +146,7 @@ export const NESTED_RECORD = [ name: 'TodoComponent', }, ], + type: 'element', }, { children: [], @@ -163,6 +168,7 @@ export const NESTED_RECORD = [ name: 'TodoComponent', }, ], + type: 'element', }, ], directives: [ @@ -175,6 +181,7 @@ export const NESTED_RECORD = [ changeDetection: 0, }, ], + type: 'element', }, ], directives: [ @@ -187,6 +194,7 @@ export const NESTED_RECORD = [ name: 'TodosComponent', }, ], + type: 'element', }, ], directives: [ @@ -199,6 +207,7 @@ export const NESTED_RECORD = [ changeDetection: 0, }, ], + type: 'element', }, ], directives: [ @@ -211,6 +220,7 @@ export const NESTED_RECORD = [ name: 'AppComponent', }, ], + type: 'element', }, ], directives: [ @@ -223,6 +233,7 @@ export const NESTED_RECORD = [ changeDetection: 0, }, ], + type: 'element', }, { children: [], @@ -236,6 +247,7 @@ export const NESTED_RECORD = [ name: 'HeavyComponent', }, ], + type: 'element', }, ], directives: [ @@ -248,6 +260,7 @@ export const NESTED_RECORD = [ name: 'DemoAppComponent', }, ], + type: 'element', }, ], directives: [ @@ -260,6 +273,7 @@ export const NESTED_RECORD = [ changeDetection: 0, }, ], + type: 'element', }, ], directives: [ @@ -272,6 +286,7 @@ export const NESTED_RECORD = [ name: 'AppComponent', }, ], + type: 'element', }, { children: [], @@ -285,6 +300,7 @@ export const NESTED_RECORD = [ name: 'ZippyComponent', }, ], + type: 'element', }, ]; export const NESTED_FORMATTED_FLAMEGRAPH_RECORD: FlamegraphNode[] = [ @@ -425,6 +441,7 @@ export const NESTED_FORMATTED_FLAMEGRAPH_RECORD: FlamegraphNode[] = [ name: 'ZippyComponent', }, ], + type: 'element', }, }, ]; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.spec.ts index c8aaee00341..44edc6d810a 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.spec.ts @@ -106,6 +106,7 @@ describe('getLabel cases', () => { name: 'AppComponent', }, ], + type: 'element', }; expect(formatter.getLabel(element)).toBe('AppComponent'); }); @@ -123,6 +124,7 @@ describe('getLabel cases', () => { changeDetection: 0, }, ], + type: 'element', }; expect(formatter.getLabel(element)).toBe('[RouterOutlet]'); }); @@ -148,6 +150,7 @@ describe('getLabel cases', () => { name: 'TodoComponent', }, ], + type: 'element', }; expect(formatter.getLabel(element)).toBe('TodoComponent[TooltipDirective]'); }); @@ -181,6 +184,7 @@ describe('getLabel cases', () => { name: 'TodoComponent', }, ], + type: 'element', }; expect(formatter.getLabel(element)).toBe('TodoComponent[TooltipDirective, RandomDirective]'); }); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.ts index 6c03b5a8d8e..f43bf43f27d 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler/timeline/record-formatter/record-formatter.ts @@ -29,6 +29,10 @@ export abstract class RecordFormatter { abstract addFrame(nodes: T | T[], elements: ElementProfile[]): number | void; getLabel(element: ElementProfile): string { + if (element.type === 'defer') { + return '@defer'; + } + const name = element.directives .filter((d) => d.isComponent) .map((c) => c.name) diff --git a/devtools/projects/protocol/src/lib/messages.ts b/devtools/projects/protocol/src/lib/messages.ts index 6ca3aa7f24e..0ee52d7ea3d 100644 --- a/devtools/projects/protocol/src/lib/messages.ts +++ b/devtools/projects/protocol/src/lib/messages.ts @@ -250,6 +250,7 @@ export interface DirectiveProfile { export interface ElementProfile { directives: DirectiveProfile[]; children: ElementProfile[]; + type: 'defer' | 'element'; } export interface ProfilerFrame {