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
This commit is contained in:
Alex Rickabaugh 2018-05-21 08:15:19 -07:00 committed by Victor Berchet
parent 24e5c5b425
commit 646b42a113
22 changed files with 348 additions and 194 deletions

View File

@ -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, **{

View File

@ -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.

View File

@ -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

View File

@ -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'];
}

View File

@ -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.
*/

View File

@ -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,
};

View File

@ -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: []};
}

View File

@ -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 = `

View File

@ -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;

View File

@ -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;

View File

@ -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<any>, meta: Directive) => (R3_COMPILE_DIRECTIVE || (() => {}))(type, meta));
/**
* Type of the Component decorator / constructor function.

View File

@ -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<void>[] = [];
@ -31,45 +31,65 @@ export function compileComponent(type: Type<any>, 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<any>, directive: Directive): Promise<void>|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<void> {
_pendingPromises = [];
return res;
}
/**
* Extract the `R3DirectiveMetadata` for a particular directive (either a `Directive` or a
* `Component`).
*/
function directiveMetadata(type: Type<any>, 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';
}

View File

@ -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,
};

View File

@ -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<any>, 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<any>| string | null | undefined): Expression {

View File

@ -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<any>, 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<any>& {ngModuleDef: NgModuleDef<any>}): void {
module.ngModuleDef.exports.forEach((exp: any) => {
@ -60,16 +49,49 @@ export function compileNgModule(type: Type<any>, 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<any>).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<C, M>(
component: Type<C>& {ngComponentDef: ComponentDef<C>},
module: Type<M>& {ngModuleDef: NgModuleDef<M>}) {
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);
}

View File

@ -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<any>): R3DependencyMetadata[] {
_reflect = _reflect || new ReflectionCapabilities();
return convertDependencies(_reflect.parameters(type));
return convertDependencies(getReflect().parameters(type));
}
export function convertDependencies(deps: any[]): R3DependencyMetadata[] {

View File

@ -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<any> = Promise.resolve(0);
/**

View File

@ -44,7 +44,7 @@ jasmine_node_test(
data = [
":bundle",
":bundle.js",
":bundle.min.js",
":bundle.min.js.br",
":bundle.min_debug.js",
],
tags = [

View File

@ -2072,9 +2072,6 @@
{
"name": "__extends$7"
},
{
"name": "__global"
},
{
"name": "__read"
},

View File

@ -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"],
)

View File

@ -119,9 +119,6 @@
{
"name": "__extends"
},
{
"name": "__global"
},
{
"name": "__read"
},

View File

@ -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 {