feat(compiler): integrate compiler with view engine (#14487)
Aspects: di, query, content projection Included refactoring: - use a number as query id - use a bloom filter for aggregating matched queries of nested elements - separate static vs dynamic queries Part of #14013
This commit is contained in:
@ -304,6 +304,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||
providers: CompileProviderMetadata[];
|
||||
viewProviders: CompileProviderMetadata[];
|
||||
queries: CompileQueryMetadata[];
|
||||
viewQueries: CompileQueryMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
changeDetection: ChangeDetectionStrategy;
|
||||
template: CompileTemplateSummary;
|
||||
@ -480,6 +481,7 @@ export class CompileDirectiveMetadata {
|
||||
providers: this.providers,
|
||||
viewProviders: this.viewProviders,
|
||||
queries: this.queries,
|
||||
viewQueries: this.viewQueries,
|
||||
entryComponents: this.entryComponents,
|
||||
changeDetection: this.changeDetection,
|
||||
template: this.template && this.template.toSummary(),
|
||||
|
@ -368,6 +368,12 @@ export class Identifiers {
|
||||
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'pipeDef', runtime: ɵviewEngine.pipeDef};
|
||||
static nodeValue: IdentifierSpec =
|
||||
{name: 'ɵviewEngine', moduleUrl: CORE, member: 'nodeValue', runtime: ɵviewEngine.nodeValue};
|
||||
static ngContentDef: IdentifierSpec = {
|
||||
name: 'ɵviewEngine',
|
||||
moduleUrl: CORE,
|
||||
member: 'ngContentDef',
|
||||
runtime: ɵviewEngine.ngContentDef
|
||||
};
|
||||
static unwrapValue: IdentifierSpec = {
|
||||
name: 'ɵviewEngine',
|
||||
moduleUrl: CORE,
|
||||
|
@ -11,7 +11,7 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveS
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {ParseError, ParseSourceSpan} from './parse_util';
|
||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst} from './template_parser/template_ast';
|
||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst} from './template_parser/template_ast';
|
||||
|
||||
export class ProviderError extends ParseError {
|
||||
constructor(message: string, span: ParseSourceSpan) { super(span, message); }
|
||||
@ -19,7 +19,7 @@ export class ProviderError extends ParseError {
|
||||
|
||||
export interface QueryWithId {
|
||||
meta: CompileQueryMetadata;
|
||||
id: QueryId;
|
||||
queryId: number;
|
||||
}
|
||||
|
||||
export class ProviderViewContext {
|
||||
@ -57,16 +57,21 @@ export class ProviderElementContext {
|
||||
constructor(
|
||||
public viewContext: ProviderViewContext, private _parent: ProviderElementContext,
|
||||
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
|
||||
refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) {
|
||||
refs: ReferenceAst[], isTemplate: boolean, contentQueryStartId: number,
|
||||
private _sourceSpan: ParseSourceSpan) {
|
||||
this._attrs = {};
|
||||
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
|
||||
const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
|
||||
this._allProviders =
|
||||
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors);
|
||||
this._contentQueries = _getContentQueries(this.depth, directivesMeta);
|
||||
this._contentQueries = _getContentQueries(contentQueryStartId, directivesMeta);
|
||||
Array.from(this._allProviders.values()).forEach((provider) => {
|
||||
this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens);
|
||||
});
|
||||
if (isTemplate) {
|
||||
const templateRefId = createIdentifierToken(Identifiers.TemplateRef);
|
||||
this._addQueryReadsTo(templateRefId, templateRefId, this._queriedTokens);
|
||||
}
|
||||
refs.forEach((refAst) => {
|
||||
let defaultQueryValue = refAst.value || createIdentifierToken(Identifiers.ElementRef);
|
||||
this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens);
|
||||
@ -91,16 +96,6 @@ export class ProviderElementContext {
|
||||
});
|
||||
}
|
||||
|
||||
get depth(): number {
|
||||
let d = 0;
|
||||
let current: ProviderElementContext = this;
|
||||
while (current._parent) {
|
||||
d++;
|
||||
current = current._parent;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
get transformProviders(): ProviderAst[] {
|
||||
return Array.from(this._transformedProviders.values());
|
||||
}
|
||||
@ -133,7 +128,7 @@ export class ProviderElementContext {
|
||||
queryMatches = [];
|
||||
queryReadTokens.set(tokenRef, queryMatches);
|
||||
}
|
||||
queryMatches.push({query: query.id, value: queryValue});
|
||||
queryMatches.push({queryId: query.queryId, value: queryValue});
|
||||
});
|
||||
}
|
||||
|
||||
@ -483,24 +478,24 @@ function _resolveProviders(
|
||||
|
||||
|
||||
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, QueryWithId[]> {
|
||||
// Note: queries start with id 1 so we can use the number in a Bloom filter!
|
||||
let viewQueryId = 1;
|
||||
const viewQueries = new Map<any, QueryWithId[]>();
|
||||
if (component.viewQueries) {
|
||||
component.viewQueries.forEach(
|
||||
(query, queryIndex) => _addQueryToTokenMap(
|
||||
viewQueries,
|
||||
{meta: query, id: {elementDepth: null, directiveIndex: null, queryIndex: queryIndex}}));
|
||||
(query) => _addQueryToTokenMap(viewQueries, {meta: query, queryId: viewQueryId++}));
|
||||
}
|
||||
return viewQueries;
|
||||
}
|
||||
|
||||
function _getContentQueries(
|
||||
elementDepth: number, directives: CompileDirectiveSummary[]): Map<any, QueryWithId[]> {
|
||||
contentQueryStartId: number, directives: CompileDirectiveSummary[]): Map<any, QueryWithId[]> {
|
||||
let contentQueryId = contentQueryStartId;
|
||||
const contentQueries = new Map<any, QueryWithId[]>();
|
||||
directives.forEach((directive, directiveIndex) => {
|
||||
if (directive.queries) {
|
||||
directive.queries.forEach(
|
||||
(query, queryIndex) => _addQueryToTokenMap(
|
||||
contentQueries, {meta: query, id: {elementDepth, directiveIndex, queryIndex}}));
|
||||
(query) => _addQueryToTokenMap(contentQueries, {meta: query, queryId: contentQueryId++}));
|
||||
}
|
||||
});
|
||||
return contentQueries;
|
||||
|
@ -170,7 +170,7 @@ export class DirectiveAst implements TemplateAst {
|
||||
constructor(
|
||||
public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[],
|
||||
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
public contentQueryStartId: number, public sourceSpan: ParseSourceSpan) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitDirective(this, context);
|
||||
}
|
||||
@ -241,17 +241,8 @@ export enum PropertyBindingType {
|
||||
Animation
|
||||
}
|
||||
|
||||
/**
|
||||
* This id differentiates a query on an element from any query on any child.
|
||||
*/
|
||||
export interface QueryId {
|
||||
elementDepth: number;
|
||||
directiveIndex: number;
|
||||
queryIndex: number;
|
||||
}
|
||||
|
||||
export interface QueryMatch {
|
||||
query: QueryId;
|
||||
queryId: number;
|
||||
value: CompileTokenMetadata;
|
||||
}
|
||||
|
||||
|
@ -207,12 +207,15 @@ export class TemplateParser {
|
||||
class TemplateParseVisitor implements html.Visitor {
|
||||
selectorMatcher = new SelectorMatcher();
|
||||
directivesIndex = new Map<CompileDirectiveSummary, number>();
|
||||
ngContentCount: number = 0;
|
||||
ngContentCount = 0;
|
||||
contentQueryStartId: number;
|
||||
|
||||
constructor(
|
||||
public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[],
|
||||
private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) {
|
||||
// Note: queries start with id 1 so we can use the number in a Bloom filter!
|
||||
this.contentQueryStartId = providerViewContext.component.viewQueries.length + 1;
|
||||
directives.forEach((directive, index) => {
|
||||
const selector = CssSelector.parse(directive.selector);
|
||||
this.selectorMatcher.addSelectables(selector, directive);
|
||||
@ -241,6 +244,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
visitComment(comment: html.Comment, context: any): any { return null; }
|
||||
|
||||
visitElement(element: html.Element, parent: ElementContext): any {
|
||||
const queryStartIndex = this.contentQueryStartId;
|
||||
const nodeName = element.name;
|
||||
const preparsedElement = preparseElement(element);
|
||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||
@ -322,9 +326,9 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
const providerContext = new ProviderElementContext(
|
||||
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
|
||||
references, element.sourceSpan);
|
||||
references, isTemplateElement, queryStartIndex, element.sourceSpan);
|
||||
|
||||
const children = html.visitAll(
|
||||
const children: TemplateAst[] = html.visitAll(
|
||||
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
|
||||
ElementContext.create(
|
||||
isTemplateElement, directiveAsts,
|
||||
@ -378,6 +382,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
if (hasInlineTemplates) {
|
||||
const templateQueryStartIndex = this.contentQueryStartId;
|
||||
const templateCssSelector =
|
||||
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
|
||||
const {directives: templateDirectiveMetas} =
|
||||
@ -392,7 +397,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
templateDirectiveAsts, templateElementProps, element.sourceSpan);
|
||||
const templateProviderContext = new ProviderElementContext(
|
||||
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
|
||||
templateDirectiveAsts, [], [], element.sourceSpan);
|
||||
templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan);
|
||||
templateProviderContext.afterElement();
|
||||
|
||||
parsedElement = new EmbeddedTemplateAst(
|
||||
@ -585,8 +590,11 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
matchedReferences.add(elOrDirRef.name);
|
||||
}
|
||||
});
|
||||
const contentQueryStartId = this.contentQueryStartId;
|
||||
this.contentQueryStartId += directive.queries.length;
|
||||
return new DirectiveAst(
|
||||
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
|
||||
directive, directiveProperties, hostProperties, hostEvents, contentQueryStartId,
|
||||
sourceSpan);
|
||||
});
|
||||
|
||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||
@ -779,7 +787,7 @@ class NonBindableVisitor implements html.Visitor {
|
||||
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
|
||||
const selector = createElementCssSelector(ast.name, attrNameAndValues);
|
||||
const ngContentIndex = parent.findNgContentIndex(selector);
|
||||
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
const children: TemplateAst[] = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
return new ElementAst(
|
||||
ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children,
|
||||
ngContentIndex, ast.sourceSpan, ast.endSourceSpan);
|
||||
|
@ -9,23 +9,24 @@
|
||||
import {ChangeDetectionStrategy} from '@angular/core';
|
||||
|
||||
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, CompileTypeMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata';
|
||||
import {BuiltinConverter, BuiltinConverterFactory, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||
import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers';
|
||||
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from '../identifiers';
|
||||
import {CompilerInjectable} from '../injectable';
|
||||
import * as o from '../output/output_ast';
|
||||
import {convertValueToOutputAst} from '../output/value_util';
|
||||
import {LifecycleHooks, viewEngine} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
import {ViewEncapsulationEnum} from '../view_compiler/constants';
|
||||
import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
const CLASS_ATTR = 'class';
|
||||
const STYLE_ATTR = 'style';
|
||||
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
|
||||
const NG_CONTAINER_TAG = 'ng-container';
|
||||
|
||||
@CompilerInjectable()
|
||||
export class ViewCompilerNext extends ViewCompiler {
|
||||
@ -41,15 +42,16 @@ export class ViewCompilerNext extends ViewCompiler {
|
||||
const compName = identifierName(component.type) + (component.isHost ? `_Host` : '');
|
||||
|
||||
let embeddedViewCount = 0;
|
||||
const staticQueryIds = findStaticQueryIds(template);
|
||||
|
||||
const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => {
|
||||
const embeddedViewIndex = embeddedViewCount++;
|
||||
const viewName = `view_${compName}_${embeddedViewIndex}`;
|
||||
return new ViewBuilder(parent, viewName, usedPipes, viewBuilderFactory);
|
||||
return new ViewBuilder(parent, viewName, usedPipes, staticQueryIds, viewBuilderFactory);
|
||||
};
|
||||
|
||||
const visitor = viewBuilderFactory(null);
|
||||
visitor.visitAll([], template, 0);
|
||||
visitor.visitAll([], template);
|
||||
|
||||
const statements: o.Statement[] = [];
|
||||
statements.push(...visitor.build(component));
|
||||
@ -93,9 +95,10 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
|
||||
constructor(
|
||||
private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[],
|
||||
private staticQueryIds: Map<TemplateAst, StaticAndDynamicQueryIds>,
|
||||
private viewBuilderFactory: ViewBuilderFactory) {}
|
||||
|
||||
visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) {
|
||||
visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
|
||||
this.variables = variables;
|
||||
// create the pipes for the pure pipes immediately, so that we know their indices.
|
||||
if (!this.parent) {
|
||||
@ -106,7 +109,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
});
|
||||
}
|
||||
|
||||
templateVisitAll(this, astNodes, {elementDepth});
|
||||
templateVisitAll(this, astNodes);
|
||||
if (astNodes.length === 0 ||
|
||||
(this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) {
|
||||
// if the view is empty, or an embedded view has a view container as last root nde,
|
||||
@ -197,12 +200,17 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
return updateFn;
|
||||
}
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any {}
|
||||
visitNgContent(ast: NgContentAst, context: any): any {
|
||||
// ngContentDef(ngContentIndex: number, index: number): NodeDef;
|
||||
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.ngContentDef)).callFn([
|
||||
o.literal(ast.ngContentIndex), o.literal(ast.index)
|
||||
]));
|
||||
}
|
||||
|
||||
visitText(ast: TextAst, context: any): any {
|
||||
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
||||
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
||||
o.NULL_EXPR, o.literalArr([o.literal(ast.value)])
|
||||
o.literal(ast.ngContentIndex), o.literalArr([o.literal(ast.value)])
|
||||
]));
|
||||
}
|
||||
|
||||
@ -220,20 +228,20 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
|
||||
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
||||
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
||||
o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s)))
|
||||
o.literal(ast.ngContentIndex), o.literalArr(inter.strings.map(s => o.literal(s)))
|
||||
]);
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any {
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
const nodeIndex = this.nodeDefs.length;
|
||||
// reserve the space in the nodeDefs array
|
||||
this.nodeDefs.push(null);
|
||||
|
||||
const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context);
|
||||
const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast);
|
||||
|
||||
const childVisitor = this.viewBuilderFactory(this);
|
||||
this.children.push(childVisitor);
|
||||
childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1);
|
||||
childVisitor.visitAll(ast.variables, ast.children);
|
||||
|
||||
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
||||
|
||||
@ -241,20 +249,20 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
||||
// childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef;
|
||||
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
|
||||
o.literal(flags), queryMatchesExpr, o.NULL_EXPR, o.literal(childCount),
|
||||
o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount),
|
||||
o.variable(childVisitor.viewName)
|
||||
]);
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, context: {elementDepth: number}): any {
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
const nodeIndex = this.nodeDefs.length;
|
||||
// reserve the space in the nodeDefs array so we can add children
|
||||
this.nodeDefs.push(null);
|
||||
|
||||
const {flags, usedEvents, queryMatchesExpr, hostBindings} =
|
||||
this._visitElementOrTemplate(nodeIndex, ast, context);
|
||||
let {flags, usedEvents, queryMatchesExpr, hostBindings} =
|
||||
this._visitElementOrTemplate(nodeIndex, ast);
|
||||
|
||||
templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1});
|
||||
templateVisitAll(this, ast.children);
|
||||
|
||||
ast.inputs.forEach(
|
||||
(inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); });
|
||||
@ -270,6 +278,12 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
|
||||
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
||||
|
||||
let elName = ast.name;
|
||||
if (ast.name === NG_CONTAINER_TAG) {
|
||||
// Using a null element name creates an anchor.
|
||||
elName = null;
|
||||
}
|
||||
|
||||
// elementDef(
|
||||
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
||||
// childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
|
||||
@ -279,22 +293,21 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
// SecurityContext])[],
|
||||
// outputs?: (string | [string, string])[]): NodeDef;
|
||||
this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
|
||||
o.literal(flags), queryMatchesExpr, o.NULL_EXPR, o.literal(childCount), o.literal(ast.name),
|
||||
fixedAttrsDef(ast), inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
|
||||
o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount),
|
||||
o.literal(elName), fixedAttrsDef(ast),
|
||||
inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
|
||||
outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR
|
||||
]);
|
||||
}
|
||||
|
||||
private _visitElementOrTemplate(
|
||||
nodeIndex: number, ast: {
|
||||
hasViewContainer: boolean,
|
||||
outputs: BoundEventAst[],
|
||||
directives: DirectiveAst[],
|
||||
providers: ProviderAst[],
|
||||
references: ReferenceAst[],
|
||||
queryMatches: QueryMatch[]
|
||||
},
|
||||
context: {elementDepth: number}): {
|
||||
private _visitElementOrTemplate(nodeIndex: number, ast: {
|
||||
hasViewContainer: boolean,
|
||||
outputs: BoundEventAst[],
|
||||
directives: DirectiveAst[],
|
||||
providers: ProviderAst[],
|
||||
references: ReferenceAst[],
|
||||
queryMatches: QueryMatch[]
|
||||
}): {
|
||||
flags: number,
|
||||
usedEvents: [string, string][],
|
||||
queryMatchesExpr: o.Expression,
|
||||
@ -317,19 +330,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
});
|
||||
const hostBindings: {value: AST, context: o.Expression}[] = [];
|
||||
const hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] = [];
|
||||
const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
|
||||
if (componentFactoryResolverProvider) {
|
||||
this._visitProvider(componentFactoryResolverProvider, ast.queryMatches);
|
||||
}
|
||||
|
||||
ast.providers.forEach((providerAst, providerIndex) => {
|
||||
let dirAst: DirectiveAst;
|
||||
let dirIndex: number;
|
||||
ast.directives.forEach((localDirAst, i) => {
|
||||
if (localDirAst.directive.type.reference === providerAst.token.identifier.reference) {
|
||||
if (localDirAst.directive.type.reference === tokenReference(providerAst.token)) {
|
||||
dirAst = localDirAst;
|
||||
dirIndex = i;
|
||||
}
|
||||
});
|
||||
if (dirAst) {
|
||||
const {hostBindings: dirHostBindings, hostEvents: dirHostEvents} = this._visitDirective(
|
||||
providerAst, dirAst, dirIndex, nodeIndex, context.elementDepth, ast.references,
|
||||
ast.queryMatches, usedEvents);
|
||||
providerAst, dirAst, dirIndex, nodeIndex, ast.references, ast.queryMatches, usedEvents,
|
||||
this.staticQueryIds.get(<any>ast));
|
||||
hostBindings.push(...dirHostBindings);
|
||||
hostEvents.push(...dirHostEvents);
|
||||
} else {
|
||||
@ -348,8 +366,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
valueType = viewEngine.QueryValueType.TemplateRef;
|
||||
}
|
||||
if (valueType != null) {
|
||||
queryMatchExprs.push(
|
||||
o.literalArr([o.literal(calcQueryId(match.query)), o.literal(valueType)]));
|
||||
queryMatchExprs.push(o.literalArr([o.literal(match.queryId), o.literal(valueType)]));
|
||||
}
|
||||
});
|
||||
ast.references.forEach((ref) => {
|
||||
@ -361,7 +378,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
}
|
||||
if (valueType != null) {
|
||||
this.refNodeIndices[ref.name] = nodeIndex;
|
||||
queryMatchExprs.push(o.literalArr([o.literal(`#${ref.name}`), o.literal(valueType)]));
|
||||
queryMatchExprs.push(o.literalArr([o.literal(ref.name), o.literal(valueType)]));
|
||||
}
|
||||
});
|
||||
ast.outputs.forEach(
|
||||
@ -383,8 +400,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
|
||||
private _visitDirective(
|
||||
providerAst: ProviderAst, directiveAst: DirectiveAst, directiveIndex: number,
|
||||
elementNodeIndex: number, elementDepth: number, refs: ReferenceAst[],
|
||||
queryMatches: QueryMatch[], usedEvents: Map<string, any>): {
|
||||
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
|
||||
usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): {
|
||||
hostBindings: {value: AST, context: o.Expression}[],
|
||||
hostEvents: {context: o.Expression, eventAst: BoundEventAst}[]
|
||||
} {
|
||||
@ -392,12 +409,34 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
// reserve the space in the nodeDefs array so we can add children
|
||||
this.nodeDefs.push(null);
|
||||
|
||||
directiveAst.directive.viewQueries.forEach((query, queryIndex) => {
|
||||
// Note: queries start with id 1 so we can use the number in a Bloom filter!
|
||||
const queryId = queryIndex + 1;
|
||||
const bindingType =
|
||||
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
|
||||
let flags = viewEngine.NodeFlags.HasViewQuery;
|
||||
if (queryIds.staticQueryIds.has(queryId)) {
|
||||
flags |= viewEngine.NodeFlags.HasStaticQuery;
|
||||
} else {
|
||||
flags |= viewEngine.NodeFlags.HasDynamicQuery;
|
||||
}
|
||||
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
||||
o.literal(flags), o.literal(queryId),
|
||||
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
||||
]));
|
||||
});
|
||||
directiveAst.directive.queries.forEach((query, queryIndex) => {
|
||||
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex};
|
||||
let flags = viewEngine.NodeFlags.HasContentQuery;
|
||||
const queryId = directiveAst.contentQueryStartId + queryIndex;
|
||||
if (queryIds.staticQueryIds.has(queryId)) {
|
||||
flags |= viewEngine.NodeFlags.HasStaticQuery;
|
||||
} else {
|
||||
flags |= viewEngine.NodeFlags.HasDynamicQuery;
|
||||
}
|
||||
const bindingType =
|
||||
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
|
||||
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
||||
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)),
|
||||
o.literal(flags), o.literal(queryId),
|
||||
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
||||
]));
|
||||
});
|
||||
@ -414,8 +453,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
refs.forEach((ref) => {
|
||||
if (ref.value && tokenReference(ref.value) === tokenReference(providerAst.token)) {
|
||||
this.refNodeIndices[ref.name] = nodeIndex;
|
||||
queryMatchExprs.push(o.literalArr(
|
||||
[o.literal(`#${ref.name}`), o.literal(viewEngine.QueryValueType.Provider)]));
|
||||
queryMatchExprs.push(
|
||||
o.literalArr([o.literal(ref.name), o.literal(viewEngine.QueryValueType.Provider)]));
|
||||
}
|
||||
});
|
||||
|
||||
@ -505,6 +544,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
if (!providerAst.eager) {
|
||||
flags |= viewEngine.NodeFlags.LazyProvider;
|
||||
}
|
||||
if (providerAst.providerType === ProviderAstType.PrivateService) {
|
||||
flags |= viewEngine.NodeFlags.PrivateProvider;
|
||||
}
|
||||
providerAst.lifecycleHooks.forEach((lifecycleHook) => {
|
||||
// for regular providers, we only support ngOnDestroy
|
||||
if (lifecycleHook === LifecycleHooks.OnDestroy ||
|
||||
@ -518,7 +560,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||
queryMatches.forEach((match) => {
|
||||
if (tokenReference(match.value) === tokenReference(providerAst.token)) {
|
||||
queryMatchExprs.push(o.literalArr(
|
||||
[o.literal(calcQueryId(match.query)), o.literal(viewEngine.QueryValueType.Provider)]));
|
||||
[o.literal(match.queryId), o.literal(viewEngine.QueryValueType.Provider)]));
|
||||
}
|
||||
});
|
||||
const {providerExpr, providerType, depsExpr} = providerDef(providerAst);
|
||||
@ -680,18 +722,15 @@ function multiProviderDef(providers: CompileProviderMetadata[]):
|
||||
const allDepDefs: o.Expression[] = [];
|
||||
const allParams: o.FnParam[] = [];
|
||||
const exprs = providers.map((provider, providerIndex) => {
|
||||
const depExprs = provider.deps.map((dep, depIndex) => {
|
||||
const paramName = `p${providerIndex}_${depIndex}`;
|
||||
allParams.push(new o.FnParam(paramName, o.DYNAMIC_TYPE));
|
||||
allDepDefs.push(depDef(dep));
|
||||
return o.variable(paramName);
|
||||
});
|
||||
let expr: o.Expression;
|
||||
if (provider.useClass) {
|
||||
const depExprs = convertDeps(providerIndex, provider.deps || provider.useClass.diDeps);
|
||||
expr = o.importExpr(provider.useClass).instantiate(depExprs);
|
||||
} else if (provider.useFactory) {
|
||||
const depExprs = convertDeps(providerIndex, provider.deps || provider.useFactory.diDeps);
|
||||
expr = o.importExpr(provider.useFactory).callFn(depExprs);
|
||||
} else if (provider.useExisting) {
|
||||
const depExprs = convertDeps(providerIndex, [{token: provider.useExisting}]);
|
||||
expr = depExprs[0];
|
||||
} else {
|
||||
expr = convertValueToOutputAst(provider.useValue);
|
||||
@ -704,6 +743,15 @@ function multiProviderDef(providers: CompileProviderMetadata[]):
|
||||
providerType: viewEngine.ProviderType.Factory,
|
||||
depsExpr: o.literalArr(allDepDefs)
|
||||
};
|
||||
|
||||
function convertDeps(providerIndex: number, deps: CompileDiDependencyMetadata[]) {
|
||||
return deps.map((dep, depIndex) => {
|
||||
const paramName = `p${providerIndex}_${depIndex}`;
|
||||
allParams.push(new o.FnParam(paramName, o.DYNAMIC_TYPE));
|
||||
allDepDefs.push(depDef(dep));
|
||||
return o.variable(paramName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function singleProviderDef(providerMeta: CompileProviderMetadata):
|
||||
@ -765,15 +813,6 @@ function needsAdditionalRootNode(ast: TemplateAst): boolean {
|
||||
return ast instanceof NgContentAst;
|
||||
}
|
||||
|
||||
function calcQueryId(queryId: QueryId): string {
|
||||
if (queryId.directiveIndex == null) {
|
||||
// view query
|
||||
return `v${queryId.queryIndex}`;
|
||||
} else {
|
||||
return `c${queryId.elementDepth}_${queryId.directiveIndex}_${queryId.queryIndex}`;
|
||||
}
|
||||
}
|
||||
|
||||
function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number {
|
||||
let nodeFlag = viewEngine.NodeFlags.None;
|
||||
switch (lifecycleHook) {
|
||||
@ -872,4 +911,66 @@ function callCheckStmt(nodeIndex: number, exprs: o.Expression[]): o.Expression {
|
||||
|
||||
function callUnwrapValue(expr: o.Expression): o.Expression {
|
||||
return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([expr]);
|
||||
}
|
||||
|
||||
interface StaticAndDynamicQueryIds {
|
||||
staticQueryIds: Set<number>;
|
||||
dynamicQueryIds: Set<number>;
|
||||
}
|
||||
|
||||
|
||||
function findStaticQueryIds(
|
||||
nodes: TemplateAst[], result = new Map<TemplateAst, StaticAndDynamicQueryIds>()):
|
||||
Map<TemplateAst, StaticAndDynamicQueryIds> {
|
||||
nodes.forEach((node) => {
|
||||
const staticQueryIds = new Set<number>();
|
||||
const dynamicQueryIds = new Set<number>();
|
||||
let queryMatches: QueryMatch[];
|
||||
if (node instanceof ElementAst) {
|
||||
findStaticQueryIds(node.children, result);
|
||||
node.children.forEach((child) => {
|
||||
const childData = result.get(child);
|
||||
childData.staticQueryIds.forEach(queryId => staticQueryIds.add(queryId));
|
||||
childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
|
||||
});
|
||||
queryMatches = node.queryMatches;
|
||||
} else if (node instanceof EmbeddedTemplateAst) {
|
||||
findStaticQueryIds(node.children, result);
|
||||
node.children.forEach((child) => {
|
||||
const childData = result.get(child);
|
||||
childData.staticQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
|
||||
childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
|
||||
});
|
||||
queryMatches = node.queryMatches;
|
||||
}
|
||||
if (queryMatches) {
|
||||
queryMatches.forEach((match) => staticQueryIds.add(match.queryId));
|
||||
}
|
||||
dynamicQueryIds.forEach(queryId => staticQueryIds.delete(queryId));
|
||||
result.set(node, {staticQueryIds, dynamicQueryIds});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst {
|
||||
const componentDirMeta = directives.find(dirAst => dirAst.directive.isComponent);
|
||||
if (componentDirMeta) {
|
||||
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
|
||||
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
|
||||
const cfrExpr = o.importExpr(createIdentifier(Identifiers.CodegenComponentFactoryResolver))
|
||||
.instantiate([o.literalArr(entryComponentFactories)]);
|
||||
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
|
||||
const classMeta: CompileTypeMetadata = {
|
||||
diDeps: [
|
||||
{isValue: true, value: o.literalArr(entryComponentFactories)},
|
||||
{token: token, isSkipSelf: true, isOptional: true}
|
||||
],
|
||||
lifecycleHooks: [],
|
||||
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
|
||||
};
|
||||
return new ProviderAst(
|
||||
token, false, true, [{token, multi: false, useClass: classMeta}],
|
||||
ProviderAstType.PrivateService, [], componentDirMeta.sourceSpan);
|
||||
}
|
||||
return null;
|
||||
}
|
@ -155,7 +155,7 @@ export function main() {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitDirective(ast: DirectiveAst, context: any): any{return ast;}},
|
||||
new DirectiveAst(null, [], [], [], null));
|
||||
new DirectiveAst(null, [], [], [], 0, null));
|
||||
});
|
||||
|
||||
it('should visit DirectiveAst', () => {
|
||||
@ -178,7 +178,7 @@ export function main() {
|
||||
new BoundEventAst('foo', 'bar', 'goo', null, null),
|
||||
new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null),
|
||||
new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null),
|
||||
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null),
|
||||
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], 0, null),
|
||||
new BoundDirectivePropertyAst('foo', 'bar', null, null)
|
||||
];
|
||||
const result = templateVisitAll(visitor, nodes, null);
|
||||
|
Reference in New Issue
Block a user