Skip to content

Commit 6cce056

Browse files
crisbetopkozlowski-opensource
authored andcommitted
refactor(compiler): allow different kinds of directive matchers to be passed to binder (angular#60952)
Updates the target binder to allow either a selector-based or selectorless matcher to be passed in. This will allow us to skip some of the overhead when matching directives to nodes. PR Close angular#60952
1 parent 0859a99 commit 6cce056

File tree

11 files changed

+54
-32
lines changed

11 files changed

+54
-32
lines changed

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,8 @@ export class ComponentDecoratorHandler
10471047
}
10481048
}
10491049
}
1050+
1051+
// TODO(crisbeto): implement for selectorless.
10501052
const binder = new R3TargetBinder(matcher);
10511053
const boundTemplate = binder.bind({template: analysis.template.diagNodes});
10521054

@@ -1076,6 +1078,7 @@ export class ComponentDecoratorHandler
10761078
return;
10771079
}
10781080

1081+
// TODO(crisbeto): implement for selectorless.
10791082
const binder = new R3TargetBinder<TypeCheckableDirectiveMeta>(scope.matcher);
10801083
const templateContext: TemplateContext = {
10811084
nodes: meta.template.diagNodes,
@@ -2266,6 +2269,8 @@ function createTargetBinder(dependencies: Array<PipeMeta | DirectiveMeta | NgMod
22662269
matcher.addSelectables(CssSelector.parse(dep.selector), [dep]);
22672270
}
22682271
}
2272+
2273+
// TODO(crisbeto): implement for selectorless.
22692274
return new R3TargetBinder(matcher);
22702275
}
22712276

packages/compiler/src/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ export {createCssSelectorFromNode} from './render3/view/util';
245245
export * from './resource_loader';
246246
export * from './schema/dom_element_schema_registry';
247247
export * from './schema/element_schema_registry';
248-
export * from './selector';
248+
export * from './directive_matching';
249249
export {Version} from './util';
250250
export * from './version';
251251
export {outputAst};

packages/compiler/src/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// This is important to prevent a build cycle, as @angular/core needs to
1313
// be compiled with the compiler.
1414

15-
import {CssSelector} from './selector';
15+
import {CssSelector} from './directive_matching';
1616

1717
// Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not
1818
// explicitly set.

packages/compiler/src/selector.ts renamed to packages/compiler/src/directive_matching.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,11 @@ export class SelectorContext<T = any> {
472472
return result;
473473
}
474474
}
475+
476+
export class SelectorlessMatcher<T = unknown> {
477+
constructor(private registry: Map<string, T>) {}
478+
479+
match(name: string): T | null {
480+
return this.registry.get(name) ?? null;
481+
}
482+
}

packages/compiler/src/jit_compiler_facade.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ import {R3TargetBinder} from './render3/view/t2_binder';
104104
import {makeBindingParser, parseTemplate} from './render3/view/template';
105105
import {ResourceLoader} from './resource_loader';
106106
import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
107-
import {SelectorMatcher} from './selector';
107+
import {SelectorMatcher} from './directive_matching';
108108
import {getJitStandaloneDefaultForVersion} from './util';
109109

110110
export class CompilerFacadeImpl implements CompilerFacade {

packages/compiler/src/render3/view/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {ConstantPool} from '../../constant_pool';
1010
import * as core from '../../core';
1111
import * as o from '../../output/output_ast';
1212
import {ParseError, ParseSourceSpan} from '../../parse_util';
13-
import {CssSelector} from '../../selector';
13+
import {CssSelector} from '../../directive_matching';
1414
import {ShadowCss} from '../../shadow_css';
1515
import {CompilationJobKind} from '../../template/pipeline/src/compilation';
1616
import {emitHostBindingFunction, emitTemplateFn, transform} from '../../template/pipeline/src/emit';

packages/compiler/src/render3/view/t2_binder.ts

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
SafePropertyRead,
1717
ThisReceiver,
1818
} from '../../expression_parser/ast';
19-
import {CssSelector, SelectorMatcher} from '../../selector';
19+
import {CssSelector, SelectorlessMatcher, SelectorMatcher} from '../../directive_matching';
2020
import {
2121
BoundAttribute,
2222
BoundEvent,
@@ -119,7 +119,7 @@ type DeferBlockScopes = [DeferredBlock, Scope][];
119119
* where a host component (that owns the template) is located
120120
*/
121121
export function findMatchingDirectivesAndPipes(template: string, directiveSelectors: string[]) {
122-
const matcher = new SelectorMatcher<unknown[]>();
122+
const matcher = new SelectorMatcher<DirectiveMeta[]>();
123123
for (const selector of directiveSelectors) {
124124
// Create a fake directive instance to account for the logic inside
125125
// of the `R3TargetBinder` class (which invokes the `hasBindingPropertyName`
@@ -137,11 +137,11 @@ export function findMatchingDirectivesAndPipes(template: string, directiveSelect
137137
return false;
138138
},
139139
},
140-
};
140+
} as unknown as DirectiveMeta;
141141
matcher.addSelectables(CssSelector.parse(selector), [fakeDirective]);
142142
}
143143
const parsedTemplate = parseTemplate(template, '' /* templateUrl */);
144-
const binder = new R3TargetBinder(matcher as any);
144+
const binder = new R3TargetBinder(matcher);
145145
const bound = binder.bind({template: parsedTemplate.nodes});
146146

147147
const eagerDirectiveSelectors = bound.getEagerlyUsedDirectives().map((dir) => dir.selector!);
@@ -159,13 +159,18 @@ export function findMatchingDirectivesAndPipes(template: string, directiveSelect
159159
};
160160
}
161161

162+
/** Object used to match template nodes to directives. */
163+
export type DirectiveMatcher<DirectiveT extends DirectiveMeta> =
164+
| SelectorMatcher<DirectiveT[]>
165+
| SelectorlessMatcher<DirectiveT[]>;
166+
162167
/**
163168
* Processes `Target`s with a given set of directives and performs a binding operation, which
164169
* returns an object similar to TypeScript's `ts.TypeChecker` that contains knowledge about the
165170
* target.
166171
*/
167172
export class R3TargetBinder<DirectiveT extends DirectiveMeta> implements TargetBinder<DirectiveT> {
168-
constructor(private directiveMatcher: SelectorMatcher<DirectiveT[]>) {}
173+
constructor(private directiveMatcher: DirectiveMatcher<DirectiveT>) {}
169174

170175
/**
171176
* Perform a binding operation on the given `Target` and return a `BoundTarget` which contains
@@ -497,7 +502,7 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
497502
private isInDeferBlock = false;
498503

499504
private constructor(
500-
private matcher: SelectorMatcher<DirectiveT[]>,
505+
private directiveMatcher: DirectiveMatcher<DirectiveT>,
501506
private directives: MatchedDirectives<DirectiveT>,
502507
private eagerDirectives: DirectiveT[],
503508
private bindings: BindingsMap<DirectiveT>,
@@ -518,14 +523,14 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
518523
*/
519524
static apply<DirectiveT extends DirectiveMeta>(
520525
template: Node[],
521-
selectorMatcher: SelectorMatcher<DirectiveT[]>,
526+
directiveMatcher: DirectiveMatcher<DirectiveT>,
522527
directives: MatchedDirectives<DirectiveT>,
523528
eagerDirectives: DirectiveT[],
524529
bindings: BindingsMap<DirectiveT>,
525530
references: ReferenceMap<DirectiveT>,
526531
): void {
527532
const matcher = new DirectiveBinder(
528-
selectorMatcher,
533+
directiveMatcher,
529534
directives,
530535
eagerDirectives,
531536
bindings,
@@ -547,23 +552,23 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
547552
}
548553

549554
visitElementOrTemplate(node: Element | Template): void {
550-
// First, determine the HTML shape of the node for the purpose of directive matching.
551-
// Do this by building up a `CssSelector` for the node.
552-
const cssSelector = createCssSelectorFromNode(node);
555+
const directives: DirectiveT[] = [];
553556

554-
// TODO(crisbeto): account for selectorless directives here.
555-
if (node.directives.length > 0) {
556-
throw new Error('TODO');
557-
}
557+
if (this.directiveMatcher instanceof SelectorMatcher) {
558+
// First, determine the HTML shape of the node for the purpose of directive matching.
559+
// Do this by building up a `CssSelector` for the node.
560+
const cssSelector = createCssSelectorFromNode(node);
558561

559-
// Next, use the `SelectorMatcher` to get the list of directives on the node.
560-
const directives: DirectiveT[] = [];
561-
this.matcher.match(cssSelector, (_selector, results) => directives.push(...results));
562-
if (directives.length > 0) {
563-
this.directives.set(node, directives);
564-
if (!this.isInDeferBlock) {
565-
this.eagerDirectives.push(...directives);
562+
this.directiveMatcher.match(cssSelector, (_selector, results) => directives.push(...results));
563+
564+
if (directives.length > 0) {
565+
this.directives.set(node, directives);
566+
if (!this.isInDeferBlock) {
567+
this.eagerDirectives.push(...directives);
568+
}
566569
}
570+
} else {
571+
throw new Error('TODO');
567572
}
568573

569574
// Resolve any references that are created on this node.

packages/compiler/src/render3/view/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {InputFlags} from '../../core';
1010
import {BindingType} from '../../expression_parser/ast';
1111
import {splitNsName} from '../../ml_parser/tags';
1212
import * as o from '../../output/output_ast';
13-
import {CssSelector} from '../../selector';
13+
import {CssSelector} from '../../directive_matching';
1414
import * as t from '../r3_ast';
1515

1616
import {isI18nAttribute} from './i18n/util';

packages/compiler/src/template_parser/binding_parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {mergeNsAndName} from '../ml_parser/tags';
3737
import {InterpolatedAttributeToken, InterpolatedTextToken} from '../ml_parser/tokens';
3838
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
3939
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
40-
import {CssSelector} from '../selector';
40+
import {CssSelector} from '../directive_matching';
4141
import {splitAtColon, splitAtPeriod} from '../util';
4242

4343
const PROPERTY_PARTS_SEPARATOR = '.';

packages/compiler/test/render3/view/binding_spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
import * as e from '../../../src/expression_parser/ast';
1010
import * as a from '../../../src/render3/r3_ast';
1111
import {DirectiveMeta, InputOutputPropertySet} from '../../../src/render3/view/t2_api';
12-
import {findMatchingDirectivesAndPipes, R3TargetBinder} from '../../../src/render3/view/t2_binder';
13-
import {parseTemplate} from '../../../src/render3/view/template';
14-
import {CssSelector, SelectorMatcher} from '../../../src/selector';
12+
import {
13+
DirectiveMatcher,
14+
findMatchingDirectivesAndPipes,
15+
R3TargetBinder,
16+
} from '../../../src/render3/view/t2_binder';
17+
import {parseTemplate, ParseTemplateOptions} from '../../../src/render3/view/template';
18+
import {CssSelector, SelectorMatcher} from '../../../src/directive_matching';
1519

1620
import {findExpression} from './util';
1721

0 commit comments

Comments
 (0)