This change reduces the amount of generated code by only adding `log` calls for elements and text nodes. We need the `log` calls to allow users to jump to the right place in the template via source maps. However, we only need it for element and text nodes, but not for directives, queries, … as for them we first locate the corresponding element or text node. Related to #15239 PR Close #15350
1197 lines
49 KiB
TypeScript
1197 lines
49 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {ChangeDetectionStrategy, ViewEncapsulation, ɵArgumentType as ArgumentType, ɵBindingFlags as BindingFlags, ɵDepFlags as DepFlags, ɵLifecycleHooks as LifecycleHooks, ɵNodeFlags as NodeFlags, ɵQueryBindingType as QueryBindingType, ɵQueryValueType as QueryValueType, ɵViewFlags as ViewFlags, ɵelementEventFullName as elementEventFullName} from '@angular/core';
|
|
|
|
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, CompileTypeMetadata, identifierModuleUrl, identifierName, rendererTypeName, tokenReference, viewClassName} 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, createIdentifierToken, resolveIdentifier} from '../identifiers';
|
|
import {CompilerInjectable} from '../injectable';
|
|
import * as o from '../output/output_ast';
|
|
import {convertValueToOutputAst} from '../output/value_util';
|
|
import {ParseSourceSpan} from '../parse_util';
|
|
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
|
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';
|
|
|
|
const CLASS_ATTR = 'class';
|
|
const STYLE_ATTR = 'style';
|
|
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
|
|
const NG_CONTAINER_TAG = 'ng-container';
|
|
|
|
export class ViewCompileResult {
|
|
constructor(
|
|
public statements: o.Statement[], public viewClassVar: string,
|
|
public rendererTypeVar: string) {}
|
|
}
|
|
|
|
@CompilerInjectable()
|
|
export class ViewCompiler {
|
|
constructor(
|
|
private _genConfigNext: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {}
|
|
|
|
compileComponent(
|
|
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
|
|
usedPipes: CompilePipeSummary[]): ViewCompileResult {
|
|
let embeddedViewCount = 0;
|
|
const staticQueryIds = findStaticQueryIds(template);
|
|
|
|
const statements: o.Statement[] = [];
|
|
|
|
let renderComponentVarName: string;
|
|
if (!component.isHost) {
|
|
const customRenderData: o.LiteralMapEntry[] = [];
|
|
if (component.template.animations && component.template.animations.length) {
|
|
customRenderData.push(new o.LiteralMapEntry(
|
|
'animation', convertValueToOutputAst(component.template.animations), true));
|
|
}
|
|
|
|
const renderComponentVar = o.variable(rendererTypeName(component.type.reference));
|
|
renderComponentVarName = renderComponentVar.name;
|
|
statements.push(
|
|
renderComponentVar
|
|
.set(o.importExpr(createIdentifier(Identifiers.createRendererType2))
|
|
.callFn([new o.LiteralMapExpr([
|
|
new o.LiteralMapEntry(
|
|
'encapsulation', o.literal(component.template.encapsulation)),
|
|
new o.LiteralMapEntry('styles', styles),
|
|
new o.LiteralMapEntry('data', new o.LiteralMapExpr(customRenderData))
|
|
])]))
|
|
.toDeclStmt(
|
|
o.importType(createIdentifier(Identifiers.RendererType2)),
|
|
[o.StmtModifier.Final]));
|
|
}
|
|
|
|
const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => {
|
|
const embeddedViewIndex = embeddedViewCount++;
|
|
return new ViewBuilder(
|
|
parent, component, embeddedViewIndex, usedPipes, staticQueryIds, viewBuilderFactory);
|
|
};
|
|
|
|
const visitor = viewBuilderFactory(null);
|
|
visitor.visitAll([], template);
|
|
|
|
statements.push(...visitor.build());
|
|
|
|
return new ViewCompileResult(statements, visitor.viewName, renderComponentVarName);
|
|
}
|
|
}
|
|
|
|
interface ViewBuilderFactory {
|
|
(parent: ViewBuilder): ViewBuilder;
|
|
}
|
|
|
|
interface UpdateExpression {
|
|
context: o.Expression;
|
|
nodeIndex: number;
|
|
bindingIndex: number;
|
|
sourceSpan: ParseSourceSpan;
|
|
value: AST;
|
|
}
|
|
|
|
const LOG_VAR = o.variable('l');
|
|
const VIEW_VAR = o.variable('v');
|
|
const CHECK_VAR = o.variable('ck');
|
|
const COMP_VAR = o.variable('co');
|
|
const EVENT_NAME_VAR = o.variable('en');
|
|
const ALLOW_DEFAULT_VAR = o.variable(`ad`);
|
|
|
|
class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
|
private compType: o.Type;
|
|
private nodes: (() => {
|
|
sourceSpan: ParseSourceSpan,
|
|
nodeDef: o.Expression,
|
|
nodeFlags: NodeFlags, updateDirectives?: UpdateExpression[], updateRenderer?: UpdateExpression[]
|
|
})[] = [];
|
|
private purePipeNodeIndices: {[pipeName: string]: number} = Object.create(null);
|
|
// Need Object.create so that we don't have builtin values...
|
|
private refNodeIndices: {[refName: string]: number} = Object.create(null);
|
|
private variables: VariableAst[] = [];
|
|
private children: ViewBuilder[] = [];
|
|
|
|
constructor(
|
|
private parent: ViewBuilder, private component: CompileDirectiveMetadata,
|
|
private embeddedViewIndex: number, private usedPipes: CompilePipeSummary[],
|
|
private staticQueryIds: Map<TemplateAst, StaticAndDynamicQueryIds>,
|
|
private viewBuilderFactory: ViewBuilderFactory) {
|
|
// TODO(tbosch): The old view compiler used to use an `any` type
|
|
// for the context in any embedded view. We keep this behaivor for now
|
|
// to be able to introduce the new view compiler without too many errors.
|
|
this.compType = this.embeddedViewIndex > 0 ? o.DYNAMIC_TYPE : o.importType(this.component.type);
|
|
}
|
|
|
|
get viewName(): string {
|
|
return viewClassName(this.component.type.reference, this.embeddedViewIndex);
|
|
}
|
|
|
|
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) {
|
|
this.usedPipes.forEach((pipe) => {
|
|
if (pipe.pure) {
|
|
this.purePipeNodeIndices[pipe.name] = this._createPipe(null, pipe);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!this.parent) {
|
|
const queryIds = staticViewQueryIds(this.staticQueryIds);
|
|
this.component.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 ? QueryBindingType.First : QueryBindingType.All;
|
|
let flags = NodeFlags.TypeViewQuery;
|
|
if (queryIds.staticQueryIds.has(queryId)) {
|
|
flags |= NodeFlags.StaticQuery;
|
|
} else {
|
|
flags |= NodeFlags.DynamicQuery;
|
|
}
|
|
this.nodes.push(() => ({
|
|
sourceSpan: null,
|
|
nodeFlags: flags,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
|
o.literal(flags), o.literal(queryId),
|
|
new o.LiteralMapExpr(
|
|
[new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
|
])
|
|
}));
|
|
});
|
|
}
|
|
templateVisitAll(this, astNodes);
|
|
if (this.parent && (astNodes.length === 0 || needsAdditionalRootNode(astNodes))) {
|
|
// if the view is an embedded view, then we need to add an additional root node in some cases
|
|
this.nodes.push(() => ({
|
|
sourceSpan: null,
|
|
nodeFlags: NodeFlags.TypeElement,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
|
|
o.literal(NodeFlags.None), o.NULL_EXPR, o.NULL_EXPR, o.literal(0)
|
|
])
|
|
}));
|
|
}
|
|
}
|
|
|
|
build(targetStatements: o.Statement[] = []): o.Statement[] {
|
|
this.children.forEach((child) => child.build(targetStatements));
|
|
|
|
const {updateRendererStmts, updateDirectivesStmts, nodeDefExprs} =
|
|
this._createNodeExpressions();
|
|
|
|
const updateRendererFn = this._createUpdateFn(updateRendererStmts);
|
|
const updateDirectivesFn = this._createUpdateFn(updateDirectivesStmts);
|
|
|
|
|
|
let viewFlags = ViewFlags.None;
|
|
if (!this.parent && this.component.changeDetection === ChangeDetectionStrategy.OnPush) {
|
|
viewFlags |= ViewFlags.OnPush;
|
|
}
|
|
const viewFactory = new o.DeclareFunctionStmt(
|
|
this.viewName, [new o.FnParam(LOG_VAR.name)],
|
|
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
|
|
o.literal(viewFlags),
|
|
o.literalArr(nodeDefExprs),
|
|
updateDirectivesFn,
|
|
updateRendererFn,
|
|
]))],
|
|
o.importType(createIdentifier(Identifiers.ViewDefinition)));
|
|
|
|
targetStatements.push(viewFactory);
|
|
return targetStatements;
|
|
}
|
|
|
|
private _createUpdateFn(updateStmts: o.Statement[]): o.Expression {
|
|
let updateFn: o.Expression;
|
|
if (updateStmts.length > 0) {
|
|
const preStmts: o.Statement[] = [];
|
|
if (!this.component.isHost && o.findReadVarNames(updateStmts).has(COMP_VAR.name)) {
|
|
preStmts.push(COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(this.compType));
|
|
}
|
|
updateFn = o.fn(
|
|
[
|
|
new o.FnParam(CHECK_VAR.name, o.INFERRED_TYPE),
|
|
new o.FnParam(VIEW_VAR.name, o.INFERRED_TYPE)
|
|
],
|
|
[...preStmts, ...updateStmts], o.INFERRED_TYPE);
|
|
} else {
|
|
updateFn = o.NULL_EXPR;
|
|
}
|
|
return updateFn;
|
|
}
|
|
|
|
visitNgContent(ast: NgContentAst, context: any): any {
|
|
// ngContentDef(ngContentIndex: number, index: number): NodeDef;
|
|
this.nodes.push(() => ({
|
|
sourceSpan: ast.sourceSpan,
|
|
nodeFlags: NodeFlags.TypeNgContent,
|
|
nodeDef: 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.nodes.push(() => ({
|
|
sourceSpan: ast.sourceSpan,
|
|
nodeFlags: NodeFlags.TypeText,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
|
o.literal(ast.ngContentIndex), o.literalArr([o.literal(ast.value)])
|
|
])
|
|
}));
|
|
}
|
|
|
|
visitBoundText(ast: BoundTextAst, context: any): any {
|
|
const nodeIndex = this.nodes.length;
|
|
// reserve the space in the nodeDefs array
|
|
this.nodes.push(null);
|
|
|
|
const astWithSource = <ASTWithSource>ast.value;
|
|
const inter = <Interpolation>astWithSource.ast;
|
|
|
|
const updateRendererExpressions = inter.expressions.map(
|
|
(expr, bindingIndex) => this._preprocessUpdateExpression(
|
|
{nodeIndex, bindingIndex, sourceSpan: ast.sourceSpan, context: COMP_VAR, value: expr}));
|
|
|
|
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
|
this.nodes[nodeIndex] = () => ({
|
|
sourceSpan: ast.sourceSpan,
|
|
nodeFlags: NodeFlags.TypeText,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
|
o.literal(ast.ngContentIndex), o.literalArr(inter.strings.map(s => o.literal(s)))
|
|
]),
|
|
updateRenderer: updateRendererExpressions
|
|
});
|
|
}
|
|
|
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
|
const nodeIndex = this.nodes.length;
|
|
// reserve the space in the nodeDefs array
|
|
this.nodes.push(null);
|
|
|
|
const {flags, queryMatchesExpr, hostEvents} = this._visitElementOrTemplate(nodeIndex, ast);
|
|
|
|
const childVisitor = this.viewBuilderFactory(this);
|
|
this.children.push(childVisitor);
|
|
childVisitor.visitAll(ast.variables, ast.children);
|
|
|
|
const childCount = this.nodes.length - nodeIndex - 1;
|
|
|
|
// anchorDef(
|
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
|
// childCount: number, handleEventFn?: ElementHandleEventFn, templateFactory?:
|
|
// ViewDefinitionFactory): NodeDef;
|
|
this.nodes[nodeIndex] = () => ({
|
|
sourceSpan: ast.sourceSpan,
|
|
nodeFlags: NodeFlags.TypeElement | flags,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
|
|
o.literal(flags),
|
|
queryMatchesExpr,
|
|
o.literal(ast.ngContentIndex),
|
|
o.literal(childCount),
|
|
this._createElementHandleEventFn(nodeIndex, hostEvents),
|
|
o.variable(childVisitor.viewName),
|
|
])
|
|
});
|
|
}
|
|
|
|
visitElement(ast: ElementAst, context: any): any {
|
|
const nodeIndex = this.nodes.length;
|
|
// reserve the space in the nodeDefs array so we can add children
|
|
this.nodes.push(null);
|
|
|
|
let elName = ast.name;
|
|
if (ast.name === NG_CONTAINER_TAG) {
|
|
// Using a null element name creates an anchor.
|
|
elName = null;
|
|
}
|
|
|
|
const {flags, usedEvents, queryMatchesExpr, hostBindings: dirHostBindings, hostEvents} =
|
|
this._visitElementOrTemplate(nodeIndex, ast);
|
|
|
|
let inputDefs: o.Expression[] = [];
|
|
let updateRendererExpressions: UpdateExpression[] = [];
|
|
let outputDefs: o.Expression[] = [];
|
|
if (elName) {
|
|
const hostBindings = ast.inputs
|
|
.map((inputAst) => ({
|
|
context: COMP_VAR as o.Expression,
|
|
inputAst,
|
|
dirAst: null,
|
|
}))
|
|
.concat(dirHostBindings);
|
|
if (hostBindings.length) {
|
|
updateRendererExpressions =
|
|
hostBindings.map((hostBinding, bindingIndex) => this._preprocessUpdateExpression({
|
|
context: hostBinding.context,
|
|
nodeIndex,
|
|
bindingIndex,
|
|
sourceSpan: hostBinding.inputAst.sourceSpan,
|
|
value: hostBinding.inputAst.value
|
|
}));
|
|
inputDefs = hostBindings.map(
|
|
hostBinding => elementBindingDef(hostBinding.inputAst, hostBinding.dirAst));
|
|
}
|
|
outputDefs = usedEvents.map(
|
|
([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)]));
|
|
}
|
|
|
|
templateVisitAll(this, ast.children);
|
|
|
|
const childCount = this.nodes.length - nodeIndex - 1;
|
|
|
|
const compAst = ast.directives.find(dirAst => dirAst.directive.isComponent);
|
|
let compRendererType = o.NULL_EXPR;
|
|
let compView = o.NULL_EXPR;
|
|
if (compAst) {
|
|
compView = o.importExpr({reference: compAst.directive.componentViewType});
|
|
compRendererType = o.importExpr({reference: compAst.directive.rendererType});
|
|
}
|
|
|
|
// elementDef(
|
|
// flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
|
|
// ngContentIndex: number, childCount: number, namespaceAndName: string,
|
|
// fixedAttrs: [string, string][] = [],
|
|
// bindings?: [BindingFlags, string, string | SecurityContext][],
|
|
// outputs?: ([OutputType.ElementOutput | OutputType.DirectiveHostOutput, string, string])[],
|
|
// handleEvent?: ElementHandleEventFn,
|
|
// componentView?: () => ViewDefinition, componentRendererType?: RendererType2): NodeDef;
|
|
this.nodes[nodeIndex] = () => ({
|
|
sourceSpan: ast.sourceSpan,
|
|
nodeFlags: NodeFlags.TypeElement | flags,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
|
|
o.literal(flags),
|
|
queryMatchesExpr,
|
|
o.literal(ast.ngContentIndex),
|
|
o.literal(childCount),
|
|
o.literal(elName),
|
|
elName ? fixedAttrsDef(ast) : o.NULL_EXPR,
|
|
inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
|
|
outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR,
|
|
this._createElementHandleEventFn(nodeIndex, hostEvents),
|
|
compView,
|
|
compRendererType,
|
|
]),
|
|
updateRenderer: updateRendererExpressions
|
|
});
|
|
}
|
|
|
|
private _visitElementOrTemplate(nodeIndex: number, ast: {
|
|
hasViewContainer: boolean,
|
|
outputs: BoundEventAst[],
|
|
directives: DirectiveAst[],
|
|
providers: ProviderAst[],
|
|
references: ReferenceAst[],
|
|
queryMatches: QueryMatch[]
|
|
}): {
|
|
flags: NodeFlags,
|
|
usedEvents: [string, string][],
|
|
queryMatchesExpr: o.Expression,
|
|
hostBindings:
|
|
{context: o.Expression, inputAst: BoundElementPropertyAst, dirAst: DirectiveAst}[],
|
|
hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[],
|
|
} {
|
|
let flags = NodeFlags.None;
|
|
if (ast.hasViewContainer) {
|
|
flags |= NodeFlags.EmbeddedViews;
|
|
}
|
|
const usedEvents = new Map<string, [string, string]>();
|
|
ast.outputs.forEach((event) => {
|
|
const {name, target} = elementEventNameAndTarget(event, null);
|
|
usedEvents.set(elementEventFullName(target, name), [target, name]);
|
|
});
|
|
ast.directives.forEach((dirAst) => {
|
|
dirAst.hostEvents.forEach((event) => {
|
|
const {name, target} = elementEventNameAndTarget(event, dirAst);
|
|
usedEvents.set(elementEventFullName(target, name), [target, name]);
|
|
});
|
|
});
|
|
const hostBindings:
|
|
{context: o.Expression, inputAst: BoundElementPropertyAst, dirAst: DirectiveAst}[] = [];
|
|
const hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] = [];
|
|
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 === tokenReference(providerAst.token)) {
|
|
dirAst = localDirAst;
|
|
dirIndex = i;
|
|
}
|
|
});
|
|
if (dirAst) {
|
|
const {hostBindings: dirHostBindings, hostEvents: dirHostEvents} = this._visitDirective(
|
|
providerAst, dirAst, dirIndex, nodeIndex, ast.references, ast.queryMatches, usedEvents,
|
|
this.staticQueryIds.get(<any>ast));
|
|
hostBindings.push(...dirHostBindings);
|
|
hostEvents.push(...dirHostEvents);
|
|
} else {
|
|
this._visitProvider(providerAst, ast.queryMatches);
|
|
}
|
|
});
|
|
|
|
let queryMatchExprs: o.Expression[] = [];
|
|
ast.queryMatches.forEach((match) => {
|
|
let valueType: QueryValueType;
|
|
if (tokenReference(match.value) === resolveIdentifier(Identifiers.ElementRef)) {
|
|
valueType = QueryValueType.ElementRef;
|
|
} else if (tokenReference(match.value) === resolveIdentifier(Identifiers.ViewContainerRef)) {
|
|
valueType = QueryValueType.ViewContainerRef;
|
|
} else if (tokenReference(match.value) === resolveIdentifier(Identifiers.TemplateRef)) {
|
|
valueType = QueryValueType.TemplateRef;
|
|
}
|
|
if (valueType != null) {
|
|
queryMatchExprs.push(o.literalArr([o.literal(match.queryId), o.literal(valueType)]));
|
|
}
|
|
});
|
|
ast.references.forEach((ref) => {
|
|
let valueType: QueryValueType;
|
|
if (!ref.value) {
|
|
valueType = QueryValueType.RenderElement;
|
|
} else if (tokenReference(ref.value) === resolveIdentifier(Identifiers.TemplateRef)) {
|
|
valueType = QueryValueType.TemplateRef;
|
|
}
|
|
if (valueType != null) {
|
|
this.refNodeIndices[ref.name] = nodeIndex;
|
|
queryMatchExprs.push(o.literalArr([o.literal(ref.name), o.literal(valueType)]));
|
|
}
|
|
});
|
|
ast.outputs.forEach((outputAst) => {
|
|
hostEvents.push({context: COMP_VAR, eventAst: outputAst, dirAst: null});
|
|
});
|
|
|
|
return {
|
|
flags,
|
|
usedEvents: Array.from(usedEvents.values()),
|
|
queryMatchesExpr: queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
|
|
hostBindings,
|
|
hostEvents
|
|
};
|
|
}
|
|
|
|
private _visitDirective(
|
|
providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number,
|
|
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
|
|
usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): {
|
|
hostBindings:
|
|
{context: o.Expression, inputAst: BoundElementPropertyAst, dirAst: DirectiveAst}[],
|
|
hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[]
|
|
} {
|
|
const nodeIndex = this.nodes.length;
|
|
// reserve the space in the nodeDefs array so we can add children
|
|
this.nodes.push(null);
|
|
|
|
dirAst.directive.queries.forEach((query, queryIndex) => {
|
|
let flags = NodeFlags.TypeContentQuery;
|
|
const queryId = dirAst.contentQueryStartId + queryIndex;
|
|
// Note: We only make queries static that query for a single item.
|
|
// This is because of backwards compatibility with the old view compiler...
|
|
if (queryIds.staticQueryIds.has(queryId) && query.first) {
|
|
flags |= NodeFlags.StaticQuery;
|
|
} else {
|
|
flags |= NodeFlags.DynamicQuery;
|
|
}
|
|
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
|
|
this.nodes.push(() => ({
|
|
sourceSpan: dirAst.sourceSpan,
|
|
nodeFlags: flags,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
|
o.literal(flags), o.literal(queryId),
|
|
new o.LiteralMapExpr(
|
|
[new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
|
]),
|
|
}));
|
|
});
|
|
|
|
// Note: the operation below might also create new nodeDefs,
|
|
// but we don't want them to be a child of a directive,
|
|
// as they might be a provider/pipe on their own.
|
|
// I.e. we only allow queries as children of directives nodes.
|
|
const childCount = this.nodes.length - nodeIndex - 1;
|
|
|
|
let {flags, queryMatchExprs, providerExpr, depsExpr} =
|
|
this._visitProviderOrDirective(providerAst, queryMatches);
|
|
|
|
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(QueryValueType.Provider)]));
|
|
}
|
|
});
|
|
|
|
if (dirAst.directive.isComponent) {
|
|
flags |= NodeFlags.Component;
|
|
}
|
|
|
|
const inputDefs = dirAst.inputs.map((inputAst, inputIndex) => {
|
|
const mapValue = o.literalArr([o.literal(inputIndex), o.literal(inputAst.directiveName)]);
|
|
// Note: it's important to not quote the key so that we can capture renames by minifiers!
|
|
return new o.LiteralMapEntry(inputAst.directiveName, mapValue, false);
|
|
});
|
|
|
|
const outputDefs: o.LiteralMapEntry[] = [];
|
|
const dirMeta = dirAst.directive;
|
|
Object.keys(dirMeta.outputs).forEach((propName) => {
|
|
const eventName = dirMeta.outputs[propName];
|
|
if (usedEvents.has(eventName)) {
|
|
// Note: it's important to not quote the key so that we can capture renames by minifiers!
|
|
outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false));
|
|
}
|
|
});
|
|
let updateDirectiveExpressions: UpdateExpression[] = [];
|
|
if (dirAst.inputs.length || (flags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0) {
|
|
updateDirectiveExpressions =
|
|
dirAst.inputs.map((input, bindingIndex) => this._preprocessUpdateExpression({
|
|
nodeIndex,
|
|
bindingIndex,
|
|
sourceSpan: input.sourceSpan,
|
|
context: COMP_VAR,
|
|
value: input.value
|
|
}));
|
|
}
|
|
|
|
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
|
VIEW_VAR, o.literal(nodeIndex)
|
|
]);
|
|
const hostBindings = dirAst.hostProperties.map((inputAst) => ({
|
|
context: dirContextExpr,
|
|
dirAst,
|
|
inputAst,
|
|
}));
|
|
const hostEvents = dirAst.hostEvents.map((hostEventAst) => ({
|
|
context: dirContextExpr,
|
|
eventAst: hostEventAst, dirAst,
|
|
}));
|
|
|
|
|
|
// directiveDef(
|
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor:
|
|
// any,
|
|
// deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
|
|
// outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef;
|
|
this.nodes[nodeIndex] = () => ({
|
|
sourceSpan: dirAst.sourceSpan,
|
|
nodeFlags: NodeFlags.TypeDirective | flags,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.directiveDef)).callFn([
|
|
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
|
|
o.literal(childCount), providerExpr, depsExpr,
|
|
inputDefs.length ? new o.LiteralMapExpr(inputDefs) : o.NULL_EXPR,
|
|
outputDefs.length ? new o.LiteralMapExpr(outputDefs) : o.NULL_EXPR
|
|
]),
|
|
updateDirectives: updateDirectiveExpressions,
|
|
directive: dirAst.directive.type,
|
|
});
|
|
|
|
return {hostBindings, hostEvents};
|
|
}
|
|
|
|
private _visitProvider(providerAst: ProviderAst, queryMatches: QueryMatch[]): void {
|
|
const nodeIndex = this.nodes.length;
|
|
// reserve the space in the nodeDefs array so we can add children
|
|
this.nodes.push(null);
|
|
|
|
const {flags, queryMatchExprs, providerExpr, depsExpr} =
|
|
this._visitProviderOrDirective(providerAst, queryMatches);
|
|
|
|
// providerDef(
|
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], token:any,
|
|
// value: any, deps: ([DepFlags, any] | any)[]): NodeDef;
|
|
this.nodes[nodeIndex] = () => ({
|
|
sourceSpan: providerAst.sourceSpan,
|
|
nodeFlags: flags,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([
|
|
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
|
|
tokenExpr(providerAst.token), providerExpr, depsExpr
|
|
])
|
|
});
|
|
}
|
|
|
|
private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): {
|
|
flags: NodeFlags,
|
|
queryMatchExprs: o.Expression[],
|
|
providerExpr: o.Expression,
|
|
depsExpr: o.Expression
|
|
} {
|
|
let flags = NodeFlags.None;
|
|
if (!providerAst.eager) {
|
|
flags |= NodeFlags.LazyProvider;
|
|
}
|
|
if (providerAst.providerType === ProviderAstType.PrivateService) {
|
|
flags |= NodeFlags.PrivateProvider;
|
|
}
|
|
providerAst.lifecycleHooks.forEach((lifecycleHook) => {
|
|
// for regular providers, we only support ngOnDestroy
|
|
if (lifecycleHook === LifecycleHooks.OnDestroy ||
|
|
providerAst.providerType === ProviderAstType.Directive ||
|
|
providerAst.providerType === ProviderAstType.Component) {
|
|
flags |= lifecycleHookToNodeFlag(lifecycleHook);
|
|
}
|
|
});
|
|
let queryMatchExprs: o.Expression[] = [];
|
|
|
|
queryMatches.forEach((match) => {
|
|
if (tokenReference(match.value) === tokenReference(providerAst.token)) {
|
|
queryMatchExprs.push(
|
|
o.literalArr([o.literal(match.queryId), o.literal(QueryValueType.Provider)]));
|
|
}
|
|
});
|
|
const {providerExpr, depsExpr, flags: providerType} = providerDef(providerAst);
|
|
return {flags: flags | providerType, queryMatchExprs, providerExpr, depsExpr};
|
|
}
|
|
|
|
getLocal(name: string): o.Expression {
|
|
if (name == EventHandlerVars.event.name) {
|
|
return EventHandlerVars.event;
|
|
}
|
|
let currViewExpr: o.Expression = VIEW_VAR;
|
|
for (let currBuilder: ViewBuilder = this; currBuilder;
|
|
currBuilder = currBuilder.parent, currViewExpr = currViewExpr.prop('parent')) {
|
|
// check references
|
|
const refNodeIndex = currBuilder.refNodeIndices[name];
|
|
if (refNodeIndex != null) {
|
|
return o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
|
currViewExpr, o.literal(refNodeIndex)
|
|
]);
|
|
}
|
|
|
|
// check variables
|
|
const varAst = currBuilder.variables.find((varAst) => varAst.name === name);
|
|
if (varAst) {
|
|
const varValue = varAst.value || IMPLICIT_TEMPLATE_VAR;
|
|
return currViewExpr.prop('context').prop(varValue);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
createLiteralArrayConverter(sourceSpan: ParseSourceSpan, argCount: number): BuiltinConverter {
|
|
if (argCount === 0) {
|
|
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
|
|
return () => valueExpr;
|
|
}
|
|
|
|
const nodeIndex = this.nodes.length;
|
|
// pureArrayDef(argCount: number): NodeDef;
|
|
this.nodes.push(
|
|
() => ({
|
|
sourceSpan,
|
|
nodeFlags: NodeFlags.TypePureArray,
|
|
nodeDef:
|
|
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)])
|
|
}));
|
|
|
|
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
|
}
|
|
createLiteralMapConverter(sourceSpan: ParseSourceSpan, keys: string[]): BuiltinConverter {
|
|
if (keys.length === 0) {
|
|
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
|
return () => valueExpr;
|
|
}
|
|
|
|
const nodeIndex = this.nodes.length;
|
|
// function pureObjectDef(propertyNames: string[]): NodeDef
|
|
this.nodes.push(() => ({
|
|
sourceSpan,
|
|
nodeFlags: NodeFlags.TypePureObject,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.pureObjectDef))
|
|
.callFn([o.literalArr(keys.map(key => o.literal(key)))])
|
|
}));
|
|
|
|
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
|
}
|
|
createPipeConverter(expression: UpdateExpression, name: string, argCount: number):
|
|
BuiltinConverter {
|
|
const pipe = this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
|
|
if (pipe.pure) {
|
|
const nodeIndex = this.nodes.length;
|
|
// function purePipeDef(argCount: number): NodeDef;
|
|
this.nodes.push(() => ({
|
|
sourceSpan: expression.sourceSpan,
|
|
nodeFlags: NodeFlags.TypePurePipe,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.purePipeDef))
|
|
.callFn([o.literal(argCount)])
|
|
}));
|
|
|
|
// find underlying pipe in the component view
|
|
let compViewExpr: o.Expression = VIEW_VAR;
|
|
let compBuilder: ViewBuilder = this;
|
|
while (compBuilder.parent) {
|
|
compBuilder = compBuilder.parent;
|
|
compViewExpr = compViewExpr.prop('parent');
|
|
}
|
|
const pipeNodeIndex = compBuilder.purePipeNodeIndices[name];
|
|
const pipeValueExpr: o.Expression =
|
|
o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
|
compViewExpr, o.literal(pipeNodeIndex)
|
|
]);
|
|
|
|
return (args: o.Expression[]) => callUnwrapValue(
|
|
expression.nodeIndex, expression.bindingIndex,
|
|
callCheckStmt(nodeIndex, [pipeValueExpr].concat(args)));
|
|
} else {
|
|
const nodeIndex = this._createPipe(expression.sourceSpan, pipe);
|
|
const nodeValueExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
|
VIEW_VAR, o.literal(nodeIndex)
|
|
]);
|
|
|
|
return (args: o.Expression[]) => callUnwrapValue(
|
|
expression.nodeIndex, expression.bindingIndex,
|
|
nodeValueExpr.callMethod('transform', args));
|
|
}
|
|
}
|
|
|
|
private _createPipe(sourceSpan: ParseSourceSpan, pipe: CompilePipeSummary): number {
|
|
const nodeIndex = this.nodes.length;
|
|
let flags = NodeFlags.None;
|
|
pipe.type.lifecycleHooks.forEach((lifecycleHook) => {
|
|
// for pipes, we only support ngOnDestroy
|
|
if (lifecycleHook === LifecycleHooks.OnDestroy) {
|
|
flags |= lifecycleHookToNodeFlag(lifecycleHook);
|
|
}
|
|
});
|
|
|
|
const depExprs = pipe.type.diDeps.map(depDef);
|
|
// function pipeDef(
|
|
// flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef
|
|
this.nodes.push(() => ({
|
|
sourceSpan,
|
|
nodeFlags: NodeFlags.TypePipe,
|
|
nodeDef: o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
|
|
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
|
|
])
|
|
}));
|
|
return nodeIndex;
|
|
}
|
|
|
|
// Attention: This might create new nodeDefs (for pipes and literal arrays and literal maps)!
|
|
private _preprocessUpdateExpression(expression: UpdateExpression): UpdateExpression {
|
|
return {
|
|
nodeIndex: expression.nodeIndex,
|
|
bindingIndex: expression.bindingIndex,
|
|
sourceSpan: expression.sourceSpan,
|
|
context: expression.context,
|
|
value: convertPropertyBindingBuiltins(
|
|
{
|
|
createLiteralArrayConverter: (argCount: number) => this.createLiteralArrayConverter(
|
|
expression.sourceSpan, argCount),
|
|
createLiteralMapConverter:
|
|
(keys: string[]) => this.createLiteralMapConverter(expression.sourceSpan, keys),
|
|
createPipeConverter: (name: string, argCount: number) =>
|
|
this.createPipeConverter(expression, name, argCount)
|
|
},
|
|
expression.value)
|
|
};
|
|
}
|
|
|
|
private _createNodeExpressions(): {
|
|
updateRendererStmts: o.Statement[],
|
|
updateDirectivesStmts: o.Statement[],
|
|
nodeDefExprs: o.Expression[]
|
|
} {
|
|
const self = this;
|
|
let updateBindingCount = 0;
|
|
const updateRendererStmts: o.Statement[] = [];
|
|
const updateDirectivesStmts: o.Statement[] = [];
|
|
const nodeDefExprs = this.nodes.map((factory, nodeIndex) => {
|
|
const {nodeDef, nodeFlags, updateDirectives, updateRenderer, sourceSpan} = factory();
|
|
if (updateRenderer) {
|
|
updateRendererStmts.push(
|
|
...createUpdateStatements(nodeIndex, sourceSpan, updateRenderer, false));
|
|
}
|
|
if (updateDirectives) {
|
|
updateDirectivesStmts.push(...createUpdateStatements(
|
|
nodeIndex, sourceSpan, updateDirectives,
|
|
(nodeFlags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0));
|
|
}
|
|
// We use a comma expression to call the log function before
|
|
// the nodeDef function, but still use the result of the nodeDef function
|
|
// as the value.
|
|
// Note: We only add the logger to elements / text nodes,
|
|
// so we don't generate too much code.
|
|
const logWithNodeDef = nodeFlags & NodeFlags.CatRenderNode ?
|
|
new o.CommaExpr([LOG_VAR.callFn([]).callFn([]), nodeDef]) :
|
|
nodeDef;
|
|
return o.applySourceSpanToExpressionIfNeeded(logWithNodeDef, sourceSpan);
|
|
});
|
|
return {updateRendererStmts, updateDirectivesStmts, nodeDefExprs};
|
|
|
|
function createUpdateStatements(
|
|
nodeIndex: number, sourceSpan: ParseSourceSpan, expressions: UpdateExpression[],
|
|
allowEmptyExprs: boolean): o.Statement[] {
|
|
const updateStmts: o.Statement[] = [];
|
|
const exprs = expressions.map(({sourceSpan, context, value}) => {
|
|
const bindingId = `${updateBindingCount++}`;
|
|
const nameResolver = context === COMP_VAR ? self : null;
|
|
const {stmts, currValExpr} =
|
|
convertPropertyBinding(nameResolver, context, value, bindingId);
|
|
updateStmts.push(
|
|
...stmts.map(stmt => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
|
return o.applySourceSpanToExpressionIfNeeded(currValExpr, sourceSpan);
|
|
});
|
|
if (expressions.length || allowEmptyExprs) {
|
|
updateStmts.push(o.applySourceSpanToStatementIfNeeded(
|
|
callCheckStmt(nodeIndex, exprs).toStmt(), sourceSpan));
|
|
}
|
|
return updateStmts;
|
|
}
|
|
}
|
|
|
|
private _createElementHandleEventFn(
|
|
nodeIndex: number,
|
|
handlers: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[]) {
|
|
const handleEventStmts: o.Statement[] = [];
|
|
let handleEventBindingCount = 0;
|
|
handlers.forEach(({context, eventAst, dirAst}) => {
|
|
const bindingId = `${handleEventBindingCount++}`;
|
|
const nameResolver = context === COMP_VAR ? this : null;
|
|
const {stmts, allowDefault} =
|
|
convertActionBinding(nameResolver, context, eventAst.handler, bindingId);
|
|
const trueStmts = stmts;
|
|
if (allowDefault) {
|
|
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
|
|
}
|
|
const {target: eventTarget, name: eventName} = elementEventNameAndTarget(eventAst, dirAst);
|
|
const fullEventName = elementEventFullName(eventTarget, eventName);
|
|
handleEventStmts.push(o.applySourceSpanToStatementIfNeeded(
|
|
new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts),
|
|
eventAst.sourceSpan));
|
|
});
|
|
let handleEventFn: o.Expression;
|
|
if (handleEventStmts.length > 0) {
|
|
const preStmts: o.Statement[] =
|
|
[ALLOW_DEFAULT_VAR.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
|
|
if (!this.component.isHost && o.findReadVarNames(handleEventStmts).has(COMP_VAR.name)) {
|
|
preStmts.push(COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(this.compType));
|
|
}
|
|
handleEventFn = o.fn(
|
|
[
|
|
new o.FnParam(VIEW_VAR.name, o.INFERRED_TYPE),
|
|
new o.FnParam(EVENT_NAME_VAR.name, o.INFERRED_TYPE),
|
|
new o.FnParam(EventHandlerVars.event.name, o.INFERRED_TYPE)
|
|
],
|
|
[...preStmts, ...handleEventStmts, new o.ReturnStatement(ALLOW_DEFAULT_VAR)],
|
|
o.INFERRED_TYPE);
|
|
} else {
|
|
handleEventFn = o.NULL_EXPR;
|
|
}
|
|
return handleEventFn;
|
|
}
|
|
|
|
visitDirective(ast: DirectiveAst, context: {usedEvents: Set<string>}): any {}
|
|
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
|
|
visitReference(ast: ReferenceAst, context: any): any {}
|
|
visitVariable(ast: VariableAst, context: any): any {}
|
|
visitEvent(ast: BoundEventAst, context: any): any {}
|
|
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {}
|
|
visitAttr(ast: AttrAst, context: any): any {}
|
|
}
|
|
|
|
function providerDef(providerAst: ProviderAst):
|
|
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
|
|
return providerAst.multiProvider ?
|
|
multiProviderDef(providerAst.providers) :
|
|
singleProviderDef(providerAst.providerType, providerAst.providers[0]);
|
|
}
|
|
|
|
function multiProviderDef(providers: CompileProviderMetadata[]):
|
|
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
|
|
const allDepDefs: o.Expression[] = [];
|
|
const allParams: o.FnParam[] = [];
|
|
const exprs = providers.map((provider, providerIndex) => {
|
|
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);
|
|
}
|
|
return expr;
|
|
});
|
|
const providerExpr =
|
|
o.fn(allParams, [new o.ReturnStatement(o.literalArr(exprs))], o.INFERRED_TYPE);
|
|
return {providerExpr, flags: NodeFlags.TypeFactoryProvider, 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(providerType: ProviderAstType, providerMeta: CompileProviderMetadata):
|
|
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
|
|
let providerExpr: o.Expression;
|
|
let flags: NodeFlags;
|
|
let deps: CompileDiDependencyMetadata[];
|
|
if (providerType === ProviderAstType.Directive || providerType === ProviderAstType.Component) {
|
|
providerExpr = o.importExpr(providerMeta.useClass);
|
|
flags = NodeFlags.TypeDirective;
|
|
deps = providerMeta.deps || providerMeta.useClass.diDeps;
|
|
} else {
|
|
if (providerMeta.useClass) {
|
|
providerExpr = o.importExpr(providerMeta.useClass);
|
|
flags = NodeFlags.TypeClassProvider;
|
|
deps = providerMeta.deps || providerMeta.useClass.diDeps;
|
|
} else if (providerMeta.useFactory) {
|
|
providerExpr = o.importExpr(providerMeta.useFactory);
|
|
flags = NodeFlags.TypeFactoryProvider;
|
|
deps = providerMeta.deps || providerMeta.useFactory.diDeps;
|
|
} else if (providerMeta.useExisting) {
|
|
providerExpr = o.NULL_EXPR;
|
|
flags = NodeFlags.TypeUseExistingProvider;
|
|
deps = [{token: providerMeta.useExisting}];
|
|
} else {
|
|
providerExpr = convertValueToOutputAst(providerMeta.useValue);
|
|
flags = NodeFlags.TypeValueProvider;
|
|
deps = [];
|
|
}
|
|
}
|
|
const depsExpr = o.literalArr(deps.map(dep => depDef(dep)));
|
|
return {providerExpr, flags, depsExpr};
|
|
}
|
|
|
|
function tokenExpr(tokenMeta: CompileTokenMetadata): o.Expression {
|
|
return tokenMeta.identifier ? o.importExpr(tokenMeta.identifier) : o.literal(tokenMeta.value);
|
|
}
|
|
|
|
function depDef(dep: CompileDiDependencyMetadata): o.Expression {
|
|
// Note: the following fields have already been normalized out by provider_analyzer:
|
|
// - isAttribute, isSelf, isHost
|
|
const expr = dep.isValue ? convertValueToOutputAst(dep.value) : tokenExpr(dep.token);
|
|
let flags = DepFlags.None;
|
|
if (dep.isSkipSelf) {
|
|
flags |= DepFlags.SkipSelf;
|
|
}
|
|
if (dep.isOptional) {
|
|
flags |= DepFlags.Optional;
|
|
}
|
|
if (dep.isValue) {
|
|
flags |= DepFlags.Value;
|
|
}
|
|
return flags === DepFlags.None ? expr : o.literalArr([o.literal(flags), expr]);
|
|
}
|
|
|
|
function needsAdditionalRootNode(astNodes: TemplateAst[]): boolean {
|
|
const lastAstNode = astNodes[astNodes.length - 1];
|
|
if (lastAstNode instanceof EmbeddedTemplateAst) {
|
|
return lastAstNode.hasViewContainer;
|
|
}
|
|
|
|
if (lastAstNode instanceof ElementAst) {
|
|
if (lastAstNode.name === NG_CONTAINER_TAG && lastAstNode.children.length) {
|
|
return needsAdditionalRootNode(lastAstNode.children);
|
|
}
|
|
return lastAstNode.hasViewContainer;
|
|
}
|
|
|
|
return lastAstNode instanceof NgContentAst;
|
|
}
|
|
|
|
function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags {
|
|
let nodeFlag = NodeFlags.None;
|
|
switch (lifecycleHook) {
|
|
case LifecycleHooks.AfterContentChecked:
|
|
nodeFlag = NodeFlags.AfterContentChecked;
|
|
break;
|
|
case LifecycleHooks.AfterContentInit:
|
|
nodeFlag = NodeFlags.AfterContentInit;
|
|
break;
|
|
case LifecycleHooks.AfterViewChecked:
|
|
nodeFlag = NodeFlags.AfterViewChecked;
|
|
break;
|
|
case LifecycleHooks.AfterViewInit:
|
|
nodeFlag = NodeFlags.AfterViewInit;
|
|
break;
|
|
case LifecycleHooks.DoCheck:
|
|
nodeFlag = NodeFlags.DoCheck;
|
|
break;
|
|
case LifecycleHooks.OnChanges:
|
|
nodeFlag = NodeFlags.OnChanges;
|
|
break;
|
|
case LifecycleHooks.OnDestroy:
|
|
nodeFlag = NodeFlags.OnDestroy;
|
|
break;
|
|
case LifecycleHooks.OnInit:
|
|
nodeFlag = NodeFlags.OnInit;
|
|
break;
|
|
}
|
|
return nodeFlag;
|
|
}
|
|
|
|
function elementBindingDef(inputAst: BoundElementPropertyAst, dirAst: DirectiveAst): o.Expression {
|
|
switch (inputAst.type) {
|
|
case PropertyBindingType.Attribute:
|
|
return o.literalArr([
|
|
o.literal(BindingFlags.TypeElementAttribute), o.literal(inputAst.name),
|
|
o.literal(inputAst.securityContext)
|
|
]);
|
|
case PropertyBindingType.Property:
|
|
return o.literalArr([
|
|
o.literal(BindingFlags.TypeProperty), o.literal(inputAst.name),
|
|
o.literal(inputAst.securityContext)
|
|
]);
|
|
case PropertyBindingType.Animation:
|
|
const bindingType = BindingFlags.TypeProperty |
|
|
(dirAst && dirAst.directive.isComponent ? BindingFlags.SyntheticHostProperty :
|
|
BindingFlags.SyntheticProperty);
|
|
return o.literalArr([
|
|
o.literal(bindingType), o.literal('@' + inputAst.name), o.literal(inputAst.securityContext)
|
|
]);
|
|
case PropertyBindingType.Class:
|
|
return o.literalArr(
|
|
[o.literal(BindingFlags.TypeElementClass), o.literal(inputAst.name), o.NULL_EXPR]);
|
|
case PropertyBindingType.Style:
|
|
return o.literalArr([
|
|
o.literal(BindingFlags.TypeElementStyle), o.literal(inputAst.name), o.literal(inputAst.unit)
|
|
]);
|
|
}
|
|
}
|
|
|
|
|
|
function fixedAttrsDef(elementAst: ElementAst): o.Expression {
|
|
const mapResult: {[key: string]: string} = Object.create(null);
|
|
elementAst.attrs.forEach(attrAst => { mapResult[attrAst.name] = attrAst.value; });
|
|
elementAst.directives.forEach(dirAst => {
|
|
Object.keys(dirAst.directive.hostAttributes).forEach(name => {
|
|
const value = dirAst.directive.hostAttributes[name];
|
|
const prevValue = mapResult[name];
|
|
mapResult[name] = prevValue != null ? mergeAttributeValue(name, prevValue, value) : value;
|
|
});
|
|
});
|
|
const mapEntries: o.LiteralMapEntry[] = [];
|
|
// Note: We need to sort to get a defined output order
|
|
// for tests and for caching generated artifacts...
|
|
return o.literalArr(Object.keys(mapResult).sort().map(
|
|
(attrName) => o.literalArr([o.literal(attrName), o.literal(mapResult[attrName])])));
|
|
}
|
|
|
|
function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string {
|
|
if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
|
|
return `${attrValue1} ${attrValue2}`;
|
|
} else {
|
|
return attrValue2;
|
|
}
|
|
}
|
|
|
|
function callCheckStmt(nodeIndex: number, exprs: o.Expression[]): o.Expression {
|
|
if (exprs.length > 10) {
|
|
return CHECK_VAR.callFn(
|
|
[VIEW_VAR, o.literal(nodeIndex), o.literal(ArgumentType.Dynamic), o.literalArr(exprs)]);
|
|
} else {
|
|
return CHECK_VAR.callFn(
|
|
[VIEW_VAR, o.literal(nodeIndex), o.literal(ArgumentType.Inline), ...exprs]);
|
|
}
|
|
}
|
|
|
|
function callUnwrapValue(nodeIndex: number, bindingIdx: number, expr: o.Expression): o.Expression {
|
|
return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([
|
|
VIEW_VAR, o.literal(nodeIndex), o.literal(bindingIdx), 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 staticViewQueryIds(nodeStaticQueryIds: Map<TemplateAst, StaticAndDynamicQueryIds>):
|
|
StaticAndDynamicQueryIds {
|
|
const staticQueryIds = new Set<number>();
|
|
const dynamicQueryIds = new Set<number>();
|
|
Array.from(nodeStaticQueryIds.values()).forEach((entry) => {
|
|
entry.staticQueryIds.forEach(queryId => staticQueryIds.add(queryId));
|
|
entry.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId));
|
|
});
|
|
dynamicQueryIds.forEach(queryId => staticQueryIds.delete(queryId));
|
|
return {staticQueryIds, dynamicQueryIds};
|
|
}
|
|
|
|
function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst {
|
|
const componentDirMeta = directives.find(dirAst => dirAst.directive.isComponent);
|
|
if (componentDirMeta && componentDirMeta.directive.entryComponents.length) {
|
|
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
|
|
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
|
|
|
|
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
|
|
|
|
const classMeta: CompileTypeMetadata = {
|
|
diDeps: [
|
|
{isValue: true, value: o.literalArr(entryComponentFactories)},
|
|
{token: token, isSkipSelf: true, isOptional: true},
|
|
{token: createIdentifierToken(Identifiers.NgModuleRef)},
|
|
],
|
|
lifecycleHooks: [],
|
|
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
|
|
};
|
|
return new ProviderAst(
|
|
token, false, true, [{token, multi: false, useClass: classMeta}],
|
|
ProviderAstType.PrivateService, [], componentDirMeta.sourceSpan);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function elementEventNameAndTarget(
|
|
eventAst: BoundEventAst, dirAst: DirectiveAst): {name: string, target: string} {
|
|
if (eventAst.isAnimation) {
|
|
return {
|
|
name: `@${eventAst.name}.${eventAst.phase}`,
|
|
target: dirAst && dirAst.directive.isComponent ? 'component' : null
|
|
};
|
|
} else {
|
|
return eventAst;
|
|
}
|
|
}
|