Skip to content

Commit 6473214

Browse files
crisbetokirjs
authored andcommitted
refactor(core): avoid memory allocations if there are no host directives (#60075)
Currently the host directive logic disassembles and re-assembles the array of directive matches, in case there are host directives which in most cases produces an identical array. These changes add some logic so that we only need to allocate the additional memory if we actually need it. PR Close #60075
1 parent f23946a commit 6473214

File tree

1 file changed

+51
-32
lines changed

1 file changed

+51
-32
lines changed

packages/core/src/render3/view/directives.ts

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ export type DirectiveMatcherStrategy = (
5252
tNode: TElementNode | TContainerNode | TElementContainerNode,
5353
) => DirectiveDef<unknown>[] | null;
5454

55+
/** Data produced after host directives are resolved for a node. */
56+
type HostDirectiveResolution = [
57+
matches: DirectiveDef<unknown>[],
58+
hostDirectiveDefs: HostDirectiveDefs | null,
59+
hostDirectiveRanges: HostDirectiveRanges | null,
60+
];
61+
5562
/**
5663
* Map that tracks a selector-matched directive to the range within which its host directives
5764
* are declared. Host directives for a specific directive are always contiguous within the runtime.
@@ -76,8 +83,16 @@ export function resolveDirectives(
7683
const matchedDirectiveDefs = directiveMatcher(tView, tNode);
7784

7885
if (matchedDirectiveDefs !== null) {
79-
const [directiveDefs, hostDirectiveDefs, hostDirectiveRanges] =
80-
resolveHostDirectives(matchedDirectiveDefs);
86+
let directiveDefs: DirectiveDef<unknown>[];
87+
let hostDirectiveDefs: HostDirectiveDefs | null = null;
88+
let hostDirectiveRanges: HostDirectiveRanges | null = null;
89+
const hostDirectiveResolution = resolveHostDirectives(matchedDirectiveDefs);
90+
91+
if (hostDirectiveResolution === null) {
92+
directiveDefs = matchedDirectiveDefs;
93+
} else {
94+
[directiveDefs, hostDirectiveDefs, hostDirectiveRanges] = hostDirectiveResolution;
95+
}
8196

8297
initializeDirectives(
8398
tView,
@@ -116,15 +131,28 @@ function cacheMatchingLocalNames(
116131
}
117132
}
118133

119-
function resolveHostDirectives(
120-
matches: DirectiveDef<unknown>[],
121-
): [
122-
matches: DirectiveDef<unknown>[],
123-
hostDirectiveDefs: HostDirectiveDefs | null,
124-
hostDirectiveRanges: HostDirectiveRanges | null,
125-
] {
126-
const allDirectiveDefs: DirectiveDef<unknown>[] = [];
127-
const hasComponent = matches.length > 0 && isComponentDef(matches[0]);
134+
function resolveHostDirectives(matches: DirectiveDef<unknown>[]): HostDirectiveResolution | null {
135+
let componentDef: ComponentDef<unknown> | null = null;
136+
let hasHostDirectives = false;
137+
138+
for (let i = 0; i < matches.length; i++) {
139+
const def = matches[i];
140+
141+
if (i === 0 && isComponentDef(def)) {
142+
componentDef = def;
143+
}
144+
145+
if (def.findHostDirectiveDefs !== null) {
146+
hasHostDirectives = true;
147+
break;
148+
}
149+
}
150+
151+
if (!hasHostDirectives) {
152+
return null;
153+
}
154+
155+
let allDirectiveDefs: DirectiveDef<unknown>[] | null = null;
128156
let hostDirectiveDefs: HostDirectiveDefs | null = null;
129157
let hostDirectiveRanges: HostDirectiveRanges | null = null;
130158

@@ -138,37 +166,28 @@ function resolveHostDirectives(
138166
// 2. Selector-matched component.
139167
// 3. Host directives belonging to selector-matched directives.
140168
// 4. Selector-matched dir
141-
if (hasComponent) {
142-
const def = matches[0];
169+
for (const def of matches) {
143170
if (def.findHostDirectiveDefs !== null) {
144-
hostDirectiveRanges ??= new Map();
171+
allDirectiveDefs ??= [];
145172
hostDirectiveDefs ??= new Map();
146-
resolveHostDirectivesForDef(def, allDirectiveDefs, hostDirectiveRanges, hostDirectiveDefs);
147-
}
148-
allDirectiveDefs.push(def);
149-
}
150-
151-
// If there's a component, we already processed it above so we can skip it here.
152-
for (let i = hasComponent ? 1 : 0; i < matches.length; i++) {
153-
const def = matches[i];
154-
if (def.findHostDirectiveDefs !== null) {
155173
hostDirectiveRanges ??= new Map();
156-
hostDirectiveDefs ??= new Map();
157174
resolveHostDirectivesForDef(def, allDirectiveDefs, hostDirectiveRanges, hostDirectiveDefs);
158175
}
159-
}
160176

161-
if (hasComponent) {
162-
allDirectiveDefs.push(...matches.slice(1));
163-
} else {
164-
allDirectiveDefs.push(...matches);
177+
// Component definition needs to be pushed early to maintain the correct ordering.
178+
if (def === componentDef) {
179+
allDirectiveDefs ??= [];
180+
allDirectiveDefs.push(def);
181+
}
165182
}
166183

167-
if (ngDevMode) {
168-
assertNoDuplicateDirectives(allDirectiveDefs);
184+
if (allDirectiveDefs !== null) {
185+
allDirectiveDefs.push(...(componentDef === null ? matches : matches.slice(1)));
186+
ngDevMode && assertNoDuplicateDirectives(allDirectiveDefs);
187+
return [allDirectiveDefs, hostDirectiveDefs, hostDirectiveRanges];
169188
}
170189

171-
return [allDirectiveDefs, hostDirectiveDefs, hostDirectiveRanges];
190+
return null;
172191
}
173192

174193
function resolveHostDirectivesForDef(

0 commit comments

Comments
 (0)