diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 692de99a14..5cfd865425 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -77,6 +77,7 @@ export * from './ml_parser/tags'; export {NgModuleCompiler} from './ng_module_compiler'; export {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinType, BuiltinTypeName, BuiltinVar, CastExpr, ClassField, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, TypeofExpr, collectExternalReferences} from './output/output_ast'; export {EmitterVisitorContext} from './output/abstract_emitter'; +export {JitEvaluator} from './output/output_jit'; export * from './output/ts_emitter'; export * from './parse_util'; export * from './schema/dom_element_schema_registry'; @@ -92,7 +93,6 @@ export {BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent export * from './render3/view/t2_api'; export * from './render3/view/t2_binder'; export {Identifiers as R3Identifiers} from './render3/r3_identifiers'; -export {jitExpression} from './render3/r3_jit'; export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler'; export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler'; diff --git a/packages/compiler/src/jit/compiler.ts b/packages/compiler/src/jit/compiler.ts index dd245fb2d1..8fd53b03c8 100644 --- a/packages/compiler/src/jit/compiler.ts +++ b/packages/compiler/src/jit/compiler.ts @@ -15,7 +15,7 @@ import {CompileMetadataResolver} from '../metadata_resolver'; import {NgModuleCompiler} from '../ng_module_compiler'; import * as ir from '../output/output_ast'; import {interpretStatements} from '../output/output_interpreter'; -import {jitStatements} from '../output/output_jit'; +import {JitEvaluator} from '../output/output_jit'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {SummaryResolver} from '../summary_resolver'; import {TemplateAst} from '../template_parser/template_ast'; @@ -49,8 +49,8 @@ export class JitCompiler { private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver, - private _reflector: CompileReflector, private _compilerConfig: CompilerConfig, - private _console: Console, + private _reflector: CompileReflector, private _jitEvaluator: JitEvaluator, + private _compilerConfig: CompilerConfig, private _console: Console, private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {} compileModuleSync(moduleType: Type): object { @@ -322,7 +322,8 @@ export class JitCompiler { if (!this._compilerConfig.useJit) { return interpretStatements(statements, this._reflector); } else { - return jitStatements(sourceUrl, statements, this._reflector, this._compilerConfig.jitDevMode); + return this._jitEvaluator.evaluateStatements( + sourceUrl, statements, this._reflector, this._compilerConfig.jitDevMode); } } } diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index 88c6e5c1e7..589e54afbf 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -12,10 +12,11 @@ import {ConstantPool} from './constant_pool'; import {HostBinding, HostListener, Input, Output, Type} from './core'; import {compileInjectable} from './injectable_compiler_2'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config'; -import {Expression, LiteralExpr, WrappedNodeExpr} from './output/output_ast'; +import {DeclareVarStmt, Expression, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast'; +import {JitEvaluator} from './output/output_jit'; import {ParseError, ParseSourceSpan, r3JitTypeSourceSpan} from './parse_util'; import {R3DependencyMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; -import {jitExpression} from './render3/r3_jit'; +import {R3JitReflector} from './render3/r3_jit'; import {R3InjectorMetadata, R3NgModuleMetadata, compileInjector, compileNgModule} from './render3/r3_module_compiler'; import {compilePipeFromMetadata} from './render3/r3_pipe_compiler'; import {R3Reference} from './render3/util'; @@ -27,6 +28,7 @@ import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry'; export class CompilerFacadeImpl implements CompilerFacade { R3ResolvedDependencyType = R3ResolvedDependencyType as any; private elementSchemaRegistry = new DomElementSchemaRegistry(); + private jitEvaluator = new JitEvaluator(); compilePipe(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3PipeMetadataFacade): any { @@ -37,7 +39,7 @@ export class CompilerFacadeImpl implements CompilerFacade { pipeName: facade.pipeName, pure: facade.pure, }); - return jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements); + return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements); } compileInjectable( @@ -56,7 +58,7 @@ export class CompilerFacadeImpl implements CompilerFacade { userDeps: convertR3DependencyMetadataArray(facade.userDeps) || undefined, }); - return jitExpression(expression, angularCoreEnv, sourceMapUrl, statements); + return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements); } compileInjector( @@ -70,7 +72,7 @@ export class CompilerFacadeImpl implements CompilerFacade { imports: new WrappedNodeExpr(facade.imports), }; const res = compileInjector(meta); - return jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements); + return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements); } compileNgModule( @@ -85,7 +87,7 @@ export class CompilerFacadeImpl implements CompilerFacade { emitInline: true, }; const res = compileNgModule(meta); - return jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []); + return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []); } compileDirective( @@ -97,7 +99,7 @@ export class CompilerFacadeImpl implements CompilerFacade { const meta: R3DirectiveMetadata = convertDirectiveFacadeToMetadata(facade); const res = compileDirectiveFromMetadata(meta, constantPool, bindingParser); const preStatements = [...constantPool.statements, ...res.statements]; - return jitExpression(res.expression, angularCoreEnv, sourceMapUrl, preStatements); + return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, preStatements); } compileComponent( @@ -140,13 +142,38 @@ export class CompilerFacadeImpl implements CompilerFacade { }, constantPool, makeBindingParser(interpolationConfig)); const preStatements = [...constantPool.statements, ...res.statements]; - - return jitExpression(res.expression, angularCoreEnv, sourceMapUrl, preStatements); + return this.jitExpression( + res.expression, angularCoreEnv, sourceMapUrl, preStatements); } createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan { return r3JitTypeSourceSpan(kind, typeName, sourceUrl); } + + /** + * JIT compiles an expression and returns the result of executing that expression. + * + * @param def the definition which will be compiled and executed to get the value to patch + * @param context an object map of @angular/core symbol names to symbols which will be available + * in the context of the compiled expression + * @param sourceUrl a URL to use for the source map of the compiled expression + * @param preStatements a collection of statements that should be evaluated before the expression. + */ + private jitExpression( + def: Expression, context: {[key: string]: any}, sourceUrl: string, + preStatements: Statement[]): any { + // The ConstantPool may contain Statements which declare variables used in the final expression. + // Therefore, its statements need to precede the actual JIT operation. The final statement is a + // declaration of $def which is set to the expression being compiled. + const statements: Statement[] = [ + ...preStatements, + new DeclareVarStmt('$def', def, undefined, [StmtModifier.Exported]), + ]; + + const res = this.jitEvaluator.evaluateStatements( + sourceUrl, statements, new R3JitReflector(context), /* enableSourceMaps */ true); + return res['$def']; + } } // This seems to be needed to placate TS v3.0 only diff --git a/packages/compiler/src/output/output_jit.ts b/packages/compiler/src/output/output_jit.ts index 3c077aa911..c6ad4abd55 100644 --- a/packages/compiler/src/output/output_jit.ts +++ b/packages/compiler/src/output/output_jit.ts @@ -13,39 +13,79 @@ import {EmitterVisitorContext} from './abstract_emitter'; import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; import * as o from './output_ast'; -function evalExpression( - sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}, - createSourceMap: boolean): any { - let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`; - const fnArgNames: string[] = []; - const fnArgValues: any[] = []; - for (const argName in vars) { - fnArgNames.push(argName); - fnArgValues.push(vars[argName]); +/** + * A helper class to manage the evaluation of JIT generated code. + */ +export class JitEvaluator { + /** + * + * @param sourceUrl The URL of the generated code. + * @param statements An array of Angular statement AST nodes to be evaluated. + * @param reflector A helper used when converting the statements to executable code. + * @param createSourceMaps If true then create a source-map for the generated code and include it + * inline as a source-map comment. + * @returns A map of all the variables in the generated code. + */ + evaluateStatements( + sourceUrl: string, statements: o.Statement[], reflector: CompileReflector, + createSourceMaps: boolean): {[key: string]: any} { + const converter = new JitEmitterVisitor(reflector); + const ctx = EmitterVisitorContext.createRoot(); + converter.visitAllStatements(statements, ctx); + converter.createReturnStmt(ctx); + return this.evaluateCode(sourceUrl, ctx, converter.getArgs(), createSourceMaps); } - if (createSourceMap) { - // using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise - // E.g. ``` - // function anonymous(a,b,c - // /**/) { ... }``` - // We don't want to hard code this fact, so we auto detect it via an empty function first. - const emptyFn = new Function(...fnArgNames.concat('return null;')).toString(); - const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1; - fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`; + + /** + * Evaluate a piece of JIT generated code. + * @param sourceUrl The URL of this generated code. + * @param ctx A context object that contains an AST of the code to be evaluated. + * @param vars A map containing the names and values of variables that the evaluated code might + * reference. + * @param createSourceMap If true then create a source-map for the generated code and include it + * inline as a source-map comment. + * @returns The result of evaluating the code. + */ + evaluateCode( + sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}, + createSourceMap: boolean): any { + let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`; + const fnArgNames: string[] = []; + const fnArgValues: any[] = []; + for (const argName in vars) { + fnArgValues.push(vars[argName]); + fnArgNames.push(argName); + } + if (createSourceMap) { + // using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise + // E.g. ``` + // function anonymous(a,b,c + // /**/) { ... }``` + // We don't want to hard code this fact, so we auto detect it via an empty function first. + const emptyFn = new Function(...fnArgNames.concat('return null;')).toString(); + const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1; + fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`; + } + const fn = new Function(...fnArgNames.concat(fnBody)); + return this.executeFunction(fn, fnArgValues); } - return new Function(...fnArgNames.concat(fnBody))(...fnArgValues); -} - -export function jitStatements( - sourceUrl: string, statements: o.Statement[], reflector: CompileReflector, - createSourceMaps: boolean): {[key: string]: any} { - const converter = new JitEmitterVisitor(reflector); - const ctx = EmitterVisitorContext.createRoot(); - converter.visitAllStatements(statements, ctx); - converter.createReturnStmt(ctx); - return evalExpression(sourceUrl, ctx, converter.getArgs(), createSourceMaps); + + /** + * Execute a JIT generated function by calling it. + * + * This method can be overridden in tests to capture the functions that are generated + * by this `JitEvaluator` class. + * + * @param fn A function to execute. + * @param args The arguments to pass to the function being executed. + * @returns The return value of the executed function. + */ + executeFunction(fn: Function, args: any[]) { return fn(...args); } } +/** + * An Angular AST visitor that converts AST nodes into executable JavaScript code. + */ export class JitEmitterVisitor extends AbstractJsEmitterVisitor { private _evalArgNames: string[] = []; private _evalArgValues: any[] = []; diff --git a/packages/compiler/src/render3/r3_jit.ts b/packages/compiler/src/render3/r3_jit.ts index 2626dbbdbe..6c73d6a6a8 100644 --- a/packages/compiler/src/render3/r3_jit.ts +++ b/packages/compiler/src/render3/r3_jit.ts @@ -7,9 +7,7 @@ */ import {CompileReflector} from '../compile_reflector'; -import {ConstantPool} from '../constant_pool'; import * as o from '../output/output_ast'; -import {jitStatements} from '../output/output_jit'; /** * Implementation of `CompileReflector` which resolves references to @angular/core @@ -17,7 +15,7 @@ import {jitStatements} from '../output/output_jit'; * * Only supports `resolveExternalReference`, all other methods throw. */ -class R3JitReflector implements CompileReflector { +export class R3JitReflector implements CompileReflector { constructor(private context: {[key: string]: any}) {} resolveExternalReference(ref: o.ExternalReference): any { @@ -48,27 +46,3 @@ class R3JitReflector implements CompileReflector { componentModuleUrl(type: any, cmpMetadata: any): string { throw new Error('Not implemented.'); } } - -/** - * JIT compiles an expression and returns the result of executing that expression. - * - * @param def the definition which will be compiled and executed to get the value to patch - * @param context an object map of @angular/core symbol names to symbols which will be available in - * the context of the compiled expression - * @param sourceUrl a URL to use for the source map of the compiled expression - * @param constantPool an optional `ConstantPool` which contains constants used in the expression - */ -export function jitExpression( - def: o.Expression, context: {[key: string]: any}, sourceUrl: string, - preStatements: o.Statement[]): any { - // The ConstantPool may contain Statements which declare variables used in the final expression. - // Therefore, its statements need to precede the actual JIT operation. The final statement is a - // declaration of $def which is set to the expression being compiled. - const statements: o.Statement[] = [ - ...preStatements, - new o.DeclareVarStmt('$def', def, undefined, [o.StmtModifier.Exported]), - ]; - - const res = jitStatements(sourceUrl, statements, new R3JitReflector(context), false); - return res['$def']; -} diff --git a/packages/platform-browser-dynamic/src/compiler_factory.ts b/packages/platform-browser-dynamic/src/compiler_factory.ts index d978a0393a..728117a857 100644 --- a/packages/platform-browser-dynamic/src/compiler_factory.ts +++ b/packages/platform-browser-dynamic/src/compiler_factory.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, CompilerFactory, ComponentFactory, CompilerOptions, ModuleWithComponentFactories, Inject, InjectionToken, Optional, PACKAGE_ROOT_URL, PlatformRef, StaticProvider, TRANSLATIONS, Type, isDevMode, platformCore, ɵConsole as Console, ViewEncapsulation, Injector, NgModuleFactory, TRANSLATIONS_FORMAT, MissingTranslationStrategy,} from '@angular/core'; +import {Compiler, CompilerFactory, ComponentFactory, CompilerOptions, ModuleWithComponentFactories, Inject, InjectionToken, Optional, PACKAGE_ROOT_URL, StaticProvider, TRANSLATIONS, Type, isDevMode, ɵConsole as Console, ViewEncapsulation, Injector, NgModuleFactory, TRANSLATIONS_FORMAT, MissingTranslationStrategy,} from '@angular/core'; -import {StaticSymbolCache, JitCompiler, ProviderMeta, ExternalReference, I18NHtmlParser, Identifiers, ViewCompiler, CompileMetadataResolver, UrlResolver, TemplateParser, NgModuleCompiler, JitSummaryResolver, SummaryResolver, StyleCompiler, PipeResolver, ElementSchemaRegistry, DomElementSchemaRegistry, ResourceLoader, NgModuleResolver, HtmlParser, CompileReflector, CompilerConfig, DirectiveNormalizer, DirectiveResolver, Lexer, Parser} from '@angular/compiler'; +import {StaticSymbolCache, JitCompiler, ProviderMeta, I18NHtmlParser, ViewCompiler, CompileMetadataResolver, UrlResolver, TemplateParser, NgModuleCompiler, JitEvaluator, JitSummaryResolver, SummaryResolver, StyleCompiler, PipeResolver, ElementSchemaRegistry, DomElementSchemaRegistry, ResourceLoader, NgModuleResolver, HtmlParser, CompileReflector, CompilerConfig, DirectiveNormalizer, DirectiveResolver, Lexer, Parser} from '@angular/compiler'; import {JitReflector} from './compiler_reflector'; @@ -37,10 +37,11 @@ export class CompilerImpl implements Compiler { injector: Injector, private _metadataResolver: CompileMetadataResolver, templateParser: TemplateParser, styleCompiler: StyleCompiler, viewCompiler: ViewCompiler, ngModuleCompiler: NgModuleCompiler, summaryResolver: SummaryResolver>, - compileReflector: CompileReflector, compilerConfig: CompilerConfig, console: Console) { + compileReflector: CompileReflector, jitEvaluator: JitEvaluator, + compilerConfig: CompilerConfig, console: Console) { this._delegate = new JitCompiler( _metadataResolver, templateParser, styleCompiler, viewCompiler, ngModuleCompiler, - summaryResolver, compileReflector, compilerConfig, console, + summaryResolver, compileReflector, jitEvaluator, compilerConfig, console, this.getExtraNgModuleProviders.bind(this)); this.injector = injector; } @@ -127,6 +128,7 @@ export const COMPILER_PROVIDERS = [ Parser, ElementSchemaRegistry, I18NHtmlParser, Console] }, + { provide: JitEvaluator, useClass: JitEvaluator, deps: [] }, { provide: DirectiveNormalizer, deps: [ResourceLoader, UrlResolver, HtmlParser, CompilerConfig]}, { provide: CompileMetadataResolver, deps: [CompilerConfig, HtmlParser, NgModuleResolver, DirectiveResolver, PipeResolver, @@ -144,7 +146,7 @@ export const COMPILER_PROVIDERS = [ { provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver, TemplateParser, StyleCompiler, ViewCompiler, NgModuleCompiler, - SummaryResolver, CompileReflector, CompilerConfig, + SummaryResolver, CompileReflector, JitEvaluator, CompilerConfig, Console]}, { provide: DomElementSchemaRegistry, deps: []}, { provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},