mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(compiler): create an internal util method to detect matching directives and pipes (#57466)
This commit adds an internal util method that allows to detect: * which selectors are matching nodes in a template * which pipes are present in a template Both directives and pipes are split into 2 buckets: eagerly used and the ones that might potentially be defer-loaded. PR Close #57466
This commit is contained in:
committed by
Dylan Hunn
parent
aff6729398
commit
e75e61a637
@@ -16,7 +16,7 @@ import {
|
||||
SafePropertyRead,
|
||||
ThisReceiver,
|
||||
} from '../../expression_parser/ast';
|
||||
import {SelectorMatcher} from '../../selector';
|
||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||
import {
|
||||
BoundAttribute,
|
||||
BoundEvent,
|
||||
@@ -59,8 +59,73 @@ import {
|
||||
TargetBinder,
|
||||
TemplateEntity,
|
||||
} from './t2_api';
|
||||
import {parseTemplate} from './template';
|
||||
import {createCssSelectorFromNode} from './util';
|
||||
|
||||
/**
|
||||
* Computes a difference between full list (first argument) and
|
||||
* list of items that should be excluded from the full list (second
|
||||
* argument).
|
||||
*/
|
||||
function diff(fullList: string[], itemsToExclude: string[]): string[] {
|
||||
const exclude = new Set(itemsToExclude);
|
||||
return fullList.filter((item) => !exclude.has(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a template string and a set of available directive selectors,
|
||||
* computes a list of matching selectors and splits them into 2 buckets:
|
||||
* (1) eagerly used in a template and (2) directives used only in defer
|
||||
* blocks. Similarly, returns 2 lists of pipes (eager and deferrable).
|
||||
*
|
||||
* Note: deferrable directives selectors and pipes names used in `@defer`
|
||||
* blocks are **candidates** and API caller should make sure that:
|
||||
*
|
||||
* * A Component where a given template is defined is standalone
|
||||
* * Underlying dependency classes are also standalone
|
||||
* * Dependency class symbols are not eagerly used in a TS file
|
||||
* where a host component (that owns the template) is located
|
||||
*/
|
||||
export function findMatchingDirectivesAndPipes(template: string, directiveSelectors: string[]) {
|
||||
const matcher = new SelectorMatcher<unknown[]>();
|
||||
for (const selector of directiveSelectors) {
|
||||
// Create a fake directive instance to account for the logic inside
|
||||
// of the `R3TargetBinder` class (which invokes the `hasBindingPropertyName`
|
||||
// function internally).
|
||||
const fakeDirective = {
|
||||
selector,
|
||||
inputs: {
|
||||
hasBindingPropertyName() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
outputs: {
|
||||
hasBindingPropertyName() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
matcher.addSelectables(CssSelector.parse(selector), [fakeDirective]);
|
||||
}
|
||||
const parsedTemplate = parseTemplate(template, '' /* templateUrl */);
|
||||
const binder = new R3TargetBinder(matcher as any);
|
||||
const bound = binder.bind({template: parsedTemplate.nodes});
|
||||
|
||||
const eagerDirectiveSelectors = bound.getEagerlyUsedDirectives().map((dir) => dir.selector!);
|
||||
const allMatchedDirectiveSelectors = bound.getUsedDirectives().map((dir) => dir.selector!);
|
||||
const eagerPipes = bound.getEagerlyUsedPipes();
|
||||
return {
|
||||
directives: {
|
||||
regular: eagerDirectiveSelectors,
|
||||
deferCandidates: diff(allMatchedDirectiveSelectors, eagerDirectiveSelectors),
|
||||
},
|
||||
pipes: {
|
||||
regular: eagerPipes,
|
||||
deferCandidates: diff(bound.getUsedPipes(), eagerPipes),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes `Target`s with a given set of directives and performs a binding operation, which
|
||||
* returns an object similar to TypeScript's `ts.TypeChecker` that contains knowledge about the
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import * as e from '../../../src/expression_parser/ast';
|
||||
import * as a from '../../../src/render3/r3_ast';
|
||||
import {DirectiveMeta, InputOutputPropertySet} from '../../../src/render3/view/t2_api';
|
||||
import {R3TargetBinder} from '../../../src/render3/view/t2_binder';
|
||||
import {findMatchingDirectivesAndPipes, R3TargetBinder} from '../../../src/render3/view/t2_binder';
|
||||
import {parseTemplate} from '../../../src/render3/view/template';
|
||||
import {CssSelector, SelectorMatcher} from '../../../src/selector';
|
||||
|
||||
@@ -140,6 +140,74 @@ function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta[]> {
|
||||
return matcher;
|
||||
}
|
||||
|
||||
describe('findMatchingDirectivesAndPipes', () => {
|
||||
it('should match directives and detect pipes in eager and deferrable parts of a template', () => {
|
||||
const template = `
|
||||
<div [title]="abc | uppercase"></div>
|
||||
@defer {
|
||||
<my-defer-cmp [label]="abc | lowercase" />
|
||||
} @placeholder {}
|
||||
`;
|
||||
const directiveSelectors = ['[title]', 'my-defer-cmp', 'not-matching'];
|
||||
const result = findMatchingDirectivesAndPipes(template, directiveSelectors);
|
||||
expect(result).toEqual({
|
||||
directives: {
|
||||
regular: ['[title]'],
|
||||
deferCandidates: ['my-defer-cmp'],
|
||||
},
|
||||
pipes: {
|
||||
regular: ['uppercase'],
|
||||
deferCandidates: ['lowercase'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty directive list if no selectors are provided', () => {
|
||||
const template = `
|
||||
<div [title]="abc | uppercase"></div>
|
||||
@defer {
|
||||
<my-defer-cmp [label]="abc | lowercase" />
|
||||
} @placeholder {}
|
||||
`;
|
||||
const directiveSelectors: string[] = [];
|
||||
const result = findMatchingDirectivesAndPipes(template, directiveSelectors);
|
||||
expect(result).toEqual({
|
||||
directives: {
|
||||
regular: [],
|
||||
deferCandidates: [],
|
||||
},
|
||||
// Expect pipes to be present still.
|
||||
pipes: {
|
||||
regular: ['uppercase'],
|
||||
deferCandidates: ['lowercase'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a directive and a pipe only once (either as a regular or deferrable)', () => {
|
||||
const template = `
|
||||
<my-defer-cmp [label]="abc | lowercase" [title]="abc | uppercase" />
|
||||
@defer {
|
||||
<my-defer-cmp [label]="abc | lowercase" [title]="abc | uppercase" />
|
||||
} @placeholder {}
|
||||
`;
|
||||
const directiveSelectors = ['[title]', 'my-defer-cmp', 'not-matching'];
|
||||
const result = findMatchingDirectivesAndPipes(template, directiveSelectors);
|
||||
expect(result).toEqual({
|
||||
directives: {
|
||||
regular: ['my-defer-cmp', '[title]'],
|
||||
// All directives/components are used eagerly.
|
||||
deferCandidates: [],
|
||||
},
|
||||
pipes: {
|
||||
regular: ['lowercase', 'uppercase'],
|
||||
// All pipes are used eagerly.
|
||||
deferCandidates: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('t2 binding', () => {
|
||||
it('should bind a simple template', () => {
|
||||
const template = parseTemplate('<div *ngFor="let item of items">{{item.name}}</div>', '', {});
|
||||
|
||||
Reference in New Issue
Block a user