mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
perf(devtools): improve injector tree rendering performance
Introduce an equality function for transformed injector trees in order to omit redundant D3 tree visualization render cycles when the tree hasn't been changed.
This commit is contained in:
committed by
Kirill Cherkashin
parent
de43806a2e
commit
2d9e179188
+133
@@ -9,6 +9,8 @@
|
||||
import {DevToolsNode, SerializedInjector} from '../../../../../protocol';
|
||||
|
||||
import {
|
||||
areInjectorTreeNodesEqual,
|
||||
areInjectorTreesEqual,
|
||||
equalInjector,
|
||||
generateEdgeIdsFromNodeIds,
|
||||
getInjectorIdsToRootFromNode,
|
||||
@@ -11518,3 +11520,134 @@ describe('grabInjectorPathsFromDirectiveForest', () => {
|
||||
expect(grabInjectorPathsFromDirectiveForest(directiveForest)).toEqual(expectedInjectorPaths);
|
||||
});
|
||||
});
|
||||
|
||||
describe('areInjectorTreeNodesEqual', () => {
|
||||
const tree: InjectorTreeNode = {
|
||||
label: 'Foo',
|
||||
children: [],
|
||||
injector: {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
type: 'environment',
|
||||
},
|
||||
};
|
||||
|
||||
it('should return true if the nodes are equal', () => {
|
||||
const a = tree;
|
||||
const b = structuredClone(tree); // Ensure that the ref is different
|
||||
|
||||
expect(areInjectorTreeNodesEqual(a, b)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true if the nodes are equal, irrespective of the children', () => {
|
||||
const a = tree;
|
||||
const b = structuredClone(tree); // Ensure that the ref is different
|
||||
|
||||
b.children.push({
|
||||
label: 'Bar',
|
||||
children: [],
|
||||
injector: {
|
||||
id: 'bar',
|
||||
name: 'Bar',
|
||||
type: 'element',
|
||||
},
|
||||
});
|
||||
|
||||
expect(areInjectorTreeNodesEqual(a, b)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if the nodes are different', () => {
|
||||
const a = tree;
|
||||
const b = structuredClone(tree); // Ensure that the ref is different
|
||||
|
||||
b.injector.id = 'baz';
|
||||
|
||||
expect(areInjectorTreeNodesEqual(a, b)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('areInjectorTreesEqual', () => {
|
||||
const tree: InjectorTreeNode = {
|
||||
label: 'Foo',
|
||||
injector: {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
type: 'null',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
label: 'Bar',
|
||||
injector: {
|
||||
id: 'bar',
|
||||
name: 'Bar',
|
||||
type: 'environment',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
label: 'Baz',
|
||||
injector: {
|
||||
id: 'baz',
|
||||
name: 'Baz',
|
||||
type: 'environment',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
label: 'Qux',
|
||||
injector: {
|
||||
id: 'qux',
|
||||
name: 'Qux',
|
||||
type: 'element',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it('should return true when both null', () => {
|
||||
expect(areInjectorTreesEqual(null, null)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if one of the trees is null', () => {
|
||||
expect(areInjectorTreesEqual(tree, null)).toEqual(false);
|
||||
expect(areInjectorTreesEqual(null, tree)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true if the trees are equal', () => {
|
||||
const a = tree;
|
||||
const b = structuredClone(tree); // Ensure that the ref is different
|
||||
|
||||
expect(areInjectorTreesEqual(a, b)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if the trees are different', () => {
|
||||
const a = tree;
|
||||
|
||||
// Ensure that the refs are different
|
||||
const b = structuredClone(tree);
|
||||
const c = structuredClone(tree);
|
||||
const d = structuredClone(tree);
|
||||
|
||||
// Update an existing node
|
||||
b.children[0].label = 'Quux';
|
||||
expect(areInjectorTreesEqual(a, b)).toEqual(false);
|
||||
|
||||
// Delete a node
|
||||
c.children[1].children = [];
|
||||
expect(areInjectorTreesEqual(a, c)).toEqual(false);
|
||||
|
||||
// Add a node
|
||||
d.children[0].children.push({
|
||||
label: 'Quux',
|
||||
injector: {
|
||||
id: 'quux',
|
||||
name: 'Quux',
|
||||
type: 'hidden',
|
||||
},
|
||||
children: [],
|
||||
});
|
||||
expect(areInjectorTreesEqual(a, d)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
+48
@@ -296,3 +296,51 @@ export function d3InjectorTreeNodeModifier(d3Node: SvgD3Node<InjectorTreeNode>)
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns whether InjectorTreeNodes are equal (excl. children comparison). */
|
||||
export function areInjectorTreeNodesEqual(a: InjectorTreeNode, b: InjectorTreeNode): boolean {
|
||||
const isSameInjector = equalInjector(a?.injector, b?.injector);
|
||||
const isSameLabel = a?.label === b?.label;
|
||||
const isSameSubLabel = a?.subLabel === b?.subLabel;
|
||||
|
||||
return isSameInjector && isSameLabel && isSameSubLabel;
|
||||
}
|
||||
|
||||
/** Returns whether injector trees (InjectorTreeNodes with children) are equal. */
|
||||
export function areInjectorTreesEqual(
|
||||
a: InjectorTreeNode | null,
|
||||
b: InjectorTreeNode | null,
|
||||
): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
if ((a && !b) || (!a && b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stackA: InjectorTreeNode[] = [a!];
|
||||
const stackB: InjectorTreeNode[] = [b!];
|
||||
|
||||
while (stackA.length && stackB.length) {
|
||||
const aNode = stackA.pop()!;
|
||||
const bNode = stackB.pop()!;
|
||||
|
||||
const isDiffChildrenLength = aNode?.children.length !== bNode?.children.length;
|
||||
|
||||
if (!areInjectorTreeNodesEqual(aNode, bNode) || isDiffChildrenLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aNode?.children && bNode?.children) {
|
||||
for (let i = 0; i < aNode.children.length; i++) {
|
||||
const aChild = aNode.children[i];
|
||||
const bChild = bNode.children[i];
|
||||
|
||||
stackA.push(aChild);
|
||||
stackB.push(bChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+7
-2
@@ -30,6 +30,7 @@ import {TreeD3Node, TreeVisualizerConfig} from '../../shared/tree-visualizer/tre
|
||||
import {TreeVisualizerComponent} from '../../shared/tree-visualizer/tree-visualizer.component';
|
||||
import {InjectorProvidersComponent} from './injector-providers/injector-providers.component';
|
||||
import {
|
||||
areInjectorTreesEqual,
|
||||
d3InjectorTreeLinkModifier,
|
||||
d3InjectorTreeNodeModifier,
|
||||
filterOutAngularInjectors,
|
||||
@@ -103,8 +104,12 @@ export class InjectorTreeComponent {
|
||||
private hideInjectorsWithNoProviders = false;
|
||||
private hideFrameworkInjectors = false;
|
||||
|
||||
protected readonly elementInjectorTree = signal<InjectorTreeNode | null>(null);
|
||||
protected readonly environmentInjectorTree = signal<InjectorTreeNode | null>(null);
|
||||
protected readonly elementInjectorTree = signal<InjectorTreeNode | null>(null, {
|
||||
equal: areInjectorTreesEqual,
|
||||
});
|
||||
protected readonly environmentInjectorTree = signal<InjectorTreeNode | null>(null, {
|
||||
equal: areInjectorTreesEqual,
|
||||
});
|
||||
|
||||
protected readonly responsiveSplitConfig: ResponsiveSplitConfig = {
|
||||
defaultDirection: 'vertical',
|
||||
|
||||
Reference in New Issue
Block a user