1201 lines
50 KiB
TypeScript
1201 lines
50 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, ɵ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, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, CompileTypeMetadata, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
|
import {BuiltinConverter, 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 = undefined !;
|
|
if (!component.isHost) {
|
|
const template = component.template !;
|
|
const customRenderData: o.LiteralMapEntry[] = [];
|
|
if (template.animations && template.animations.length) {
|
|
customRenderData.push(
|
|
new o.LiteralMapEntry('animation', convertValueToOutputAst(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(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 | null): 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 | null,
|
|
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|null, 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;
|
|
const flags =
|
|
NodeFlags.TypeViewQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first);
|
|
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: string|null = 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: any[] = ast.inputs
|
|
.map((inputAst) => ({
|
|
context: COMP_VAR as o.Expression,
|
|
inputAst,
|
|
dirAst: null as any,
|
|
}))
|
|
.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 | null, 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 | null, 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 = undefined !;
|
|
let dirIndex: number = undefined !;
|
|
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 = undefined !;
|
|
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 = undefined !;
|
|
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: 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) => {
|
|
const queryId = dirAst.contentQueryStartId + queryIndex;
|
|
const flags =
|
|
NodeFlags.TypeContentQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first);
|
|
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|null {
|
|
if (name == EventHandlerVars.event.name) {
|
|
return EventHandlerVars.event;
|
|
}
|
|
let currViewExpr: o.Expression = VIEW_VAR;
|
|
for (let currBuilder: ViewBuilder|null = this; currBuilder; currBuilder = currBuilder.parent,
|
|
currViewExpr = currViewExpr.prop('parent').cast(o.DYNAMIC_TYPE)) {
|
|
// 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').cast(o.DYNAMIC_TYPE);
|
|
}
|
|
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|null, 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 | null, 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.Statement) => 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[] = undefined !;
|
|
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|null {
|
|
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 | null): {name: string, target: string | null} {
|
|
if (eventAst.isAnimation) {
|
|
return {
|
|
name: `@${eventAst.name}.${eventAst.phase}`,
|
|
target: dirAst && dirAst.directive.isComponent ? 'component' : null
|
|
};
|
|
} else {
|
|
return eventAst;
|
|
}
|
|
}
|
|
|
|
function calcStaticDynamicQueryFlags(
|
|
queryIds: StaticAndDynamicQueryIds, queryId: number, isFirst: boolean) {
|
|
let flags = NodeFlags.None;
|
|
// Note: We only make queries static that query for a single item.
|
|
// This is because of backwards compatibility with the old view compiler...
|
|
if (isFirst && (queryIds.staticQueryIds.has(queryId) || !queryIds.dynamicQueryIds.has(queryId))) {
|
|
flags |= NodeFlags.StaticQuery;
|
|
} else {
|
|
flags |= NodeFlags.DynamicQuery;
|
|
}
|
|
return flags;
|
|
}
|