feat(ivy): first steps towards JIT compilation (#23833)
This commit adds a mechanism by which the @angular/core annotations for @Component, @Injectable, and @NgModule become decorators which, when executed at runtime, trigger just-in-time compilation of their associated types. The activation of these decorators is configured by the ivy_switch mechanism, ensuring that the Ivy JIT engine does not get included in Angular bundles unless specifically requested. PR Close #23833
This commit is contained in:

committed by
Matias Niemelä

parent
1b6b936ef4
commit
919f42fea1
@ -110,6 +110,8 @@ export class Identifiers {
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static defineNgModule: o.ExternalReference = {name: 'ɵdefineNgModule', moduleName: CORE};
|
||||
|
||||
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
||||
|
||||
static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE};
|
||||
|
78
packages/compiler/src/render3/r3_jit.ts
Normal file
78
packages/compiler/src/render3/r3_jit.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @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 {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
|
||||
* symbols at runtime, according to a consumer-provided mapping.
|
||||
*
|
||||
* Only supports `resolveExternalReference`, all other methods throw.
|
||||
*/
|
||||
class R3JitReflector implements CompileReflector {
|
||||
constructor(private context: {[key: string]: any}) {}
|
||||
|
||||
resolveExternalReference(ref: o.ExternalReference): any {
|
||||
// This reflector only handles @angular/core imports.
|
||||
if (ref.moduleName !== '@angular/core') {
|
||||
throw new Error(
|
||||
`Cannot resolve external reference to ${ref.moduleName}, only references to @angular/core are supported.`);
|
||||
}
|
||||
if (!this.context.hasOwnProperty(ref.name !)) {
|
||||
throw new Error(`No value provided for @angular/core symbol '${ref.name!}'.`);
|
||||
}
|
||||
return this.context[ref.name !];
|
||||
}
|
||||
|
||||
parameters(typeOrFunc: any): any[][] { throw new Error('Not implemented.'); }
|
||||
|
||||
annotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); }
|
||||
|
||||
shallowAnnotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); }
|
||||
|
||||
tryAnnotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); }
|
||||
|
||||
propMetadata(typeOrFunc: any): {[key: string]: any[];} { throw new Error('Not implemented.'); }
|
||||
|
||||
hasLifecycleHook(type: any, lcProperty: string): boolean { throw new Error('Not implemented.'); }
|
||||
|
||||
guards(typeOrFunc: any): {[key: string]: any;} { throw new Error('Not implemented.'); }
|
||||
|
||||
componentModuleUrl(type: any, cmpMetadata: any): string { throw new Error('Not implemented.'); }
|
||||
}
|
||||
|
||||
/**
|
||||
* JIT compiles an expression and monkey-patches the result of executing the expression onto a given
|
||||
* type.
|
||||
*
|
||||
* @param type the type which will receive the monkey-patched result
|
||||
* @param field name of the field on the type to monkey-patch
|
||||
* @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 constantPool an optional `ConstantPool` which contains constants used in the expression
|
||||
*/
|
||||
export function jitPatchDefinition(
|
||||
type: any, field: string, def: o.Expression, context: {[key: string]: any},
|
||||
constantPool?: ConstantPool): void {
|
||||
// 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[] = [
|
||||
...(constantPool !== undefined ? constantPool.statements : []),
|
||||
new o.DeclareVarStmt('$def', def, undefined, [o.StmtModifier.Exported]),
|
||||
];
|
||||
|
||||
// Monkey patch the field on the given type with the result of compilation.
|
||||
// TODO(alxhub): consider a better source url.
|
||||
type[field] = jitStatements(
|
||||
`ng://${type && type.name}/${field}`, statements, new R3JitReflector(context), false)['$def'];
|
||||
}
|
@ -14,22 +14,72 @@ import * as o from '../output/output_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
import {convertMetaToOutput, mapToMapExpression} from './util';
|
||||
|
||||
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
||||
if (Array.isArray(meta)) {
|
||||
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
||||
}
|
||||
if (meta instanceof StaticSymbol) {
|
||||
return ctx.importExpr(meta);
|
||||
}
|
||||
if (meta == null) {
|
||||
return o.literal(meta);
|
||||
}
|
||||
|
||||
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
||||
export interface R3NgModuleDef {
|
||||
expression: o.Expression;
|
||||
type: o.Type;
|
||||
additionalStatements: o.Statement[];
|
||||
}
|
||||
|
||||
export function compileNgModule(
|
||||
/**
|
||||
* Metadata required by the module compiler to generate a `ngModuleDef` for a type.
|
||||
*/
|
||||
export interface R3NgModuleMetadata {
|
||||
/**
|
||||
* An expression representing the module type being compiled.
|
||||
*/
|
||||
type: o.Expression;
|
||||
|
||||
/**
|
||||
* An array of expressions representing the bootstrap components specified by the module.
|
||||
*/
|
||||
bootstrap: o.Expression[];
|
||||
|
||||
/**
|
||||
* An array of expressions representing the directives and pipes declared by the module.
|
||||
*/
|
||||
declarations: o.Expression[];
|
||||
|
||||
/**
|
||||
* An array of expressions representing the imports of the module.
|
||||
*/
|
||||
imports: o.Expression[];
|
||||
|
||||
/**
|
||||
* An array of expressions representing the exports of the module.
|
||||
*/
|
||||
exports: o.Expression[];
|
||||
|
||||
/**
|
||||
* Whether to emit the selector scope values (declarations, imports, exports) inline into the
|
||||
* module definition, or to generate additional statements which patch them on. Inline emission
|
||||
* does not allow components to be tree-shaken, but is useful for JIT mode.
|
||||
*/
|
||||
emitInline: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
||||
*/
|
||||
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
||||
const {type: moduleType, bootstrap, declarations, imports, exports} = meta;
|
||||
const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression({
|
||||
type: moduleType,
|
||||
bootstrap: o.literalArr(bootstrap),
|
||||
declarations: o.literalArr(declarations),
|
||||
imports: o.literalArr(imports),
|
||||
exports: o.literalArr(exports),
|
||||
})]);
|
||||
|
||||
// TODO(alxhub): write a proper type reference when AOT compilation of @NgModule is implemented.
|
||||
const type = new o.ExpressionType(o.NULL_EXPR);
|
||||
const additionalStatements: o.Statement[] = [];
|
||||
return {expression, type, additionalStatements};
|
||||
}
|
||||
|
||||
// TODO(alxhub): integrate this with `compileNgModule`. Currently the two are separate operations.
|
||||
export function compileNgModuleFromRender2(
|
||||
ctx: OutputContext, ngModule: CompileShallowModuleMetadata,
|
||||
injectableCompiler: InjectableCompiler): void {
|
||||
const className = identifierName(ngModule.type) !;
|
||||
@ -57,4 +107,9 @@ export function compileNgModule(
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]));
|
||||
}
|
||||
}
|
||||
|
||||
function accessExportScope(module: o.Expression): o.Expression {
|
||||
const selectorScope = new o.ReadPropExpr(module, 'ngModuleDef');
|
||||
return new o.ReadPropExpr(selectorScope, 'exported');
|
||||
}
|
||||
|
38
packages/compiler/src/render3/util.ts
Normal file
38
packages/compiler/src/render3/util.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @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 {StaticSymbol} from '../aot/static_symbol';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
/**
|
||||
* Convert an object map with `Expression` values into a `LiteralMapExpr`.
|
||||
*/
|
||||
export function mapToMapExpression(map: {[key: string]: o.Expression}): o.LiteralMapExpr {
|
||||
const result = Object.keys(map).map(key => ({key, value: map[key], quoted: false}));
|
||||
return o.literalMap(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert metadata into an `Expression` in the given `OutputContext`.
|
||||
*
|
||||
* This operation will handle arrays, references to symbols, or literal `null` or `undefined`.
|
||||
*/
|
||||
export function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
||||
if (Array.isArray(meta)) {
|
||||
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
||||
}
|
||||
if (meta instanceof StaticSymbol) {
|
||||
return ctx.importExpr(meta);
|
||||
}
|
||||
if (meta == null) {
|
||||
return o.literal(meta);
|
||||
}
|
||||
|
||||
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
||||
}
|
@ -12,12 +12,20 @@ import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, c
|
||||
import {ConstantPool} from '../../constant_pool';
|
||||
import * as core from '../../core';
|
||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
||||
import {Lexer} from '../../expression_parser/lexer';
|
||||
import {Parser} from '../../expression_parser/parser';
|
||||
import * as html from '../../ml_parser/ast';
|
||||
import {HtmlParser} from '../../ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import {ParseError, ParseSourceSpan} from '../../parse_util';
|
||||
import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry';
|
||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||
import {BindingParser} from '../../template_parser/binding_parser';
|
||||
import {OutputContext, error} from '../../util';
|
||||
import * as t from '../r3_ast';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {htmlAstToRender3Ast} from '../r3_template_transform';
|
||||
|
||||
import {R3QueryMetadata} from './api';
|
||||
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util';
|
||||
@ -713,3 +721,35 @@ function interpolate(args: o.Expression[]): o.Expression {
|
||||
error(`Invalid interpolation argument length ${args.length}`);
|
||||
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
|
||||
*
|
||||
* @param template text of the template to parse
|
||||
* @param templateUrl URL to use for source mapping of the parsed template
|
||||
*/
|
||||
export function parseTemplate(template: string, templateUrl: string):
|
||||
{errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} {
|
||||
const bindingParser = makeBindingParser();
|
||||
const htmlParser = new HtmlParser();
|
||||
const parseResult = htmlParser.parse(template, templateUrl);
|
||||
if (parseResult.errors && parseResult.errors.length > 0) {
|
||||
return {errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
|
||||
}
|
||||
const {nodes, hasNgContent, ngContentSelectors, errors} =
|
||||
htmlAstToRender3Ast(parseResult.rootNodes, bindingParser);
|
||||
if (errors && errors.length > 0) {
|
||||
return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
|
||||
}
|
||||
|
||||
return {nodes, hasNgContent, ngContentSelectors};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a `BindingParser` with a default configuration.
|
||||
*/
|
||||
export function makeBindingParser(): BindingParser {
|
||||
return new BindingParser(
|
||||
new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), [],
|
||||
[]);
|
||||
}
|
||||
|
Reference in New Issue
Block a user