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:
Tobias Bosch
2017-02-15 08:36:49 -08:00
committed by Igor Minar
parent e9ba7aa4f8
commit 4e7752a12a
29 changed files with 810 additions and 558 deletions

View File

@ -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(),

View File

@ -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,

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);