From 646b42a11353a12bd0bd08789f24bfb47f5785cc Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 21 May 2018 08:15:19 -0700 Subject: [PATCH] feat(ivy): JIT renders the TODO app (#24138) This commit builds out enough of the JIT compiler to render //packages/core/test/bundling/todo, and allows the tests to run in JIT mode. To play with the app, run: bazel run --define=compile=jit //packages/core/test/bundling/todo:prodserver PR Close #24138 --- packages/bazel/src/ng_module.bzl | 4 +- packages/compiler/src/compiler.ts | 4 +- .../src/ml_parser/html_whitespaces.ts | 2 +- packages/compiler/src/render3/r3_jit.ts | 18 +-- packages/compiler/src/render3/view/api.ts | 22 +-- .../compiler/src/render3/view/compiler.ts | 34 ++--- .../compiler/src/render3/view/template.ts | 13 +- .../render3/r3_compiler_compliance_spec.ts | 4 +- packages/core/src/ivy_switch_legacy.ts | 1 + packages/core/src/ivy_switch_on.ts | 3 +- packages/core/src/metadata/directives.ts | 7 +- packages/core/src/render3/jit/directive.ts | 144 +++++++++++++----- packages/core/src/render3/jit/environment.ts | 41 ++++- packages/core/src/render3/jit/injectable.ts | 109 +++++++------ packages/core/src/render3/jit/module.ts | 54 +++++-- packages/core/src/render3/jit/util.ts | 7 +- packages/core/src/util.ts | 5 +- .../test/bundling/hello_world/BUILD.bazel | 2 +- .../hello_world_r2/bundle.golden_symbols.json | 3 - packages/core/test/bundling/todo/BUILD.bazel | 2 + .../bundling/todo/bundle.golden_symbols.json | 3 - packages/core/test/bundling/todo/index.ts | 60 ++++---- 22 files changed, 348 insertions(+), 194 deletions(-) diff --git a/packages/bazel/src/ng_module.bzl b/packages/bazel/src/ng_module.bzl index 0daa736273..8defd2ff4e 100644 --- a/packages/bazel/src/ng_module.bzl +++ b/packages/bazel/src/ng_module.bzl @@ -518,8 +518,8 @@ ng_module = rule( # TODO(alxhub): this rule causes legacy ngc to produce Ivy outputs from global analysis information. -# It to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used instead, and -# should be removed once ngtsc is capable of fulfilling the same requirements. +# It exists to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used +# instead, and should be removed once ngtsc is capable of fulfilling the same requirements. internal_global_ng_module = rule( implementation = _ng_module_impl, attrs = dict(NG_MODULE_RULE_ATTRS, **{ diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 88303c3d46..b64abc6716 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -81,9 +81,9 @@ export {getParseErrors, isSyntaxError, syntaxError, Version} from './util'; export {SourceMap} from './output/source_map'; export * from './injectable_compiler_2'; export * from './render3/view/api'; -export {jitPatchDefinition} from './render3/r3_jit'; +export {jitExpression} from './render3/r3_jit'; export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler'; export {makeBindingParser, parseTemplate} from './render3/view/template'; -export {compileComponent, compileDirective} from './render3/view/compiler'; +export {compileComponentFromMetadata, compileDirectiveFromMetadata} from './render3/view/compiler'; // This file only reexports content of the `src` folder. Keep it that way. \ No newline at end of file diff --git a/packages/compiler/src/ml_parser/html_whitespaces.ts b/packages/compiler/src/ml_parser/html_whitespaces.ts index c9302a7288..f948a8481d 100644 --- a/packages/compiler/src/ml_parser/html_whitespaces.ts +++ b/packages/compiler/src/ml_parser/html_whitespaces.ts @@ -49,7 +49,7 @@ export function replaceNgsp(value: string): string { * whitespace removal. The default option for whitespace removal will be revisited in Angular 6 * and might be changed to "on" by default. */ -class WhitespaceVisitor implements html.Visitor { +export class WhitespaceVisitor implements html.Visitor { visitElement(element: html.Element, context: any): any { if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) { // don't descent into elements where we need to preserve whitespaces diff --git a/packages/compiler/src/render3/r3_jit.ts b/packages/compiler/src/render3/r3_jit.ts index 5d1a93e3d9..17ccd0e186 100644 --- a/packages/compiler/src/render3/r3_jit.ts +++ b/packages/compiler/src/render3/r3_jit.ts @@ -50,19 +50,17 @@ class R3JitReflector implements CompileReflector { } /** - * JIT compiles an expression and monkey-patches the result of executing the expression onto a given - * type. + * JIT compiles an expression and returns the result of executing that expression. * - * @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 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 jitPatchDefinition( - type: any, field: string, def: o.Expression, context: {[key: string]: any}, - constantPool?: ConstantPool): void { +export function jitExpression( + def: o.Expression, context: {[key: string]: any}, sourceUrl: string, + constantPool?: ConstantPool): 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. @@ -71,8 +69,6 @@ export function jitPatchDefinition( 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']; + const res = jitStatements(sourceUrl, statements, new R3JitReflector(context), false); + return res['$def']; } diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 71f1624a69..495ccf8976 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -66,6 +66,17 @@ export interface R3DirectiveMetadata { properties: {[key: string]: string}; }; + /** + * Information about usage of specific lifecycle events which require special treatment in the + * code generator. + */ + lifecycle: { + /** + * Whether the directive uses NgOnChanges. + */ + usesOnChanges: boolean; + }; + /** * A mapping of input field names to the property names. */ @@ -101,17 +112,6 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata { ngContentSelectors: string[]; }; - /** - * Information about usage of specific lifecycle events which require special treatment in the - * code generator. - */ - lifecycle: { - /** - * Whether the component uses NgOnChanges. - */ - usesOnChanges: boolean; - }; - /** * Information about the view queries made by the component. */ diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 6075ef0591..a057d83826 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -65,13 +65,22 @@ function baseDirectiveFields( // e.g 'outputs: {a: 'a'}` definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); + // e.g. `features: [NgOnChangesFeature(MyComponent)]` + const features: o.Expression[] = []; + if (meta.lifecycle.usesOnChanges) { + features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type])); + } + if (features.length) { + definitionMap.set('features', o.literalArr(features)); + } + return definitionMap; } /** * Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`. */ -export function compileDirective( +export function compileDirectiveFromMetadata( meta: R3DirectiveMetadata, constantPool: ConstantPool, bindingParser: BindingParser): R3DirectiveDef { const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); @@ -84,7 +93,7 @@ export function compileDirective( /** * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`. */ -export function compileComponent( +export function compileComponentFromMetadata( meta: R3ComponentMetadata, constantPool: ConstantPool, bindingParser: BindingParser): R3ComponentDef { const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); @@ -143,15 +152,6 @@ export function compileComponent( definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed))); } - // e.g. `features: [NgOnChangesFeature(MyComponent)]` - const features: o.Expression[] = []; - if (meta.lifecycle.usesOnChanges) { - features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type])); - } - if (features.length) { - definitionMap.set('features', o.literalArr(features)); - } - const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]); const type = new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)])); @@ -175,7 +175,7 @@ export function compileDirectiveFromRender2( const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive); const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector); - const res = compileDirective(meta, outputCtx.constantPool, bindingParser); + const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser); // Create the partial class to be merged with the actual class. outputCtx.statements.push(new o.ClassStmt( @@ -211,15 +211,11 @@ export function compileComponentFromRender2( hasNgContent: render3Ast.hasNgContent, ngContentSelectors: render3Ast.ngContentSelectors, }, - lifecycle: { - usesOnChanges: - component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges), - }, directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx), pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx), viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx), }; - const res = compileComponent(meta, outputCtx.constantPool, bindingParser); + const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser); // Create the partial class to be merged with the actual class. outputCtx.statements.push(new o.ClassStmt( @@ -251,6 +247,10 @@ function directiveMetadataFromGlobalMetadata( listeners: summary.hostListeners, properties: summary.hostProperties, }, + lifecycle: { + usesOnChanges: + directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges), + }, inputs: directive.inputs, outputs: directive.outputs, }; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index fc1b2a8513..b0d21a9a11 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -16,6 +16,7 @@ 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 {WhitespaceVisitor} from '../../ml_parser/html_whitespaces'; import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; import {ParseError, ParseSourceSpan} from '../../parse_util'; @@ -777,16 +778,24 @@ function interpolate(args: o.Expression[]): o.Expression { * @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): +export function parseTemplate( + template: string, templateUrl: string, options: {preserveWhitespace?: boolean} = {}): {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: []}; } + + let rootNodes: html.Node[] = parseResult.rootNodes; + if (!options.preserveWhitespace) { + rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes); + } + const {nodes, hasNgContent, ngContentSelectors, errors} = - htmlAstToRender3Ast(parseResult.rootNodes, bindingParser); + htmlAstToRender3Ast(rootNodes, bindingParser); if (errors && errors.length > 0) { return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []}; } diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 3bd371cb21..a60ccb687c 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -1078,8 +1078,8 @@ describe('compiler compliance', () => { selectors: [['lifecycle-comp']], factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, inputs: {nameMin: 'name'}, - template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}, - features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)] + features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)], + template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {} });`; const SimpleLayoutDefinition = ` diff --git a/packages/core/src/ivy_switch_legacy.ts b/packages/core/src/ivy_switch_legacy.ts index b3d9427670..0da653c104 100644 --- a/packages/core/src/ivy_switch_legacy.ts +++ b/packages/core/src/ivy_switch_legacy.ts @@ -8,5 +8,6 @@ export const ivyEnabled = false; export const R3_COMPILE_COMPONENT: ((type: any, meta: any) => void)|null = null; +export const R3_COMPILE_DIRECTIVE: ((type: any, meta: any) => void)|null = null; export const R3_COMPILE_INJECTABLE: ((type: any, meta: any) => void)|null = null; export const R3_COMPILE_NGMODULE: ((type: any, meta: any) => void)|null = null; diff --git a/packages/core/src/ivy_switch_on.ts b/packages/core/src/ivy_switch_on.ts index 0dfe7be6ee..7b2d79214a 100644 --- a/packages/core/src/ivy_switch_on.ts +++ b/packages/core/src/ivy_switch_on.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {compileComponentDecorator} from './render3/jit/directive'; +import {compileComponentDecorator, compileDirective} from './render3/jit/directive'; import {compileInjectable} from './render3/jit/injectable'; import {compileNgModule} from './render3/jit/module'; export const ivyEnabled = true; export const R3_COMPILE_COMPONENT = compileComponentDecorator; +export const R3_COMPILE_DIRECTIVE = compileDirective; export const R3_COMPILE_INJECTABLE = compileInjectable; export const R3_COMPILE_NGMODULE = compileNgModule; diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index 34bb977191..ddc1756b63 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -8,7 +8,7 @@ import {ChangeDetectionStrategy} from '../change_detection/constants'; import {Provider} from '../di'; -import {R3_COMPILE_COMPONENT} from '../ivy_switch'; +import {R3_COMPILE_COMPONENT, R3_COMPILE_DIRECTIVE} from '../ivy_switch'; import {Type} from '../type'; import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators'; import {ViewEncapsulation} from './view'; @@ -400,8 +400,9 @@ export interface Directive { * * @Annotation */ -export const Directive: DirectiveDecorator = - makeDecorator('Directive', (dir: Directive = {}) => dir); +export const Directive: DirectiveDecorator = makeDecorator( + 'Directive', (dir: Directive = {}) => dir, undefined, undefined, + (type: Type, meta: Directive) => (R3_COMPILE_DIRECTIVE || (() => {}))(type, meta)); /** * Type of the Component decorator / constructor function. diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 7cabe4f363..c3551ed9cc 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {compileComponent as compileIvyComponent, parseTemplate, ConstantPool, makeBindingParser, WrappedNodeExpr, jitPatchDefinition,} from '@angular/compiler'; +import {ConstantPool, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata as compileIvyComponent, compileDirectiveFromMetadata as compileIvyDirective, jitExpression, makeBindingParser, parseTemplate} from '@angular/compiler'; -import {Component} from '../../metadata/directives'; +import {Component, Directive, HostBinding, Input, Output} from '../../metadata/directives'; import {ReflectionCapabilities} from '../../reflection/reflection_capabilities'; import {Type} from '../../type'; import {angularCoreEnv} from './environment'; -import {reflectDependencies} from './util'; +import {getReflect, reflectDependencies} from './util'; let _pendingPromises: Promise[] = []; @@ -31,45 +31,65 @@ export function compileComponent(type: Type, metadata: Component): Promise< if (!metadata.template) { throw new Error('templateUrl not yet supported'); } + const templateStr = metadata.template; - // Parse the template and check for errors. - const template = parseTemplate(metadata.template !, `ng://${type.name}/template.html`); - if (template.errors !== undefined) { - const errors = template.errors.map(err => err.toString()).join(', '); - throw new Error(`Errors during JIT compilation of template for ${type.name}: ${errors}`); - } + let def: any = null; + Object.defineProperty(type, 'ngComponentDef', { + get: () => { + if (def === null) { + // The ConstantPool is a requirement of the JIT'er. + const constantPool = new ConstantPool(); - // The ConstantPool is a requirement of the JIT'er. - const constantPool = new ConstantPool(); + // Parse the template and check for errors. + const template = parseTemplate(templateStr, `ng://${type.name}/template.html`); + if (template.errors !== undefined) { + const errors = template.errors.map(err => err.toString()).join(', '); + throw new Error(`Errors during JIT compilation of template for ${type.name}: ${errors}`); + } - // Compile the component metadata, including template, into an expression. - // TODO(alxhub): implement inputs, outputs, queries, etc. - const res = compileIvyComponent( - { - name: type.name, - type: new WrappedNodeExpr(type), - selector: metadata.selector !, template, - deps: reflectDependencies(type), - directives: new Map(), - pipes: new Map(), - host: { - attributes: {}, - listeners: {}, - properties: {}, - }, - inputs: {}, - outputs: {}, - lifecycle: { - usesOnChanges: false, - }, - queries: [], - typeSourceSpan: null !, - viewQueries: [], - }, - constantPool, makeBindingParser()); + // Compile the component metadata, including template, into an expression. + // TODO(alxhub): implement inputs, outputs, queries, etc. + const res = compileIvyComponent( + { + ...directiveMetadata(type, metadata), + template, + directives: new Map(), + pipes: new Map(), + viewQueries: [], + }, + constantPool, makeBindingParser()); - // Patch the generated expression as ngComponentDef on the type. - jitPatchDefinition(type, 'ngComponentDef', res.expression, angularCoreEnv, constantPool); + def = jitExpression( + res.expression, angularCoreEnv, `ng://${type.name}/ngComponentDef.js`, constantPool); + } + return def; + }, + }); + + return null; +} + +/** + * Compile an Angular directive according to its decorator metadata, and patch the resulting + * ngDirectiveDef onto the component type. + * + * In the event that compilation is not immediate, `compileDirective` will return a `Promise` which + * will resolve when compilation completes and the directive becomes usable. + */ +export function compileDirective(type: Type, directive: Directive): Promise|null { + let def: any = null; + Object.defineProperty(type, 'ngDirectiveDef', { + get: () => { + if (def === null) { + const constantPool = new ConstantPool(); + const sourceMapUrl = `ng://${type && type.name}/ngDirectiveDef.js`; + const res = compileIvyDirective( + directiveMetadata(type, directive), constantPool, makeBindingParser()); + def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, constantPool); + } + return def; + }, + }); return null; } @@ -95,3 +115,51 @@ export function awaitCurrentlyCompilingComponents(): Promise { _pendingPromises = []; return res; } + +/** + * Extract the `R3DirectiveMetadata` for a particular directive (either a `Directive` or a + * `Component`). + */ +function directiveMetadata(type: Type, metadata: Directive): R3DirectiveMetadata { + // Reflect inputs and outputs. + const props = getReflect().propMetadata(type); + const inputs: {[key: string]: string} = {}; + const outputs: {[key: string]: string} = {}; + + for (let field in props) { + props[field].forEach(ann => { + if (isInput(ann)) { + inputs[field] = ann.bindingPropertyName || field; + } else if (isOutput(ann)) { + outputs[field] = ann.bindingPropertyName || field; + } + }); + } + + return { + name: type.name, + type: new WrappedNodeExpr(type), + selector: metadata.selector !, + deps: reflectDependencies(type), + host: { + attributes: {}, + listeners: {}, + properties: {}, + }, + inputs, + outputs, + queries: [], + lifecycle: { + usesOnChanges: type.prototype.ngOnChanges !== undefined, + }, + typeSourceSpan: null !, + }; +} + +function isInput(value: any): value is Input { + return value.ngMetadataName === 'Input'; +} + +function isOutput(value: any): value is Output { + return value.ngMetadataName === 'Output'; +} diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index a5068eee8c..4714697558 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -17,15 +17,37 @@ import * as r3 from '../index'; * * This should be kept up to date with the public exports of @angular/core. */ -export const angularCoreEnv = { +export const angularCoreEnv: {[name: string]: Function} = { 'ɵdefineComponent': r3.defineComponent, + 'ɵdefineDirective': r3.defineDirective, 'defineInjectable': defineInjectable, 'ɵdefineNgModule': defineNgModule, 'ɵdirectiveInject': r3.directiveInject, 'inject': inject, + 'ɵinjectAttribute': r3.injectAttribute, + 'ɵinjectChangeDetectorRef': r3.injectChangeDetectorRef, + 'ɵinjectElementRef': r3.injectElementRef, + 'ɵinjectTemplateRef': r3.injectTemplateRef, + 'ɵinjectViewContainerRef': r3.injectViewContainerRef, + 'ɵNgOnChangesFeature': r3.NgOnChangesFeature, + 'ɵa': r3.a, + 'ɵb': r3.b, 'ɵC': r3.C, + 'ɵcR': r3.cR, + 'ɵcr': r3.cr, + 'ɵd': r3.d, 'ɵE': r3.E, 'ɵe': r3.e, + 'ɵf0': r3.f0, + 'ɵf1': r3.f1, + 'ɵf2': r3.f2, + 'ɵf3': r3.f3, + 'ɵf4': r3.f4, + 'ɵf5': r3.f5, + 'ɵf6': r3.f6, + 'ɵf7': r3.f7, + 'ɵf8': r3.f8, + 'ɵfV': r3.fV, 'ɵi1': r3.i1, 'ɵi2': r3.i2, 'ɵi3': r3.i3, @@ -34,6 +56,23 @@ export const angularCoreEnv = { 'ɵi6': r3.i6, 'ɵi7': r3.i7, 'ɵi8': r3.i8, + 'ɵk': r3.k, + 'ɵkn': r3.kn, + 'ɵL': r3.L, + 'ɵld': r3.ld, + 'ɵp': r3.p, + 'ɵpb1': r3.pb1, + 'ɵpb2': r3.pb2, + 'ɵpb3': r3.pb3, + 'ɵpb4': r3.pb4, + 'ɵpbV': r3.pbV, + 'ɵQ': r3.Q, + 'ɵqR': r3.qR, + 'ɵs': r3.s, + 'ɵsn': r3.sn, + 'ɵst': r3.st, 'ɵT': r3.T, 'ɵt': r3.t, + 'ɵV': r3.V, + 'ɵv': r3.v, }; diff --git a/packages/core/src/render3/jit/injectable.ts b/packages/core/src/render3/jit/injectable.ts index e0b4bc1a26..eaf199c6a5 100644 --- a/packages/core/src/render3/jit/injectable.ts +++ b/packages/core/src/render3/jit/injectable.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileIvyInjectable, jitPatchDefinition} from '@angular/compiler'; +import {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileIvyInjectable, jitExpression} from '@angular/compiler'; import {Injectable} from '../../di/injectable'; import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../../di/provider'; @@ -27,60 +27,69 @@ export function compileInjectable(type: Type, meta?: Injectable): void { return; } - // Check whether the injectable metadata includes a provider specification. - const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || - isUseValueProvider(meta) || isUseExistingProvider(meta); + let def: any = null; + Object.defineProperty(type, 'ngInjectableDef', { + get: () => { + if (def === null) { + // Check whether the injectable metadata includes a provider specification. + const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || + isUseValueProvider(meta) || isUseExistingProvider(meta); - let deps: R3DependencyMetadata[]|undefined = undefined; - if (!hasAProvider || (isUseClassProvider(meta) && type === meta.useClass)) { - deps = reflectDependencies(type); - } else if (isUseClassProvider(meta)) { - deps = meta.deps && convertDependencies(meta.deps); - } else if (isUseFactoryProvider(meta)) { - deps = meta.deps && convertDependencies(meta.deps) || []; - } + let deps: R3DependencyMetadata[]|undefined = undefined; + if (!hasAProvider || (isUseClassProvider(meta) && type === meta.useClass)) { + deps = reflectDependencies(type); + } else if (isUseClassProvider(meta)) { + deps = meta.deps && convertDependencies(meta.deps); + } else if (isUseFactoryProvider(meta)) { + deps = meta.deps && convertDependencies(meta.deps) || []; + } - // Decide which flavor of factory to generate, based on the provider specified. - // Only one of the use* fields should be set. - let useClass: Expression|undefined = undefined; - let useFactory: Expression|undefined = undefined; - let useValue: Expression|undefined = undefined; - let useExisting: Expression|undefined = undefined; + // Decide which flavor of factory to generate, based on the provider specified. + // Only one of the use* fields should be set. + let useClass: Expression|undefined = undefined; + let useFactory: Expression|undefined = undefined; + let useValue: Expression|undefined = undefined; + let useExisting: Expression|undefined = undefined; - if (!hasAProvider) { - // In the case the user specifies a type provider, treat it as {provide: X, useClass: X}. - // The deps will have been reflected above, causing the factory to create the class by calling - // its constructor with injected deps. - useClass = new WrappedNodeExpr(type); - } else if (isUseClassProvider(meta)) { - // The user explicitly specified useClass, and may or may not have provided deps. - useClass = new WrappedNodeExpr(meta.useClass); - } else if (isUseValueProvider(meta)) { - // The user explicitly specified useValue. - useValue = new WrappedNodeExpr(meta.useValue); - } else if (isUseFactoryProvider(meta)) { - // The user explicitly specified useFactory. - useFactory = new WrappedNodeExpr(meta.useFactory); - } else if (isUseExistingProvider(meta)) { - // The user explicitly specified useExisting. - useExisting = new WrappedNodeExpr(meta.useExisting); - } else { - // Can't happen - either hasAProvider will be false, or one of the providers will be set. - throw new Error(`Unreachable state.`); - } + if (!hasAProvider) { + // In the case the user specifies a type provider, treat it as {provide: X, useClass: X}. + // The deps will have been reflected above, causing the factory to create the class by + // calling + // its constructor with injected deps. + useClass = new WrappedNodeExpr(type); + } else if (isUseClassProvider(meta)) { + // The user explicitly specified useClass, and may or may not have provided deps. + useClass = new WrappedNodeExpr(meta.useClass); + } else if (isUseValueProvider(meta)) { + // The user explicitly specified useValue. + useValue = new WrappedNodeExpr(meta.useValue); + } else if (isUseFactoryProvider(meta)) { + // The user explicitly specified useFactory. + useFactory = new WrappedNodeExpr(meta.useFactory); + } else if (isUseExistingProvider(meta)) { + // The user explicitly specified useExisting. + useExisting = new WrappedNodeExpr(meta.useExisting); + } else { + // Can't happen - either hasAProvider will be false, or one of the providers will be set. + throw new Error(`Unreachable state.`); + } - const {expression} = compileIvyInjectable({ - name: type.name, - type: new WrappedNodeExpr(type), - providedIn: computeProvidedIn(meta.providedIn), - useClass, - useFactory, - useValue, - useExisting, - deps, + const {expression} = compileIvyInjectable({ + name: type.name, + type: new WrappedNodeExpr(type), + providedIn: computeProvidedIn(meta.providedIn), + useClass, + useFactory, + useValue, + useExisting, + deps, + }); + + def = jitExpression(expression, angularCoreEnv, `ng://${type.name}/ngInjectableDef.js`); + } + return def; + }, }); - - jitPatchDefinition(type, 'ngInjectableDef', expression, angularCoreEnv); } function computeProvidedIn(providedIn: Type| string | null | undefined): Expression { diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 991faf2913..51df46a86b 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileIvyNgModule, jitPatchDefinition} from '@angular/compiler'; +import {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileIvyNgModule, jitExpression} from '@angular/compiler'; import {ModuleWithProviders, NgModule, NgModuleDef} from '../../metadata/ng_module'; import {Type} from '../../type'; @@ -33,17 +33,6 @@ export function compileNgModule(type: Type, ngModule: NgModule): void { directives: [] as any[], pipes: [] as any[], }; - flatten(ngModule.declarations || EMPTY_ARRAY).forEach(decl => { - if (decl.ngPipeDef) { - transitiveCompileScope.pipes.push(decl); - } else if (decl.ngComponentDef) { - transitiveCompileScope.directives.push(decl); - patchComponentWithScope(decl, type as any); - } else { - transitiveCompileScope.directives.push(decl); - decl.ngSelectorScope = type; - } - }); function addExportsFrom(module: Type& {ngModuleDef: NgModuleDef}): void { module.ngModuleDef.exports.forEach((exp: any) => { @@ -60,16 +49,49 @@ export function compileNgModule(type: Type, ngModule: NgModule): void { flatten([(ngModule.imports || EMPTY_ARRAY), (ngModule.exports || EMPTY_ARRAY)]) .filter(importExport => isNgModule(importExport)) .forEach(mod => addExportsFrom(mod)); - jitPatchDefinition(type, 'ngModuleDef', res.expression, angularCoreEnv); - ((type as any).ngModuleDef as NgModuleDef).transitiveCompileScope = transitiveCompileScope; + + flatten(ngModule.declarations || EMPTY_ARRAY).forEach(decl => { + if (decl.ngPipeDef) { + transitiveCompileScope.pipes.push(decl); + } else if (decl.ngComponentDef) { + transitiveCompileScope.directives.push(decl); + patchComponentWithScope(decl, type as any); + } else { + transitiveCompileScope.directives.push(decl); + decl.ngSelectorScope = type; + } + }); + + let def: any = null; + Object.defineProperty(type, 'ngModuleDef', { + get: () => { + if (def === null) { + const meta: R3NgModuleMetadata = { + type: wrap(type), + bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(wrap), + declarations: flatten(ngModule.declarations || EMPTY_ARRAY).map(wrap), + imports: + flatten(ngModule.imports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), + exports: + flatten(ngModule.exports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), + emitInline: true, + }; + const res = compileIvyNgModule(meta); + def = jitExpression(res.expression, angularCoreEnv, `ng://${type.name}/ngModuleDef.js`); + def.transitiveCompileScope = transitiveCompileScope; + } + return def; + }, + }); } export function patchComponentWithScope( component: Type& {ngComponentDef: ComponentDef}, module: Type& {ngModuleDef: NgModuleDef}) { component.ngComponentDef.directiveDefs = () => - module.ngModuleDef.transitiveCompileScope !.directives.map( - dir => dir.ngDirectiveDef || dir.ngComponentDef); + module.ngModuleDef.transitiveCompileScope !.directives + .map(dir => dir.ngDirectiveDef || dir.ngComponentDef) + .filter(def => !!def); component.ngComponentDef.pipeDefs = () => module.ngModuleDef.transitiveCompileScope !.pipes.map(pipe => pipe.ngPipeDef); } diff --git a/packages/core/src/render3/jit/util.ts b/packages/core/src/render3/jit/util.ts index 3c07880c82..485ebd5dc9 100644 --- a/packages/core/src/render3/jit/util.ts +++ b/packages/core/src/render3/jit/util.ts @@ -19,9 +19,12 @@ import {Type} from '../../type'; let _reflect: ReflectionCapabilities|null = null; +export function getReflect(): ReflectionCapabilities { + return (_reflect = _reflect || new ReflectionCapabilities()); +} + export function reflectDependencies(type: Type): R3DependencyMetadata[] { - _reflect = _reflect || new ReflectionCapabilities(); - return convertDependencies(_reflect.parameters(type)); + return convertDependencies(getReflect().parameters(type)); } export function convertDependencies(deps: any[]): R3DependencyMetadata[] { diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts index 3ff5b2ae6a..df27d9a748 100644 --- a/packages/core/src/util.ts +++ b/packages/core/src/util.ts @@ -20,7 +20,10 @@ const __window = typeof window !== 'undefined' && window; const __self = typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope && self; const __global = typeof global !== 'undefined' && global; -const _global: {[name: string]: any} = __window || __global || __self; + +// Check __global first, because in Node tests both __global and __window may be defined and _global +// should be __global in that case. +const _global: {[name: string]: any} = __global || __window || __self; const promise: Promise = Promise.resolve(0); /** diff --git a/packages/core/test/bundling/hello_world/BUILD.bazel b/packages/core/test/bundling/hello_world/BUILD.bazel index 66549bedb6..786a215b92 100644 --- a/packages/core/test/bundling/hello_world/BUILD.bazel +++ b/packages/core/test/bundling/hello_world/BUILD.bazel @@ -44,7 +44,7 @@ jasmine_node_test( data = [ ":bundle", ":bundle.js", - ":bundle.min.js", + ":bundle.min.js.br", ":bundle.min_debug.js", ], tags = [ diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index fe5e7d7bbd..8d96a81a46 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -2072,9 +2072,6 @@ { "name": "__extends$7" }, - { - "name": "__global" - }, { "name": "__read" }, diff --git a/packages/core/test/bundling/todo/BUILD.bazel b/packages/core/test/bundling/todo/BUILD.bazel index f8d5684cbb..e3fd43ba1a 100644 --- a/packages/core/test/bundling/todo/BUILD.bazel +++ b/packages/core/test/bundling/todo/BUILD.bazel @@ -27,6 +27,7 @@ ng_rollup_bundle( entry_point = "packages/core/test/bundling/todo/index.js", deps = [ ":todo", + "//packages/common", "//packages/core", ], ) @@ -50,6 +51,7 @@ jasmine_node_test( ":bundle.min.js", ":bundle.min_debug.js", ], + tags = ["ivy-jit"], deps = [":test_lib"], ) diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 11389750f7..6b487a0142 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -119,9 +119,6 @@ { "name": "__extends" }, - { - "name": "__global" - }, { "name": "__read" }, diff --git a/packages/core/test/bundling/todo/index.ts b/packages/core/test/bundling/todo/index.ts index 86e355f953..d5694a3407 100644 --- a/packages/core/test/bundling/todo/index.ts +++ b/packages/core/test/bundling/todo/index.ts @@ -6,6 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ +import 'reflect-metadata'; + import {CommonModule, NgForOf, NgIf} from '@angular/common'; import {Component, Injectable, IterableDiffers, NgModule, defineInjector, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefineDirective as defineDirective, ɵdirectiveInject as directiveInject, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵrenderComponent as renderComponent} from '@angular/core'; @@ -145,35 +147,39 @@ export class ToDoAppComponent { } } -// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. -(CommonModule as any).ngInjectorDef = defineInjector({factory: () => new CommonModule}); +// In JIT mode the @Directive decorators in //packages/common will compile the Ivy fields. When +// running under --define=compile=legacy, //packages/common is not compiled with Ivy fields, so they +// must be monkey-patched on. +if (!(NgIf as any).ngDirectiveDef) { + // TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. + (CommonModule as any).ngInjectorDef = defineInjector({factory: () => new CommonModule}); -// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. -(NgForOf as any).ngDirectiveDef = defineDirective({ - type: NgForOf, - selectors: [['', 'ngFor', '', 'ngForOf', '']], - factory: () => new NgForOf( - injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)), - features: [NgOnChangesFeature({ - ngForOf: 'ngForOf', - ngForTrackBy: 'ngForTrackBy', - ngForTemplate: 'ngForTemplate', - })], - inputs: { - ngForOf: 'ngForOf', - ngForTrackBy: 'ngForTrackBy', - ngForTemplate: 'ngForTemplate', - } -}); - -// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. -(NgIf as any).ngDirectiveDef = defineDirective({ - type: NgIf, - selectors: [['', 'ngIf', '']], - factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef()), - inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'} -}); + // TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. + (NgForOf as any).ngDirectiveDef = defineDirective({ + type: NgForOf, + selectors: [['', 'ngFor', '', 'ngForOf', '']], + factory: () => new NgForOf( + injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)), + features: [NgOnChangesFeature({ + ngForOf: 'ngForOf', + ngForTrackBy: 'ngForTrackBy', + ngForTemplate: 'ngForTemplate', + })], + inputs: { + ngForOf: 'ngForOf', + ngForTrackBy: 'ngForTrackBy', + ngForTemplate: 'ngForTemplate', + } + }); + // TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. + (NgIf as any).ngDirectiveDef = defineDirective({ + type: NgIf, + selectors: [['', 'ngIf', '']], + factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef()), + inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'} + }); +} @NgModule({declarations: [ToDoAppComponent, ToDoAppComponent], imports: [CommonModule]}) export class ToDoAppModule {