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:
parent
ce8b5877e2
commit
64d16dee02
|
@ -245,6 +245,16 @@ Tells the compiler to generate all the possible generated files even if they are
|
|||
how `bazel` rules track file dependencies. It is not recommended to use this option outside of the `bazel`
|
||||
rules.
|
||||
|
||||
### *enableIvy*
|
||||
|
||||
Tells the compiler to generate definitions using the Render3 style code generation. This option defaults to `false`.
|
||||
|
||||
Not all features are supported with this option enabled. It is only supported
|
||||
for experimentation and testing of Render3 style code generation.
|
||||
|
||||
*Note*: Is it not recommended to use this option as it is not yet feature complete with the Render2 code generation.
|
||||
|
||||
|
||||
## Angular Metadata and AOT
|
||||
|
||||
The Angular **AOT compiler** extracts and interprets **metadata** about the parts of the application that Angular is supposed to manage.
|
||||
|
|
|
@ -59,6 +59,7 @@ module.exports = function(config) {
|
|||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/compiler/test/aot/**',
|
||||
'dist/all/@angular/compiler/test/render3/**',
|
||||
'dist/all/@angular/examples/**/e2e_test/*',
|
||||
'dist/all/@angular/language-service/**',
|
||||
'dist/all/@angular/router/test/**',
|
||||
|
|
|
@ -38,6 +38,8 @@ export function isNgDiagnostic(diagnostic: any): diagnostic is Diagnostic {
|
|||
}
|
||||
|
||||
export interface CompilerOptions extends ts.CompilerOptions {
|
||||
// NOTE: These comments and aio/content/guides/aot-compiler.md should be kept in sync.
|
||||
|
||||
// Write statistics about compilation (e.g. total time, ...)
|
||||
// Note: this is the --diagnostics command line option from TS (which is @internal
|
||||
// on ts.CompilerOptions interface).
|
||||
|
@ -159,6 +161,17 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
|||
*/
|
||||
enableSummariesForJit?: boolean;
|
||||
|
||||
/**
|
||||
* Tells the compiler to generate definitions using the Render3 style code generation.
|
||||
* This option defaults to `false`.
|
||||
*
|
||||
* Not all features are supported with this option enabled. It is only supported
|
||||
* for experimentation and testing of Render3 style code generation.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
enableIvy?: boolean;
|
||||
|
||||
/** @internal */
|
||||
collectAllErrors?: boolean;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, CompileIdentifierMetadata, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ParseSourceFile, ParseSourceSpan, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StaticSymbol, StmtModifier, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassMethod, ClassStmt, CommaExpr, CommentStmt, CompileIdentifierMetadata, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StaticSymbol, StmtModifier, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
import {error} from './util';
|
||||
|
||||
export interface Node { sourceSpan: ParseSourceSpan|null; }
|
||||
|
||||
|
@ -50,6 +51,98 @@ export class TypeScriptNodeEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given source file to include the changes specified in module.
|
||||
*
|
||||
* The module parameter is treated as a partial module meaning that the statements are added to
|
||||
* the module instead of replacing the module. Also, any classes are treated as partial classes
|
||||
* and the included members are added to the class with the same name instead of a new class
|
||||
* being created.
|
||||
*/
|
||||
export function updateSourceFile(
|
||||
sourceFile: ts.SourceFile, module: PartialModule,
|
||||
context: ts.TransformationContext): [ts.SourceFile, Map<ts.Node, Node>] {
|
||||
const converter = new _NodeEmitterVisitor();
|
||||
const prefixStatements = module.statements.filter(statement => !(statement instanceof ClassStmt));
|
||||
const classes =
|
||||
module.statements.filter(statement => statement instanceof ClassStmt) as ClassStmt[];
|
||||
const classMap = new Map(
|
||||
classes.map<[string, ClassStmt]>(classStatement => [classStatement.name, classStatement]));
|
||||
const classNames = new Set(classes.map(classStatement => classStatement.name));
|
||||
|
||||
const prefix =
|
||||
<ts.Statement[]>prefixStatements.map(statement => statement.visitStatement(converter, null));
|
||||
|
||||
// Add static methods to all the classes referenced in module.
|
||||
let newStatements = sourceFile.statements.map(node => {
|
||||
if (node.kind == ts.SyntaxKind.ClassDeclaration) {
|
||||
const classDeclaration = node as ts.ClassDeclaration;
|
||||
const name = classDeclaration.name;
|
||||
if (name) {
|
||||
const classStatement = classMap.get(name.text);
|
||||
if (classStatement) {
|
||||
classNames.delete(name.text);
|
||||
const classMemberHolder =
|
||||
converter.visitDeclareClassStmt(classStatement) as ts.ClassDeclaration;
|
||||
const newMethods =
|
||||
classMemberHolder.members.filter(member => member.kind !== ts.SyntaxKind.Constructor);
|
||||
const newMembers = [...classDeclaration.members, ...newMethods];
|
||||
|
||||
return ts.updateClassDeclaration(
|
||||
classDeclaration,
|
||||
/* decorators */ classDeclaration.decorators,
|
||||
/* modifiers */ classDeclaration.modifiers,
|
||||
/* name */ classDeclaration.name,
|
||||
/* typeParameters */ classDeclaration.typeParameters,
|
||||
/* heritageClauses */ classDeclaration.heritageClauses || [],
|
||||
/* members */ newMembers);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
|
||||
// Validate that all the classes have been generated
|
||||
classNames.size == 0 ||
|
||||
error(
|
||||
`${classNames.size == 1 ? 'Class' : 'Classes'} "${Array.from(classNames.keys()).join(', ')}" not generated`);
|
||||
|
||||
// Add imports to the module required by the new methods
|
||||
const imports = converter.getImports();
|
||||
if (imports && imports.length) {
|
||||
// Find where the new imports should go
|
||||
const index = firstAfter(
|
||||
newStatements, statement => statement.kind === ts.SyntaxKind.ImportDeclaration ||
|
||||
statement.kind === ts.SyntaxKind.ImportEqualsDeclaration);
|
||||
newStatements =
|
||||
[...newStatements.slice(0, index), ...imports, ...prefix, ...newStatements.slice(index)];
|
||||
} else {
|
||||
newStatements = [...prefix, ...newStatements];
|
||||
}
|
||||
|
||||
converter.updateSourceMap(newStatements);
|
||||
const newSourceFile = ts.updateSourceFileNode(sourceFile, newStatements);
|
||||
|
||||
return [newSourceFile, converter.getNodeMap()];
|
||||
}
|
||||
|
||||
// Return the index after the first value in `a` that doesn't match the predicate after a value that
|
||||
// does or 0 if no values match.
|
||||
function firstAfter<T>(a: T[], predicate: (value: T) => boolean) {
|
||||
let index = 0;
|
||||
const len = a.length;
|
||||
for (; index < len; index++) {
|
||||
const value = a[index];
|
||||
if (predicate(value)) break;
|
||||
}
|
||||
if (index >= len) return 0;
|
||||
for (; index < len; index++) {
|
||||
const value = a[index];
|
||||
if (!predicate(value)) break;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// A recorded node is a subtype of the node that is marked as being recoreded. This is used
|
||||
// to ensure that NodeEmitterVisitor.record has been called on all nodes returned by the
|
||||
// NodeEmitterVisitor
|
||||
|
@ -232,9 +325,12 @@ class _NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
|
|||
const modifiers = this.getModifiers(stmt);
|
||||
const fields = stmt.fields.map(
|
||||
field => ts.createProperty(
|
||||
/* decorators */ undefined, /* modifiers */ undefined, field.name,
|
||||
/* decorators */ undefined, /* modifiers */ translateModifiers(field.modifiers),
|
||||
field.name,
|
||||
/* questionToken */ undefined,
|
||||
/* type */ undefined, ts.createNull()));
|
||||
/* type */ undefined,
|
||||
field.initializer == null ? ts.createNull() :
|
||||
field.initializer.visitExpression(this, null)));
|
||||
const getters = stmt.getters.map(
|
||||
getter => ts.createGetAccessor(
|
||||
/* decorators */ undefined, /* modifiers */ undefined, getter.name, /* parameters */[],
|
||||
|
@ -256,7 +352,8 @@ class _NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
|
|||
const methods = stmt.methods.filter(method => method.name)
|
||||
.map(
|
||||
method => ts.createMethod(
|
||||
/* decorators */ undefined, /* modifiers */ undefined,
|
||||
/* decorators */ undefined,
|
||||
/* modifiers */ translateModifiers(method.modifiers),
|
||||
/* astriskToken */ undefined, method.name !/* guarded by filter */,
|
||||
/* questionToken */ undefined, /* typeParameters */ undefined,
|
||||
method.params.map(
|
||||
|
@ -547,3 +644,21 @@ function getMethodName(methodRef: {name: string | null; builtin: BuiltinMethod |
|
|||
}
|
||||
throw new Error('Unexpected method reference form');
|
||||
}
|
||||
|
||||
function modifierFromModifier(modifier: StmtModifier): ts.Modifier {
|
||||
switch (modifier) {
|
||||
case StmtModifier.Exported:
|
||||
return ts.createToken(ts.SyntaxKind.ExportKeyword);
|
||||
case StmtModifier.Final:
|
||||
return ts.createToken(ts.SyntaxKind.ConstKeyword);
|
||||
case StmtModifier.Private:
|
||||
return ts.createToken(ts.SyntaxKind.PrivateKeyword);
|
||||
case StmtModifier.Static:
|
||||
return ts.createToken(ts.SyntaxKind.StaticKeyword);
|
||||
}
|
||||
return error(`unknown statement modifier`);
|
||||
}
|
||||
|
||||
function translateModifiers(modifiers: StmtModifier[] | null): ts.Modifier[]|undefined {
|
||||
return modifiers == null ? undefined : modifiers !.map(modifierFromModifier);
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
|
@ -6,7 +7,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedModules, ParseSourceSpan, Position, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, FormattedMessageChain, GeneratedFile, MessageBundle, NgAnalyzedFile, NgAnalyzedModules, ParseSourceSpan, PartialModule, Position, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isFormattedError, isSyntaxError} from '@angular/compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
@ -18,7 +19,8 @@ import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, D
|
|||
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused} from './util';
|
||||
import {getAngularClassTransformerFactory} from './r3_transform';
|
||||
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -65,9 +67,10 @@ class AngularCompilerProgram implements Program {
|
|||
private host: CompilerHost, oldProgram?: Program) {
|
||||
this.rootNames = [...rootNames];
|
||||
const [major, minor] = ts.version.split('.');
|
||||
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 4)) {
|
||||
throw new Error('The Angular Compiler requires TypeScript >= 2.4.');
|
||||
}
|
||||
|
||||
Number(major) > 2 || (Number(major) === 2 && Number(minor) >= 4) ||
|
||||
userError('The Angular Compiler requires TypeScript >= 2.4.');
|
||||
|
||||
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
||||
if (oldProgram) {
|
||||
this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
|
||||
|
@ -79,8 +82,6 @@ class AngularCompilerProgram implements Program {
|
|||
const {host: bundleHost, indexName, errors} =
|
||||
createBundleIndexHost(options, this.rootNames, host);
|
||||
if (errors) {
|
||||
// TODO(tbosch): once we move MetadataBundler from tsc_wrapped into compiler_cli,
|
||||
// directly create ng.Diagnostic instead of using ts.Diagnostic here.
|
||||
this._optionsDiagnostics.push(...errors.map(e => ({
|
||||
category: e.category,
|
||||
messageText: e.messageText as string,
|
||||
|
@ -196,7 +197,55 @@ class AngularCompilerProgram implements Program {
|
|||
return this.compiler.listLazyRoutes(route, route ? undefined : this.analyzedModules);
|
||||
}
|
||||
|
||||
emit(
|
||||
emit(parameters: {
|
||||
emitFlags?: EmitFlags,
|
||||
cancellationToken?: ts.CancellationToken,
|
||||
customTransformers?: CustomTransformers,
|
||||
emitCallback?: TsEmitCallback
|
||||
} = {}): ts.EmitResult {
|
||||
return this.options.enableIvy === true ? this._emitRender3(parameters) :
|
||||
this._emitRender2(parameters);
|
||||
}
|
||||
|
||||
private _emitRender3(
|
||||
{emitFlags = EmitFlags.Default, cancellationToken, customTransformers,
|
||||
emitCallback = defaultEmitCallback}: {
|
||||
emitFlags?: EmitFlags,
|
||||
cancellationToken?: ts.CancellationToken,
|
||||
customTransformers?: CustomTransformers,
|
||||
emitCallback?: TsEmitCallback
|
||||
} = {}): ts.EmitResult {
|
||||
const emitStart = Date.now();
|
||||
if ((emitFlags & (EmitFlags.JS | EmitFlags.DTS | EmitFlags.Metadata | EmitFlags.Codegen)) ===
|
||||
0) {
|
||||
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
|
||||
}
|
||||
const modules = this.compiler.emitAllPartialModules(this.analyzedModules);
|
||||
|
||||
const writeTsFile: ts.WriteFileCallback =
|
||||
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
|
||||
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
|
||||
let genFile: GeneratedFile|undefined;
|
||||
this.writeFile(outFileName, outData, writeByteOrderMark, onError, undefined, sourceFiles);
|
||||
};
|
||||
|
||||
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
|
||||
|
||||
const tsCustomTansformers = this.calculateTransforms(
|
||||
/* genFiles */ undefined, /* partialModules */ modules, customTransformers);
|
||||
|
||||
const emitResult = emitCallback({
|
||||
program: this.tsProgram,
|
||||
host: this.host,
|
||||
options: this.options,
|
||||
writeFile: writeTsFile, emitOnlyDtsFiles,
|
||||
customTransformers: tsCustomTansformers
|
||||
});
|
||||
|
||||
return emitResult;
|
||||
}
|
||||
|
||||
private _emitRender2(
|
||||
{emitFlags = EmitFlags.Default, cancellationToken, customTransformers,
|
||||
emitCallback = defaultEmitCallback}: {
|
||||
emitFlags?: EmitFlags,
|
||||
|
@ -244,7 +293,8 @@ class AngularCompilerProgram implements Program {
|
|||
}
|
||||
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
|
||||
};
|
||||
const tsCustomTansformers = this.calculateTransforms(genFileByFileName, customTransformers);
|
||||
const tsCustomTansformers = this.calculateTransforms(
|
||||
genFileByFileName, /* partialModules */ undefined, customTransformers);
|
||||
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
|
||||
// Restore the original references before we emit so TypeScript doesn't emit
|
||||
// a reference to the .d.ts file.
|
||||
|
@ -400,13 +450,18 @@ class AngularCompilerProgram implements Program {
|
|||
}
|
||||
|
||||
private calculateTransforms(
|
||||
genFiles: Map<string, GeneratedFile>,
|
||||
genFiles: Map<string, GeneratedFile>|undefined, partialModules: PartialModule[]|undefined,
|
||||
customTransformers?: CustomTransformers): ts.CustomTransformers {
|
||||
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
|
||||
if (!this.options.disableExpressionLowering) {
|
||||
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache, this.tsProgram));
|
||||
}
|
||||
beforeTs.push(getAngularEmitterTransformFactory(genFiles, this.getTsProgram()));
|
||||
if (genFiles) {
|
||||
beforeTs.push(getAngularEmitterTransformFactory(genFiles, this.getTsProgram()));
|
||||
}
|
||||
if (partialModules) {
|
||||
beforeTs.push(getAngularClassTransformerFactory(partialModules));
|
||||
}
|
||||
if (customTransformers && customTransformers.beforeTs) {
|
||||
beforeTs.push(...customTransformers.beforeTs);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @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 {PartialModule, Statement, StaticSymbol} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {updateSourceFile} from './node_emitter';
|
||||
|
||||
export type Transformer = (sourceFile: ts.SourceFile) => ts.SourceFile;
|
||||
export type TransformerFactory = (context: ts.TransformationContext) => Transformer;
|
||||
|
||||
/**
|
||||
* Returns a transformer that adds the requested static methods specified by modules.
|
||||
*/
|
||||
export function getAngularClassTransformerFactory(modules: PartialModule[]): TransformerFactory {
|
||||
if (modules.length === 0) {
|
||||
// If no modules are specified, just return an identity transform.
|
||||
return () => sf => sf;
|
||||
}
|
||||
const moduleMap = new Map(modules.map<[string, PartialModule]>(m => [m.fileName, m]));
|
||||
return function(context: ts.TransformationContext) {
|
||||
return function(sourceFile: ts.SourceFile): ts.SourceFile {
|
||||
const module = moduleMap.get(sourceFile.fileName);
|
||||
if (module) {
|
||||
const [newSourceFile] = updateSourceFile(sourceFile, module, context);
|
||||
return newSourceFile;
|
||||
}
|
||||
return sourceFile;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {syntaxError} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
|
@ -21,6 +22,14 @@ export function tsStructureIsReused(program: ts.Program): StructureIsReused {
|
|||
return (program as any).structureIsReused;
|
||||
}
|
||||
|
||||
export function error(msg: string): never {
|
||||
throw new Error(`Internal error: ${msg}`);
|
||||
}
|
||||
|
||||
export function userError(msg: string): never {
|
||||
throw syntaxError(msg);
|
||||
}
|
||||
|
||||
export function createMessageDiagnostic(messageText: string): ts.Diagnostic&Diagnostic {
|
||||
return {
|
||||
file: undefined,
|
||||
|
|
|
@ -13,7 +13,9 @@ export type Entry = string | Directory;
|
|||
export interface Directory { [name: string]: Entry; }
|
||||
|
||||
export class MockAotContext {
|
||||
constructor(public currentDirectory: string, private files: Entry) {}
|
||||
private files: Entry[];
|
||||
|
||||
constructor(public currentDirectory: string, ...files: Entry[]) { this.files = files; }
|
||||
|
||||
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
|
||||
|
||||
|
@ -53,19 +55,7 @@ export class MockAotContext {
|
|||
}
|
||||
parts.shift();
|
||||
parts = normalize(parts);
|
||||
let current = this.files;
|
||||
while (parts.length) {
|
||||
const part = parts.shift() !;
|
||||
if (typeof current === 'string') {
|
||||
return undefined;
|
||||
}
|
||||
const next = (<Directory>current)[part];
|
||||
if (next === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
return current;
|
||||
return first(this.files, files => getEntryFromFiles(parts, files));
|
||||
}
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
|
@ -76,6 +66,31 @@ export class MockAotContext {
|
|||
return Object.keys(dir).filter(key => typeof dir[key] === 'object');
|
||||
}
|
||||
}
|
||||
|
||||
override(files: Entry) { return new MockAotContext(this.currentDirectory, files, ...this.files); }
|
||||
}
|
||||
|
||||
function first<T>(a: T[], cb: (value: T) => T | undefined): T|undefined {
|
||||
for (const value of a) {
|
||||
const result = cb(value);
|
||||
if (result != null) return result;
|
||||
}
|
||||
}
|
||||
|
||||
function getEntryFromFiles(parts: string[], files: Entry) {
|
||||
let current = files;
|
||||
while (parts.length) {
|
||||
const part = parts.shift() !;
|
||||
if (typeof current === 'string') {
|
||||
return undefined;
|
||||
}
|
||||
const next = (<Directory>current)[part];
|
||||
if (next === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function normalize(parts: string[]): string[] {
|
||||
|
|
|
@ -1807,4 +1807,42 @@ describe('ngc transformer command-line', () => {
|
|||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ivy', () => {
|
||||
function emittedFile(name: string): string {
|
||||
const outputName = path.resolve(outDir, name);
|
||||
expect(fs.existsSync(outputName)).toBe(true);
|
||||
return fs.readFileSync(outputName, {encoding: 'UTF-8'});
|
||||
}
|
||||
|
||||
it('should emit the hello world example', () => {
|
||||
write('tsconfig.json', `{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"files": ["hello-world.ts"],
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": true
|
||||
}
|
||||
}`);
|
||||
|
||||
write('hello-world.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hello-world',
|
||||
template: 'Hello, world!'
|
||||
})
|
||||
export class HelloWorldComponent {
|
||||
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [HelloWorldComponent]
|
||||
})
|
||||
export class HelloWorldModule {}
|
||||
`);
|
||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
|
||||
expect(exitCode).toBe(0, 'Compile failed');
|
||||
expect(emittedFile('hello-world.js')).toContain('ngComponentDef');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* @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 {PartialModule} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {MockAotCompilerHost} from 'compiler/test/aot/test_util';
|
||||
import {initDomAdapter} from 'platform-browser/src/browser';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {getAngularClassTransformerFactory} from '../../src/transformers/r3_transform';
|
||||
import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
|
||||
|
||||
const someGenFilePath = '/somePackage/someGenFile';
|
||||
const someGenFileName = someGenFilePath + '.ts';
|
||||
|
||||
describe('r3_transform_spec', () => {
|
||||
let context: MockAotContext;
|
||||
let host: MockCompilerHost;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new MockAotContext('/', FILES);
|
||||
host = new MockCompilerHost(context);
|
||||
});
|
||||
|
||||
it('should be able to generate a simple identity function', () => {
|
||||
expect(emitStaticMethod(new o.ReturnStatement(o.variable('v')), ['v']))
|
||||
.toContain('static someMethod(v) { return v; }');
|
||||
});
|
||||
|
||||
it('should be able to generate a static field declaration',
|
||||
() => { expect(emitStaticField(o.literal(10))).toContain('SomeClass.someField = 10'); });
|
||||
|
||||
it('should be able to import a symbol', () => {
|
||||
expect(emitStaticMethod(new o.ReturnStatement(
|
||||
o.importExpr(new o.ExternalReference('@angular/core', 'Component')))))
|
||||
.toContain('static someMethod() { return i0.Component; } }');
|
||||
});
|
||||
|
||||
it('should be able to modify multiple classes in the same module', () => {
|
||||
const result = emit(getAngularClassTransformerFactory([{
|
||||
fileName: someGenFileName,
|
||||
statements: [
|
||||
classMethod(new o.ReturnStatement(o.variable('v')), ['v'], 'someMethod', 'SomeClass'),
|
||||
classMethod(
|
||||
new o.ReturnStatement(o.variable('v')), ['v'], 'someOtherMethod', 'SomeOtherClass')
|
||||
]
|
||||
}]));
|
||||
expect(result).toContain('static someMethod(v) { return v; }');
|
||||
expect(result).toContain('static someOtherMethod(v) { return v; }');
|
||||
});
|
||||
|
||||
it('should insert imports after existing imports', () => {
|
||||
context = context.override({
|
||||
somePackage: {
|
||||
'someGenFile.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'some-class', template: 'hello!'})
|
||||
export class SomeClass {}
|
||||
|
||||
export class SomeOtherClass {}
|
||||
`
|
||||
}
|
||||
});
|
||||
host = new MockCompilerHost(context);
|
||||
|
||||
expect(emitStaticMethod(new o.ReturnStatement(
|
||||
o.importExpr(new o.ExternalReference('@angular/core', 'Component')))))
|
||||
.toContain('const core_1 = require("@angular/core"); const i0 = require("@angular/core");');
|
||||
});
|
||||
|
||||
function emit(factory: ts.TransformerFactory<ts.SourceFile>): string {
|
||||
let result: string = '';
|
||||
const program = ts.createProgram(
|
||||
[someGenFileName], {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017}, host);
|
||||
const moduleSourceFile = program.getSourceFile(someGenFileName);
|
||||
const transformers: ts.CustomTransformers = {before: [factory]};
|
||||
const emitResult = program.emit(
|
||||
moduleSourceFile, (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
|
||||
if (fileName.startsWith(someGenFilePath)) {
|
||||
result = data;
|
||||
}
|
||||
}, undefined, undefined, transformers);
|
||||
return normalizeResult(result);
|
||||
}
|
||||
|
||||
function emitStaticMethod(
|
||||
stmt: o.Statement | o.Statement[], parameters: string[] = [],
|
||||
methodName: string = 'someMethod', className: string = 'SomeClass'): string {
|
||||
const module: PartialModule = {
|
||||
fileName: someGenFileName,
|
||||
statements: [classMethod(stmt, parameters, methodName, className)]
|
||||
};
|
||||
return emit(getAngularClassTransformerFactory([module]));
|
||||
}
|
||||
|
||||
function emitStaticField(
|
||||
initializer: o.Expression, fieldName: string = 'someField',
|
||||
className: string = 'SomeClass'): string {
|
||||
const module: PartialModule = {
|
||||
fileName: someGenFileName,
|
||||
statements: [classField(initializer, fieldName, className)]
|
||||
};
|
||||
return emit(getAngularClassTransformerFactory([module]));
|
||||
}
|
||||
});
|
||||
|
||||
const FILES: Directory = {
|
||||
somePackage: {
|
||||
'someGenFile.ts': `
|
||||
|
||||
export class SomeClass {}
|
||||
|
||||
export class SomeOtherClass {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
function classMethod(
|
||||
stmt: o.Statement | o.Statement[], parameters: string[] = [], methodName: string = 'someMethod',
|
||||
className: string = 'SomeClass'): o.ClassStmt {
|
||||
const statements = Array.isArray(stmt) ? stmt : [stmt];
|
||||
return new o.ClassStmt(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[],
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[new o.ClassMethod(
|
||||
methodName, parameters.map(name => new o.FnParam(name)), statements, null,
|
||||
[o.StmtModifier.Static])]);
|
||||
}
|
||||
|
||||
function classField(
|
||||
initializer: o.Expression, fieldName: string = 'someField',
|
||||
className: string = 'SomeClass'): o.ClassStmt {
|
||||
return new o.ClassStmt(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(fieldName, null, [o.StmtModifier.Static], initializer)],
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]);
|
||||
}
|
||||
|
||||
function normalizeResult(result: string): string {
|
||||
// Remove TypeScript prefixes
|
||||
// Remove new lines
|
||||
// Squish adjacent spaces
|
||||
// Remove prefix and postfix spaces
|
||||
return result.replace('"use strict";', ' ')
|
||||
.replace('exports.__esModule = true;', ' ')
|
||||
.replace('Object.defineProperty(exports, "__esModule", { value: true });', ' ')
|
||||
.replace(/\n/g, ' ')
|
||||
.replace(/ +/g, ' ')
|
||||
.replace(/^ /g, '')
|
||||
.replace(/ $/g, '');
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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[];
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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))]);
|
||||
|
|
|
@ -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}`);
|
||||
}
|
|
@ -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()};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, `;`);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
import * as chars from './chars';
|
||||
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
import {error} from './util';
|
||||
|
||||
export class ParseLocation {
|
||||
constructor(
|
||||
|
|
|
@ -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};
|
||||
}
|
|
@ -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];
|
||||
}
|
|
@ -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() || '');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, CompileTypeSummary, ResolvedStaticSymbol, StaticSymbol, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
|
||||
import {deserializeSummaries, serializeSummaries} from '@angular/compiler/src/aot/summary_serializer';
|
||||
import {ConstantPool} from '@angular/compiler/src/constant_pool';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {OutputContext} from '@angular/compiler/src/util';
|
||||
import * as path from 'path';
|
||||
|
@ -127,5 +128,10 @@ export class MockAotSummaryResolverHost implements AotSummaryResolverHost {
|
|||
}
|
||||
|
||||
export function createMockOutputContext(): OutputContext {
|
||||
return {statements: [], genFilePath: 'someGenFilePath', importExpr: () => o.NULL_EXPR};
|
||||
return {
|
||||
statements: [],
|
||||
genFilePath: 'someGenFilePath',
|
||||
importExpr: () => o.NULL_EXPR,
|
||||
constantPool: new ConstantPool()
|
||||
};
|
||||
}
|
|
@ -538,10 +538,12 @@ const minCoreIndex = `
|
|||
export * from './src/codegen_private_exports';
|
||||
`;
|
||||
|
||||
export function setup(options: {compileAngular: boolean, compileAnimations: boolean} = {
|
||||
compileAngular: true,
|
||||
compileAnimations: true,
|
||||
}) {
|
||||
export function setup(
|
||||
options: {compileAngular: boolean, compileAnimations: boolean, compileCommon?: boolean} = {
|
||||
compileAngular: true,
|
||||
compileAnimations: true,
|
||||
compileCommon: false,
|
||||
}) {
|
||||
let angularFiles = new Map<string, string>();
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -552,6 +554,13 @@ export function setup(options: {compileAngular: boolean, compileAnimations: bool
|
|||
emittingProgram.emit();
|
||||
emittingHost.writtenAngularFiles(angularFiles);
|
||||
}
|
||||
if (options.compileCommon) {
|
||||
const emittingHost =
|
||||
new EmittingCompilerHost(['@angular/common/index.ts'], {emitMetadata: true});
|
||||
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
emittingHost.writtenAngularFiles(angularFiles);
|
||||
}
|
||||
if (options.compileAnimations) {
|
||||
const emittingHost =
|
||||
new EmittingCompilerHost(['@angular/animations/index.ts'], {emitMetadata: true});
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Tests in this directory are excluded from running in the browser and only run in node.
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* @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 {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ConstantPool} from '../../src/constant_pool';
|
||||
import * as o from '../../src/output/output_ast';
|
||||
import {compileComponent} from '../../src/render3/r3_view_compiler';
|
||||
import {OutputContext} from '../../src/util';
|
||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, settings, setup, toMockFileArray} from '../aot/test_util';
|
||||
|
||||
describe('r3_view_compiler', () => {
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
describe('hello world', () => {
|
||||
it('should be able to generate the hello world component', () => {
|
||||
const files: MockDirectory = {
|
||||
app: {
|
||||
'hello.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hello-world',
|
||||
template: 'Hello, world!'
|
||||
})
|
||||
export class HelloWorldComponent {
|
||||
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [HelloWorldComponent]
|
||||
})
|
||||
export class HelloWorldModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
compile(files, angularFiles);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to generate the example', () => {
|
||||
const files: MockDirectory = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, OnInit, OnDestroy, ElementRef, Input, NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: '<todo [data]="list"></todo>'
|
||||
})
|
||||
export class MyApp implements OnInit {
|
||||
|
||||
list: any[] = [];
|
||||
|
||||
constructor(public elementRef: ElementRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'todo',
|
||||
template: '<ul class="list" [title]="myTitle"><li *ngFor="let item of data">{{data}}</li></ul>'
|
||||
})
|
||||
export class TodoComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input()
|
||||
data: any[] = [];
|
||||
|
||||
myTitle: string;
|
||||
|
||||
constructor(public elementRef: ElementRef) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [TodoComponent, MyApp],
|
||||
imports: [CommonModule]
|
||||
})
|
||||
export class TodoModule{}
|
||||
`
|
||||
}
|
||||
};
|
||||
const result = compile(files, angularFiles);
|
||||
expect(result.source).toContain('@angular/core');
|
||||
});
|
||||
});
|
||||
|
||||
function compile(
|
||||
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error; }) {
|
||||
const testFiles = toMockFileArray(data);
|
||||
const scripts = testFiles.map(entry => entry.fileName);
|
||||
const angularFilesArray = toMockFileArray(angularFiles);
|
||||
const files = arrayToMockDir([...testFiles, ...angularFilesArray]);
|
||||
const mockCompilerHost = new MockCompilerHost(scripts, files);
|
||||
const compilerHost = new MockAotCompilerHost(mockCompilerHost);
|
||||
|
||||
const program = ts.createProgram(scripts, {...settings}, mockCompilerHost);
|
||||
|
||||
// TODO(chuckj): Replace with a variant of createAotCompiler() when the r3_view_compiler is
|
||||
// integrated
|
||||
const translations = options.translations || '';
|
||||
|
||||
const urlResolver = createAotUrlResolver(compilerHost);
|
||||
const symbolCache = new StaticSymbolCache();
|
||||
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
||||
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
||||
const staticReflector =
|
||||
new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector);
|
||||
const htmlParser = new I18NHtmlParser(
|
||||
new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console);
|
||||
const config = new CompilerConfig({
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
useJit: false,
|
||||
enableLegacyTemplate: options.enableLegacyTemplate === true,
|
||||
missingTranslation: options.missingTranslation,
|
||||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
strictInjectionParameters: options.strictInjectionParameters,
|
||||
});
|
||||
const normalizer = new DirectiveNormalizer(
|
||||
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const templateParser = new TemplateParser(
|
||||
config, staticReflector, expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const resolver = new CompileMetadataResolver(
|
||||
config, htmlParser, new NgModuleResolver(staticReflector),
|
||||
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
|
||||
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector);
|
||||
|
||||
|
||||
|
||||
// Create the TypeScript program
|
||||
const sourceFiles = program.getSourceFiles().map(sf => sf.fileName);
|
||||
|
||||
// Analyze the modules
|
||||
// TODO(chuckj): Eventually this should not be necessary as the ts.SourceFile should be sufficient
|
||||
// to generate a template definition.
|
||||
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
||||
|
||||
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
|
||||
const fakeOuputContext: OutputContext = {
|
||||
genFilePath: 'fakeFactory.ts',
|
||||
statements: [],
|
||||
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
if (!symbol) {
|
||||
throw new Error('Invalid: undefined passed to as a symbol');
|
||||
}
|
||||
throw new Error(`Invalid: ${(symbol as any).constructor.name} is not a symbol`);
|
||||
}
|
||||
return (symbol.members || [])
|
||||
.reduce(
|
||||
(expr, member) => expr.prop(member),
|
||||
<o.Expression>o.importExpr(new o.ExternalReference(symbol.filePath, symbol.name)));
|
||||
},
|
||||
constantPool: new ConstantPool()
|
||||
};
|
||||
|
||||
// Load All directives
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||
}
|
||||
|
||||
// Compile the directives.
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
||||
if (resolver.isDirective(directive)) {
|
||||
const metadata = resolver.getDirectiveMetadata(directive);
|
||||
if (metadata.isComponent) {
|
||||
const fakeUrl = 'ng://fake-template-url.html';
|
||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
||||
|
||||
const directives = module.transitiveModule.directives.map(
|
||||
dir => resolver.getDirectiveSummary(dir.reference));
|
||||
const pipes =
|
||||
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
||||
const parsedTemplate = templateParser.parse(
|
||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||
|
||||
compileComponent(fakeOuputContext, metadata, parsedTemplate.template, staticReflector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fakeOuputContext.statements.unshift(...fakeOuputContext.constantPool.statements);
|
||||
|
||||
const emitter = new TypeScriptEmitter();
|
||||
|
||||
const result = emitter.emitStatementsAndContext(
|
||||
fakeOuputContext.genFilePath, fakeOuputContext.statements, '', false);
|
||||
|
||||
return {source: result.sourceText, outputContext: fakeOuputContext};
|
||||
}
|
Loading…
Reference in New Issue