feat(compiler): implement "enableIvy" compiler option (#21427)

The "enableIvy" compiler option is the initial implementation
of the Render3 (or Ivy) code generation. This commit enables
generation generating "Hello, World" (example in the test)
but not much else. It is currenly only useful for internal Ivy
testing as Ivy is in development.

PR Close #21427
This commit is contained in:
Chuck Jazdzewski
2017-11-20 10:21:17 -08:00
committed by Miško Hevery
parent ce8b5877e2
commit 64d16dee02
27 changed files with 1484 additions and 47 deletions

View File

@ -8,6 +8,7 @@
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {ConstantPool} from '../constant_pool';
import {ViewEncapsulation} from '../core';
import {MessageBundle} from '../i18n/message_bundle';
import {Identifiers, createTokenForExternalReference} from '../identifiers';
@ -18,11 +19,12 @@ import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {ParseError} from '../parse_util';
import {compileComponent as compileIvyComponent} from '../render3/r3_view_compiler';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateAst} from '../template_parser/template_ast';
import {TemplateParser} from '../template_parser/template_parser';
import {OutputContext, ValueVisitor, syntaxError, visitValue} from '../util';
import {OutputContext, ValueVisitor, error, syntaxError, visitValue} from '../util';
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
@ -30,6 +32,7 @@ import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {GeneratedFile} from './generated_file';
import {LazyRoute, listLazyRoutes, parseLazyRoute} from './lazy_routes';
import {PartialModule} from './partial_module';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
@ -304,6 +307,45 @@ export class AotCompiler {
return messageBundle;
}
emitAllPartialModules({ngModuleByPipeOrDirective, files}: NgAnalyzedModules): PartialModule[] {
// Using reduce like this is a select many pattern (where map is a select pattern)
return files.reduce<PartialModule[]>((r, file) => {
r.push(...this._emitPartialModule(
file.fileName, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables));
return r;
}, []);
}
private _emitPartialModule(
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
injectables: StaticSymbol[]): PartialModule[] {
const classes: o.ClassStmt[] = [];
const context = this._createOutputContext(fileName);
// Process all components
directives.forEach(directiveType => {
const directiveMetadata = this._metadataResolver.getDirectiveMetadata(directiveType);
if (directiveMetadata.isComponent) {
const module = ngModuleByPipeOrDirective.get(directiveType) !;
module ||
error(
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
const {template: parsedTemplate} =
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
}
});
if (context.statements) {
return [{fileName, statements: [...context.constantPool.statements, ...context.statements]}];
}
return [];
}
emitAllImpls(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
const {ngModuleByPipeOrDirective, files} = analyzeResult;
const sourceModules = files.map(
@ -547,7 +589,7 @@ export class AotCompiler {
new o.ExternalReference(moduleName, name, null), allTypeParams));
};
return {statements: [], genFilePath, importExpr};
return {statements: [], genFilePath, importExpr, constantPool: new ConstantPool()};
}
private _fileNameToModuleName(importedFilePath: string, containingFilePath: string): string {

View File

@ -0,0 +1,14 @@
/**
* @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 * as o from '../output/output_ast';
export interface PartialModule {
fileName: string;
statements: o.Statement[];
}

View File

@ -36,6 +36,7 @@ export * from './aot/generated_file';
export * from './aot/compiler_options';
export * from './aot/compiler_host';
export * from './aot/formatted_error';
export * from './aot/partial_module';
export * from './aot/static_reflector';
export * from './aot/static_symbol';
export * from './aot/static_symbol_resolver';
@ -66,7 +67,7 @@ export * from './ml_parser/html_tags';
export * from './ml_parser/interpolation_config';
export * from './ml_parser/tags';
export {NgModuleCompiler} from './ng_module_compiler';
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
export {EmitterVisitorContext} from './output/abstract_emitter';
export * from './output/ts_emitter';
export * from './parse_util';

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as cdAst from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast';
@ -19,13 +18,15 @@ export class ConvertActionBindingResult {
constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {}
}
export type InterpolationFunction = (args: o.Expression[]) => o.Expression;
/**
* Converts the given expression AST into an executable output AST, assuming the expression is
* used in an action binding (e.g. an event handler).
*/
export function convertActionBinding(
localResolver: LocalResolver | null, implicitReceiver: o.Expression, action: cdAst.AST,
bindingId: string): ConvertActionBindingResult {
bindingId: string, interpolationFunction?: InterpolationFunction): ConvertActionBindingResult {
if (!localResolver) {
localResolver = new DefaultLocalResolver();
}
@ -52,7 +53,8 @@ export function convertActionBinding(
},
action);
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
const visitor =
new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction);
const actionStmts: o.Statement[] = [];
flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
@ -98,6 +100,7 @@ export enum BindingForm {
// otherise generate a general binding
TrySimple,
}
/**
* Converts the given expression AST into an executable output AST, assuming the expression
* is used in property binding. The expression has to be preprocessed via
@ -105,14 +108,15 @@ export enum BindingForm {
*/
export function convertPropertyBinding(
localResolver: LocalResolver | null, implicitReceiver: o.Expression,
expressionWithoutBuiltins: cdAst.AST, bindingId: string,
form: BindingForm): ConvertPropertyBindingResult {
expressionWithoutBuiltins: cdAst.AST, bindingId: string, form: BindingForm,
interpolationFunction?: InterpolationFunction): ConvertPropertyBindingResult {
if (!localResolver) {
localResolver = new DefaultLocalResolver();
}
const currValExpr = createCurrValueExpr(bindingId);
const stmts: o.Statement[] = [];
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
const visitor =
new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction);
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
if (visitor.temporaryCount) {
@ -200,7 +204,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
constructor(
private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
private bindingId: string) {}
private bindingId: string, private interpolationFunction: InterpolationFunction|undefined) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
let op: o.BinaryOperator;
@ -303,6 +307,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
}
args.push(o.literal(ast.strings[ast.strings.length - 1]));
if (this.interpolationFunction) {
return this.interpolationFunction(args);
}
return ast.expressions.length <= 9 ?
o.importExpr(Identifiers.inlineInterpolate).callFn(args) :
o.importExpr(Identifiers.interpolate).callFn([args[0], o.literalArr(args.slice(1))]);

View File

@ -0,0 +1,141 @@
/**
* @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 * as o from './output/output_ast';
import {OutputContext, error} from './util';
export const enum DefinitionKind {Injector, Directive, Component}
/**
* A node that is a place-holder that allows the node to be replaced when the actual
* node is known.
*
* This allows the constant pool to change an expression from a direct reference to
* a constant to a shared constant. It returns a fix-up node that is later allowed to
* change the referenced expression.
*/
class FixupExpression extends o.Expression {
constructor(public resolved: o.Expression) { super(resolved.type); }
shared: boolean;
visitExpression(visitor: o.ExpressionVisitor, context: any): any {
this.resolved.visitExpression(visitor, context);
}
isEquivalent(e: o.Expression): boolean {
return e instanceof FixupExpression && this.resolved.isEquivalent(e.resolved);
}
fixup(expression: o.Expression) {
this.resolved = expression;
this.shared = true;
}
}
/**
* A constant pool allows a code emitter to share constant in an output context.
*
* The constant pool also supports sharing access to ivy definitions references.
*/
export class ConstantPool {
statements: o.Statement[] = [];
private literals = new Map<string, FixupExpression>();
private injectorDefinitions = new Map<any, FixupExpression>();
private directiveDefinitions = new Map<any, FixupExpression>();
private componentDefintions = new Map<any, FixupExpression>();
private nextNameIndex = 0;
getConstLiteral(literal: o.Expression): o.Expression {
const key = this.keyOf(literal);
let fixup = this.literals.get(key);
if (!fixup) {
fixup = new FixupExpression(literal);
this.literals.set(key, fixup);
} else if (!fixup.shared) {
// Replace the expression with a variable
const name = this.freshName();
this.statements.push(
o.variable(name).set(literal).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
fixup.fixup(o.variable(name));
}
return fixup;
}
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
const declarations = kind == DefinitionKind.Component ?
this.componentDefintions :
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
let fixup = declarations.get(type);
if (!fixup) {
const property = kind == DefinitionKind.Component ?
'ngComponentDef' :
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
declarations.set(type, fixup);
} else if (!fixup.shared) {
const name = this.freshName();
this.statements.push(
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
fixup.fixup(o.variable(name));
}
return fixup;
}
/**
* Produce a unique name.
*
* The name might be unique among different prefixes if any of the prefixes end in
* a digit so the prefix should be a constant string (not based on user input) and
* must not end in a digit.
*/
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
private freshName(): string { return this.uniqueName(`_$`); }
private keyOf(expression: o.Expression) {
return expression.visitExpression(new KeyVisitor(), null);
}
}
class KeyVisitor implements o.ExpressionVisitor {
visitLiteralExpr(ast: o.LiteralExpr): string { return `${ast.value}`; }
visitLiteralArrayExpr(ast: o.LiteralArrayExpr): string {
return ast.entries.map(entry => entry.visitExpression(this, null)).join(',');
}
visitLiteralMapExpr(ast: o.LiteralMapExpr): string {
const entries =
ast.entries.map(entry => `${entry.key}:${entry.value.visitExpression(this, null)}`);
return `{${entries.join(',')}`;
}
visitReadVarExpr = invalid;
visitWriteVarExpr = invalid;
visitWriteKeyExpr = invalid;
visitWritePropExpr = invalid;
visitInvokeMethodExpr = invalid;
visitInvokeFunctionExpr = invalid;
visitInstantiateExpr = invalid;
visitExternalExpr = invalid;
visitConditionalExpr = invalid;
visitNotExpr = invalid;
visitAssertNotNullExpr = invalid;
visitCastExpr = invalid;
visitFunctionExpr = invalid;
visitBinaryOperatorExpr = invalid;
visitReadPropExpr = invalid;
visitReadKeyExpr = invalid;
visitCommaExpr = invalid;
}
function invalid<T>(arg: o.Expression | o.Statement): never {
throw new Error(
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
}

View File

@ -9,6 +9,7 @@
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileTypeSummary, ProviderMeta, ProxyClass, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {CompilerConfig} from '../config';
import {ConstantPool} from '../constant_pool';
import {Type} from '../core';
import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from '../ng_module_compiler';
@ -355,5 +356,5 @@ function assertComponent(meta: CompileDirectiveMetadata) {
function createOutputContext(): OutputContext {
const importExpr = (symbol: any) =>
ir.importExpr({name: identifierName(symbol), moduleName: null, runtime: symbol});
return {statements: [], genFilePath: '', importExpr};
return {statements: [], genFilePath: '', importExpr, constantPool: new ConstantPool()};
}

View File

@ -8,6 +8,7 @@
import {ParseSourceSpan} from '../parse_util';
import {error} from '../util';
//// Types
export enum TypeModifier {
@ -644,7 +645,8 @@ export const TYPED_NULL_EXPR = new LiteralExpr(null, INFERRED_TYPE, null);
export enum StmtModifier {
Final,
Private,
Exported
Exported,
Static,
}
export abstract class Statement {
@ -739,7 +741,9 @@ export class AbstractClassPart {
}
export class ClassField extends AbstractClassPart {
constructor(public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null) {
constructor(
public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null,
public initializer?: Expression) {
super(type, modifiers);
}
isEquivalent(f: ClassField) { return this.name === f.name; }
@ -1370,6 +1374,10 @@ export function fn(
return new FunctionExpr(params, body, type, sourceSpan);
}
export function ifStmt(condition: Expression, thenClause: Statement[], elseClause?: Statement[]) {
return new IfStmt(condition, thenClause, elseClause);
}
export function literal(
value: any, type?: Type | null, sourceSpan?: ParseSourceSpan | null): LiteralExpr {
return new LiteralExpr(value, type, sourceSpan);

View File

@ -216,8 +216,15 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
// comment out as a workaround for #10967
ctx.print(null, `/*private*/ `);
}
if (field.hasModifier(o.StmtModifier.Static)) {
ctx.print(null, 'static ');
}
ctx.print(null, field.name);
this._printColonType(field.type, ctx);
if (field.initializer) {
ctx.print(null, ' = ');
field.initializer.visitExpression(this, ctx);
}
ctx.println(null, `;`);
}

View File

@ -7,6 +7,7 @@
*/
import * as chars from './chars';
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
import {error} from './util';
export class ParseLocation {
constructor(

View File

@ -0,0 +1,85 @@
/**
* @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 * as o from '../output/output_ast';
const CORE = '@angular/core';
// Copied from core and must be in sync with the value in the runtime.
export const enum LifeCycleGuard {ON_INIT = 1, ON_DESTROY = 2, ON_CHANGES = 4}
// TODO: Include assignments that use the enum literals
// e.g. { let a: core.LifeCycleGuard.ON_INIT = LifeCycleGuard.ON_INIT; ...}
// Ensure these get removed in bundling.
export class Identifiers {
/* Methods */
static NEW_METHOD = 'n';
static HOST_BINDING_METHOD = 'h';
static REFRESH_METHOD = 'r';
/* Instructions */
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
static elementEnd: o.ExternalReference = {name: 'ɵe', moduleName: CORE};
static elementProperty: o.ExternalReference = {name: 'ɵp', moduleName: CORE};
static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE};
static elementClass: o.ExternalReference = {name: 'ɵk', moduleName: CORE};
static elementStyle: o.ExternalReference = {name: 'ɵs', moduleName: CORE};
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
static directiveInput: o.ExternalReference = {name: 'ɵi', moduleName: CORE};
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
static bind1: o.ExternalReference = {name: 'ɵb1', moduleName: CORE};
static bind2: o.ExternalReference = {name: 'ɵb2', moduleName: CORE};
static bind3: o.ExternalReference = {name: 'ɵb3', moduleName: CORE};
static bind4: o.ExternalReference = {name: 'ɵb4', moduleName: CORE};
static bind5: o.ExternalReference = {name: 'ɵb5', moduleName: CORE};
static bind6: o.ExternalReference = {name: 'ɵb6', moduleName: CORE};
static bind7: o.ExternalReference = {name: 'ɵb7', moduleName: CORE};
static bind8: o.ExternalReference = {name: 'ɵb8', moduleName: CORE};
static bind9: o.ExternalReference = {name: 'ɵb9', moduleName: CORE};
static bindV: o.ExternalReference = {name: 'ɵbV', moduleName: CORE};
static refreshComponent: o.ExternalReference = {name: 'ɵr', moduleName: CORE};
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE};
static injectTemplateRef: o.ExternalReference = {name: 'ɵinjectTemplateRef', moduleName: CORE};
static injectViewContainerRef:
o.ExternalReference = {name: 'ɵinjectViewContainerRef', moduleName: CORE};
static inject: o.ExternalReference = {name: 'ɵinject', moduleName: CORE};
static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE};
static defineDirective: o.ExternalReference = {
name: 'ɵdefineDirective',
moduleName: CORE,
};
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
}

View File

@ -0,0 +1,432 @@
/**
* @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 {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {ConstantPool, DefinitionKind} from '../constant_pool';
import {AST} from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast';
import {ParseSourceSpan} from '../parse_util';
import {CssSelector} from '../selector';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {OutputContext, error} from '../util';
import {Identifiers as R3} from './r3_identifiers';
/** Name of the context parameter passed into a template function */
const CONTEXT_NAME = 'ctx';
/** Name of the creation mode flag passed into a template function */
const CREATION_MODE_FLAG = 'cm';
/** Name of the temporary to use during data binding */
const TEMPORARY_NAME = '_t';
export function compileComponent(
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
reflector: CompileReflector) {
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
// e.g. `type: MyApp`
definitionMapValues.push(
{key: 'type', value: outputCtx.importExpr(component.type.reference), quoted: false});
// e.g. `tag: 'my-app'
// This is optional and only included if the first selector of a component has element.
const selector = component.selector && CssSelector.parse(component.selector);
const firstSelector = selector && selector[0];
if (firstSelector && firstSelector.hasElementSelector()) {
definitionMapValues.push({key: 'tag', value: o.literal(firstSelector.element), quoted: false});
}
// e.g. `attr: ["class", ".my.app"]
// This is optional an only included if the first selector of a component specifies attributes.
if (firstSelector) {
const selectorAttributes = firstSelector.getAttrs();
if (selectorAttributes.length) {
definitionMapValues.push({
key: 'attrs',
value: outputCtx.constantPool.getConstLiteral(o.literalArr(selectorAttributes.map(
value => value != null ? o.literal(value) : o.literal(undefined)))),
quoted: false
});
}
}
// e.g. `template: function(_ctx, _cm) {...}`
const templateFunctionExpression =
new TemplateDefinitionBuilder(outputCtx, outputCtx.constantPool, CONTEXT_NAME)
.buildTemplateFunction(template);
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
// e.g. `factory: () => new MyApp(injectElementRef())`
const templateFactory = createFactory(component.type, outputCtx, reflector);
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
const className = identifierName(component.type) !;
className || error(`Cannot resolver the name of ${component.type}`);
// Create the partial class to be merged with the actual class.
outputCtx.statements.push(new o.ClassStmt(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ 'ngComponentDef',
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
definitionMapValues)]))],
/* getters */[],
/* constructorMethod */ new o.ClassMethod(null, [], []),
/* methods */[]));
}
// TODO: Remove these when the things are fully supported
function unknown<T>(arg: o.Expression | o.Statement | TemplateAst): never {
throw new Error(`Builder ${this.constructor.name} is unable to handle ${o.constructor.name} yet`);
}
function unsupported(feature: string): never {
if (this) {
throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`);
}
throw new Error(`Feature ${feature} is supported yet`);
}
const BINDING_INSTRUCTION_MAP: {[index: number]: o.ExternalReference | undefined} = {
[PropertyBindingType.Property]: R3.elementProperty,
[PropertyBindingType.Attribute]: R3.elementAttribute,
[PropertyBindingType.Class]: R3.elementClass,
[PropertyBindingType.Style]: R3.elementStyle
};
function interpolate(args: o.Expression[]): o.Expression {
args = args.slice(1); // Ignore the length prefix added for render2
switch (args.length) {
case 3:
return o.importExpr(R3.bind1).callFn(args);
case 5:
return o.importExpr(R3.bind2).callFn(args);
case 7:
return o.importExpr(R3.bind3).callFn(args);
case 9:
return o.importExpr(R3.bind4).callFn(args);
case 11:
return o.importExpr(R3.bind5).callFn(args);
case 13:
return o.importExpr(R3.bind6).callFn(args);
case 15:
return o.importExpr(R3.bind7).callFn(args);
case 17:
return o.importExpr(R3.bind8).callFn(args);
case 19:
return o.importExpr(R3.bind9).callFn(args);
}
(args.length > 19 && args.length % 2 == 1) ||
error(`Invalid interpolation argument length ${args.length}`);
return o.importExpr(R3.bindV).callFn(args);
}
class TemplateDefinitionBuilder implements TemplateAstVisitor {
private _dataIndex = 0;
private _bindingContext = 0;
private _temporaryAllocated = false;
private _prefix: o.Statement[] = [];
private _creationMode: o.Statement[] = [];
private _bindingMode: o.Statement[] = [];
private _hostMode: o.Statement[] = [];
private _refreshMode: o.Statement[] = [];
private _postfix: o.Statement[] = [];
private unsupported = unsupported;
private invalid = invalid;
constructor(
private outputCtx: OutputContext, private constantPool: ConstantPool,
private contextParameter: string, private level = 0) {}
buildTemplateFunction(asts: TemplateAst[]): o.FunctionExpr {
templateVisitAll(this, asts);
return o.fn(
[
new o.FnParam(this.contextParameter, null), new o.FnParam(CREATION_MODE_FLAG, o.BOOL_TYPE)
],
[
// Temporary variable declarations (i.e. let _t: any;)
...this._prefix,
// Creating mode (i.e. if (cm) { ... })
o.ifStmt(o.variable(CREATION_MODE_FLAG), this._creationMode),
// Binding mode (i.e. ɵp(...))
...this._bindingMode,
// Host mode (i.e. Comp.h(...))
...this._hostMode,
// Refesh mode (i.e. Comp.r(...))
...this._refreshMode,
// Nested templates (i.e. function CompTemplate() {})
...this._postfix
],
o.INFERRED_TYPE);
}
// TODO(chuckj): Implement ng-content
visitNgContent = unknown;
visitElement(ast: ElementAst) {
let bindingCount = 0;
const elementIndex = this.allocateNode();
// Element creation mode
const component = findComponent(ast.directives);
const parameters: o.Expression[] = [o.literal(elementIndex)];
if (component) {
parameters.push(this.typeReference(component.directive.type.reference));
} else {
parameters.push(o.literal(ast.name));
}
const attributes: o.Expression[] = [];
for (let attr of ast.attrs) {
attributes.push(o.literal(attr.name), o.literal(attr.value));
}
if (attributes.length !== 0) {
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributes)));
}
this.instruction(this._creationMode, ast.sourceSpan, R3.createElement, ...parameters);
const implicit = o.variable(this.contextParameter);
// Generate element input bindings
for (let input of ast.inputs) {
if (input.isAnimation) {
this.unsupported('animations');
}
// TODO(chuckj): Builtins transform?
const convertedBinding = convertPropertyBinding(
null, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingMode.push(...convertedBinding.stmts);
const parameters =
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
const instruction = BINDING_INSTRUCTION_MAP[input.type];
if (instruction) {
// TODO(chuckj): runtime: security context?
this.instruction(
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
o.literal(input.name), convertedBinding.currValExpr);
} else {
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
}
}
// Generate directives input bindings
this._visitDirectives(ast.directives, implicit, elementIndex);
// Traverse element child nodes
templateVisitAll(this, ast.children);
// Finish element construction mode.
this.instruction(this._creationMode, ast.endSourceSpan || ast.sourceSpan, R3.elementEnd);
}
private _visitDirectives(directives: DirectiveAst[], implicit: o.Expression, nodeIndex: number) {
for (let directive of directives) {
const directiveIndex = this.allocateDirective();
// Creation mode
// e.g. D(0, TodoComponentDef.n(), TodoComponentDef);
const directiveType = directive.directive.type.reference;
const kind =
directive.directive.isComponent ? DefinitionKind.Component : DefinitionKind.Directive;
// Note: *do not cache* calls to this.directiveOf() as the constant pool needs to know if the
// node is referenced multiple times to know that it must generate the reference into a
// temporary.
this.instruction(
this._creationMode, directive.sourceSpan, R3.directiveCreate, o.literal(directiveIndex),
this.definitionOf(directiveType, kind)
.callMethod(R3.NEW_METHOD, [], directive.sourceSpan),
this.definitionOf(directiveType, kind));
// Bindings
for (const input of directive.inputs) {
const convertedBinding = convertPropertyBinding(
null, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingMode.push(...convertedBinding.stmts);
this.instruction(
this._bindingMode, directive.sourceSpan, R3.elementProperty,
o.literal(input.templateName), o.literal(nodeIndex), convertedBinding.currValExpr);
}
// e.g. TodoComponentDef.h(0, 0);
this._hostMode.push(
this.definitionOf(directiveType, kind)
.callMethod(R3.HOST_BINDING_METHOD, [o.literal(directiveIndex), o.literal(nodeIndex)])
.toStmt());
// e.g. TodoComponentDef.r(0, 0);
this._refreshMode.push(
this.definitionOf(directiveType, kind)
.callMethod(R3.REFRESH_METHOD, [o.literal(directiveIndex), o.literal(nodeIndex)])
.toStmt());
}
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
const templateIndex = this.allocateNode();
const templateName = `C${templateIndex}Template`;
const templateContext = `ctx${this.level}`;
// TODO(chuckj): attrs?
// e.g. C(1, C1Template)
this.instruction(
this._creationMode, ast.sourceSpan, R3.containerCreate, o.literal(templateIndex),
o.variable(templateName));
// Generate directies
this._visitDirectives(
ast.directives, o.variable(this.contextParameter),
// TODO(chuckj): This should be the element index of the element that contained the template
templateIndex);
// Create the template function
const templateVisitor = new TemplateDefinitionBuilder(
this.outputCtx, this.constantPool, templateContext, this.level + 1);
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children);
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
// Terminate the definition
this.instruction(this._creationMode, ast.sourceSpan, R3.containerEnd);
}
// These should be handled in the template or element directly.
readonly visitReference = invalid;
readonly visitVariable = invalid;
readonly visitEvent = invalid;
readonly visitElementProperty = invalid;
readonly visitAttr = invalid;
visitBoundText(ast: BoundTextAst) {
const nodeIndex = this.allocateNode();
// Creation mode
this.instruction(this._creationMode, ast.sourceSpan, R3.text, o.literal(nodeIndex));
// Refresh mode
this.instruction(
this._refreshMode, ast.sourceSpan, R3.textCreateBound, o.literal(nodeIndex),
this.bind(o.variable(this.contextParameter), ast.value, ast.sourceSpan));
}
visitText(ast: TextAst) {
// Text is defined in creation mode only.
this.instruction(this._creationMode, ast.sourceSpan, R3.text, o.literal(ast.value));
}
// These should be handled in the template or element directly
readonly visitDirective = invalid;
readonly visitDirectiveProperty = invalid;
private allocateDirective() { return this._dataIndex++; }
private allocateNode() { return this._dataIndex++; }
private bindingContext() { return `${this._bindingContext++}`; }
private instruction(
statements: o.Statement[], span: ParseSourceSpan, reference: o.ExternalReference,
...params: o.Expression[]) {
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
}
private typeReference(type: any): o.Expression { return this.outputCtx.importExpr(type); }
private definitionOf(type: any, kind: DefinitionKind): o.Expression {
return this.constantPool.getDefinition(type, kind, this.outputCtx);
}
private temp(): o.ReadVarExpr {
if (!this._temporaryAllocated) {
this._prefix.push(o.variable(TEMPORARY_NAME, o.DYNAMIC_TYPE,  null)
.set(o.literal(undefined))
.toDeclStmt(o.DYNAMIC_TYPE));
this._temporaryAllocated = true;
}
return o.variable(TEMPORARY_NAME);
}
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
const convertedPropertyBinding = convertPropertyBinding(
null, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._refreshMode.push(...convertedPropertyBinding.stmts);
return convertedPropertyBinding.currValExpr;
}
private bind(implicit: o.Expression, value: AST, sourceSpan: ParseSourceSpan): o.Expression {
return o.importExpr(R3.bind).callFn([this.convertPropertyBinding(implicit, value)]);
}
}
function createFactory(
type: CompileTypeMetadata, outputCtx: OutputContext,
reflector: CompileReflector): o.FunctionExpr {
let args: o.Expression[] = [];
const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef);
const templateRef = reflector.resolveExternalReference(Identifiers.TemplateRef);
const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef);
for (let dependency of type.diDeps) {
if (dependency.isValue) {
unsupported('value dependencies');
}
if (dependency.isHost) {
unsupported('host dependencies');
}
const token = dependency.token;
if (token) {
const tokenRef = tokenReference(token);
if (tokenRef === elementRef) {
args.push(o.importExpr(R3.injectElementRef).callFn([]));
} else if (tokenRef === templateRef) {
args.push(o.importExpr(R3.injectTemplateRef).callFn([]));
} else if (tokenRef === viewContainerRef) {
args.push(o.importExpr(R3.injectViewContainerRef).callFn([]));
} else {
args.push(o.importExpr(R3.inject).callFn([outputCtx.importExpr(token)]));
}
} else {
unsupported('dependency without a token');
}
}
return o.fn(
[],
[new o.ReturnStatement(new o.InstantiateExpr(outputCtx.importExpr(type.reference), args))],
o.INFERRED_TYPE);
}
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
throw new Error(
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
}
function findComponent(directives: DirectiveAst[]): DirectiveAst|undefined {
return directives.filter(directive => directive.directive.isComponent)[0];
}

View File

@ -104,6 +104,14 @@ export class CssSelector {
`<${tagName}${classAttr}${attrs}></${tagName}>`;
}
getAttrs(): string[] {
const result: string[] = [];
if (this.classNames.length > 0) {
result.push('class', this.classNames.join(' '));
}
return result.concat(this.attrs);
}
addAttribute(name: string, value: string = '') {
this.attrs.push(name, value && value.toLowerCase() || '');
}

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ConstantPool} from './constant_pool';
import * as o from './output/output_ast';
import {ParseError} from './parse_util';
@ -90,6 +92,10 @@ export const SyncAsync = {
}
};
export function error(msg: string): never {
throw new Error(`Internal Error: ${msg}`);
}
export function syntaxError(msg: string, parseErrors?: ParseError[]): Error {
const error = Error(msg);
(error as any)[ERROR_SYNTAX_ERROR] = true;
@ -152,6 +158,7 @@ export function utf8Encode(str: string): string {
export interface OutputContext {
genFilePath: string;
statements: o.Statement[];
constantPool: ConstantPool;
importExpr(reference: any, typeParams?: o.Type[]|null, useSummaries?: boolean): o.Expression;
}