Skip to content

Commit

Permalink
WIP: refactor(compiler): create util method to detect metching direct…
Browse files Browse the repository at this point in the history
…ives and pipes

TODO: add more details.
  • Loading branch information
AndrewKushnir committed Aug 19, 2024
1 parent baa1254 commit 7f6e2bb
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 2 deletions.
65 changes: 64 additions & 1 deletion packages/compiler/src/render3/view/t2_binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
SafePropertyRead,
ThisReceiver,
} from '../../expression_parser/ast';
import {SelectorMatcher} from '../../selector';
import {CssSelector, SelectorMatcher} from '../../selector';
import {
BoundAttribute,
BoundEvent,
Expand Down Expand Up @@ -59,8 +59,71 @@ 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 satisfy the `R3TargetBinder` contract.
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
Expand Down
28 changes: 27 additions & 1 deletion packages/compiler/test/render3/view/binding_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -140,6 +140,32 @@ 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'],
},
});
},
);
});

describe('t2 binding', () => {
it('should bind a simple template', () => {
const template = parseTemplate('<div *ngFor="let item of items">{{item.name}}</div>', '', {});
Expand Down

0 comments on commit 7f6e2bb

Please sign in to comment.