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:
parent
1b6b936ef4
commit
919f42fea1
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Expression, IvyInjectableDep, IvyInjectableMetadata, LiteralExpr, WrappedNodeExpr, compileIvyInjectable} from '@angular/compiler';
|
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Decorator} from '../../metadata';
|
import {Decorator} from '../../metadata';
|
||||||
@ -18,20 +18,20 @@ import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api'
|
|||||||
/**
|
/**
|
||||||
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||||
*/
|
*/
|
||||||
export class InjectableCompilerAdapter implements CompilerAdapter<IvyInjectableMetadata> {
|
export class InjectableCompilerAdapter implements CompilerAdapter<R3InjectableMetadata> {
|
||||||
constructor(private checker: ts.TypeChecker) {}
|
constructor(private checker: ts.TypeChecker) {}
|
||||||
|
|
||||||
detect(decorator: Decorator[]): Decorator|undefined {
|
detect(decorator: Decorator[]): Decorator|undefined {
|
||||||
return decorator.find(dec => dec.name === 'Injectable' && dec.from === '@angular/core');
|
return decorator.find(dec => dec.name === 'Injectable' && dec.from === '@angular/core');
|
||||||
}
|
}
|
||||||
|
|
||||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<IvyInjectableMetadata> {
|
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3InjectableMetadata> {
|
||||||
return {
|
return {
|
||||||
analysis: extractInjectableMetadata(node, decorator, this.checker),
|
analysis: extractInjectableMetadata(node, decorator, this.checker),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
compile(node: ts.ClassDeclaration, analysis: IvyInjectableMetadata): AddStaticFieldInstruction {
|
compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): AddStaticFieldInstruction {
|
||||||
const res = compileIvyInjectable(analysis);
|
const res = compileIvyInjectable(analysis);
|
||||||
return {
|
return {
|
||||||
field: 'ngInjectableDef',
|
field: 'ngInjectableDef',
|
||||||
@ -47,7 +47,7 @@ export class InjectableCompilerAdapter implements CompilerAdapter<IvyInjectableM
|
|||||||
*/
|
*/
|
||||||
function extractInjectableMetadata(
|
function extractInjectableMetadata(
|
||||||
clazz: ts.ClassDeclaration, decorator: Decorator,
|
clazz: ts.ClassDeclaration, decorator: Decorator,
|
||||||
checker: ts.TypeChecker): IvyInjectableMetadata {
|
checker: ts.TypeChecker): R3InjectableMetadata {
|
||||||
if (clazz.name === undefined) {
|
if (clazz.name === undefined) {
|
||||||
throw new Error(`@Injectables must have names`);
|
throw new Error(`@Injectables must have names`);
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ function extractInjectableMetadata(
|
|||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
providedIn: new LiteralExpr(null),
|
providedIn: new LiteralExpr(null),
|
||||||
useType: getUseType(clazz, checker),
|
deps: getConstructorDependencies(clazz, checker),
|
||||||
};
|
};
|
||||||
} else if (decorator.args.length === 1) {
|
} else if (decorator.args.length === 1) {
|
||||||
const metaNode = decorator.args[0];
|
const metaNode = decorator.args[0];
|
||||||
@ -84,7 +84,7 @@ function extractInjectableMetadata(
|
|||||||
} else if (meta.has('useFactory')) {
|
} else if (meta.has('useFactory')) {
|
||||||
// useFactory is special - the 'deps' property must be analyzed.
|
// useFactory is special - the 'deps' property must be analyzed.
|
||||||
const factory = new WrappedNodeExpr(meta.get('useFactory') !);
|
const factory = new WrappedNodeExpr(meta.get('useFactory') !);
|
||||||
const deps: IvyInjectableDep[] = [];
|
const deps: R3DependencyMetadata[] = [];
|
||||||
if (meta.has('deps')) {
|
if (meta.has('deps')) {
|
||||||
const depsExpr = meta.get('deps') !;
|
const depsExpr = meta.get('deps') !;
|
||||||
if (!ts.isArrayLiteralExpression(depsExpr)) {
|
if (!ts.isArrayLiteralExpression(depsExpr)) {
|
||||||
@ -95,18 +95,19 @@ function extractInjectableMetadata(
|
|||||||
}
|
}
|
||||||
deps.push(...depsExpr.elements.map(dep => getDep(dep, checker)));
|
deps.push(...depsExpr.elements.map(dep => getDep(dep, checker)));
|
||||||
}
|
}
|
||||||
return {name, type, providedIn, useFactory: {factory, deps}};
|
return {name, type, providedIn, useFactory: factory, deps};
|
||||||
} else {
|
} else {
|
||||||
const useType = getUseType(clazz, checker);
|
const deps = getConstructorDependencies(clazz, checker);
|
||||||
return {name, type, providedIn, useType};
|
return {name, type, providedIn, deps};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Too many arguments to @Injectable`);
|
throw new Error(`Too many arguments to @Injectable`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUseType(clazz: ts.ClassDeclaration, checker: ts.TypeChecker): IvyInjectableDep[] {
|
function getConstructorDependencies(
|
||||||
const useType: IvyInjectableDep[] = [];
|
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] {
|
||||||
|
const useType: R3DependencyMetadata[] = [];
|
||||||
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
|
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
|
||||||
ctorParams.forEach(param => {
|
ctorParams.forEach(param => {
|
||||||
let tokenExpr = param.typeValueExpr;
|
let tokenExpr = param.typeValueExpr;
|
||||||
@ -131,18 +132,20 @@ function getUseType(clazz: ts.ClassDeclaration, checker: ts.TypeChecker): IvyInj
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const token = new WrappedNodeExpr(tokenExpr);
|
const token = new WrappedNodeExpr(tokenExpr);
|
||||||
useType.push({token, optional, self, skipSelf, attribute: false});
|
useType.push(
|
||||||
|
{token, optional, self, skipSelf, host: false, resolved: R3ResolvedDependencyType.Token});
|
||||||
});
|
});
|
||||||
return useType;
|
return useType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep {
|
function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetadata {
|
||||||
const depObj = {
|
const meta: R3DependencyMetadata = {
|
||||||
token: new WrappedNodeExpr(dep),
|
token: new WrappedNodeExpr(dep),
|
||||||
|
host: false,
|
||||||
|
resolved: R3ResolvedDependencyType.Token,
|
||||||
optional: false,
|
optional: false,
|
||||||
self: false,
|
self: false,
|
||||||
skipSelf: false,
|
skipSelf: false,
|
||||||
attribute: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void {
|
function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void {
|
||||||
@ -153,17 +156,17 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep {
|
|||||||
switch (source.name) {
|
switch (source.name) {
|
||||||
case 'Inject':
|
case 'Inject':
|
||||||
if (token !== undefined) {
|
if (token !== undefined) {
|
||||||
depObj.token = new WrappedNodeExpr(token);
|
meta.token = new WrappedNodeExpr(token);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'Optional':
|
case 'Optional':
|
||||||
depObj.optional = true;
|
meta.optional = true;
|
||||||
break;
|
break;
|
||||||
case 'SkipSelf':
|
case 'SkipSelf':
|
||||||
depObj.skipSelf = true;
|
meta.skipSelf = true;
|
||||||
break;
|
break;
|
||||||
case 'Self':
|
case 'Self':
|
||||||
depObj.self = true;
|
meta.self = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,5 +181,5 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return depObj;
|
return meta;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {WrappedNodeExpr, compileIvyInjectable} from '@angular/compiler';
|
import {WrappedNodeExpr} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {IvyCompilation} from './compilation';
|
import {IvyCompilation} from './compilation';
|
||||||
|
@ -22,10 +22,10 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
|||||||
import {OutputEmitter} from '../output/abstract_emitter';
|
import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler';
|
import {compileNgModuleFromRender2 as compileR3Module} from '../render3/r3_module_compiler';
|
||||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
import {compilePipe as compileR3Pipe} from '../render3/r3_pipe_compiler';
|
||||||
import {htmlAstToRender3Ast} from '../render3/r3_template_transform';
|
import {htmlAstToRender3Ast} from '../render3/r3_template_transform';
|
||||||
import {compileComponentFromRender2 as compileIvyComponent, compileDirectiveFromRender2 as compileIvyDirective} from '../render3/view/compiler';
|
import {compileComponentFromRender2 as compileR3Component, compileDirectiveFromRender2 as compileR3Directive} from '../render3/view/compiler';
|
||||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
@ -362,7 +362,7 @@ export class AotCompiler {
|
|||||||
private _compileShallowModules(
|
private _compileShallowModules(
|
||||||
fileName: string, shallowModules: CompileShallowModuleMetadata[],
|
fileName: string, shallowModules: CompileShallowModuleMetadata[],
|
||||||
context: OutputContext): void {
|
context: OutputContext): void {
|
||||||
shallowModules.forEach(module => compileIvyModule(context, module, this._injectableCompiler));
|
shallowModules.forEach(module => compileR3Module(context, module, this._injectableCompiler));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compilePartialModule(
|
private _compilePartialModule(
|
||||||
@ -413,18 +413,18 @@ export class AotCompiler {
|
|||||||
|
|
||||||
pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); });
|
pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); });
|
||||||
|
|
||||||
compileIvyComponent(
|
compileR3Component(
|
||||||
context, directiveMetadata, render3Ast, this.reflector, hostBindingParser,
|
context, directiveMetadata, render3Ast, this.reflector, hostBindingParser,
|
||||||
directiveTypeBySel, pipeTypeByName);
|
directiveTypeBySel, pipeTypeByName);
|
||||||
} else {
|
} else {
|
||||||
compileIvyDirective(context, directiveMetadata, this.reflector, hostBindingParser);
|
compileR3Directive(context, directiveMetadata, this.reflector, hostBindingParser);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pipes.forEach(pipeType => {
|
pipes.forEach(pipeType => {
|
||||||
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
||||||
if (pipeMetadata) {
|
if (pipeMetadata) {
|
||||||
compileIvyPipe(context, pipeMetadata, this.reflector);
|
compileR3Pipe(context, pipeMetadata, this.reflector);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ export {JitCompiler} from './jit/compiler';
|
|||||||
export * from './compile_reflector';
|
export * from './compile_reflector';
|
||||||
export * from './url_resolver';
|
export * from './url_resolver';
|
||||||
export * from './resource_loader';
|
export * from './resource_loader';
|
||||||
|
export {ConstantPool} from './constant_pool';
|
||||||
export {DirectiveResolver} from './directive_resolver';
|
export {DirectiveResolver} from './directive_resolver';
|
||||||
export {PipeResolver} from './pipe_resolver';
|
export {PipeResolver} from './pipe_resolver';
|
||||||
export {NgModuleResolver} from './ng_module_resolver';
|
export {NgModuleResolver} from './ng_module_resolver';
|
||||||
@ -79,4 +80,10 @@ export {ViewCompiler} from './view_compiler/view_compiler';
|
|||||||
export {getParseErrors, isSyntaxError, syntaxError, Version} from './util';
|
export {getParseErrors, isSyntaxError, syntaxError, Version} from './util';
|
||||||
export {SourceMap} from './output/source_map';
|
export {SourceMap} from './output/source_map';
|
||||||
export * from './injectable_compiler_2';
|
export * from './injectable_compiler_2';
|
||||||
|
export * from './render3/view/api';
|
||||||
|
export {jitPatchDefinition} 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';
|
||||||
// This file only reexports content of the `src` folder. Keep it that way.
|
// This file only reexports content of the `src` folder. Keep it that way.
|
@ -65,6 +65,7 @@ export class Identifiers {
|
|||||||
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
|
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
|
||||||
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
|
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
|
||||||
static defineInjectable: o.ExternalReference = {name: 'defineInjectable', moduleName: CORE};
|
static defineInjectable: o.ExternalReference = {name: 'defineInjectable', moduleName: CORE};
|
||||||
|
static InjectableDef: o.ExternalReference = {name: 'InjectableDef', moduleName: CORE};
|
||||||
static ViewEncapsulation: o.ExternalReference = {
|
static ViewEncapsulation: o.ExternalReference = {
|
||||||
name: 'ViewEncapsulation',
|
name: 'ViewEncapsulation',
|
||||||
moduleName: CORE,
|
moduleName: CORE,
|
||||||
|
@ -7,96 +7,107 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {InjectFlags} from './core';
|
import {InjectFlags} from './core';
|
||||||
|
import {Identifiers} from './identifiers';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {Identifiers} from './render3/r3_identifiers';
|
import {R3DependencyMetadata, compileFactoryFunction} from './render3/r3_factory';
|
||||||
|
import {mapToMapExpression} from './render3/util';
|
||||||
|
|
||||||
type MapEntry = {
|
|
||||||
key: string; quoted: boolean; value: o.Expression;
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InjectableDef {
|
export interface InjectableDef {
|
||||||
expression: o.Expression;
|
expression: o.Expression;
|
||||||
type: o.Type;
|
type: o.Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IvyInjectableDep {
|
export interface R3InjectableMetadata {
|
||||||
token: o.Expression;
|
|
||||||
optional: boolean;
|
|
||||||
self: boolean;
|
|
||||||
skipSelf: boolean;
|
|
||||||
attribute: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IvyInjectableMetadata {
|
|
||||||
name: string;
|
name: string;
|
||||||
type: o.Expression;
|
type: o.Expression;
|
||||||
providedIn: o.Expression;
|
providedIn: o.Expression;
|
||||||
useType?: IvyInjectableDep[];
|
|
||||||
useClass?: o.Expression;
|
useClass?: o.Expression;
|
||||||
useFactory?: {factory: o.Expression; deps: IvyInjectableDep[];};
|
useFactory?: o.Expression;
|
||||||
useExisting?: o.Expression;
|
useExisting?: o.Expression;
|
||||||
useValue?: o.Expression;
|
useValue?: o.Expression;
|
||||||
|
deps?: R3DependencyMetadata[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compileIvyInjectable(meta: IvyInjectableMetadata): InjectableDef {
|
export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||||
let ret: o.Expression = o.NULL_EXPR;
|
let factory: o.Expression = o.NULL_EXPR;
|
||||||
if (meta.useType !== undefined) {
|
|
||||||
const args = meta.useType.map(dep => injectDep(dep));
|
function makeFn(ret: o.Expression): o.Expression {
|
||||||
ret = new o.InstantiateExpr(meta.type, args);
|
return o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta.useClass !== undefined || meta.useFactory !== undefined) {
|
||||||
|
// First, handle useClass and useFactory together, since both involve a similar call to
|
||||||
|
// `compileFactoryFunction`. Either dependencies are explicitly specified, in which case
|
||||||
|
// a factory function call is generated, or they're not specified and the calls are special-
|
||||||
|
// cased.
|
||||||
|
if (meta.deps !== undefined) {
|
||||||
|
// Either call `new meta.useClass(...)` or `meta.useFactory(...)`.
|
||||||
|
const fnOrClass: o.Expression = meta.useClass || meta.useFactory !;
|
||||||
|
|
||||||
|
// useNew: true if meta.useClass, false for meta.useFactory.
|
||||||
|
const useNew = meta.useClass !== undefined;
|
||||||
|
|
||||||
|
factory = compileFactoryFunction({
|
||||||
|
name: meta.name,
|
||||||
|
fnOrClass,
|
||||||
|
useNew,
|
||||||
|
injectFn: Identifiers.inject,
|
||||||
|
useOptionalParam: true,
|
||||||
|
deps: meta.deps,
|
||||||
|
});
|
||||||
} else if (meta.useClass !== undefined) {
|
} else if (meta.useClass !== undefined) {
|
||||||
const factory =
|
// Special case for useClass where the factory from the class's ngInjectableDef is used.
|
||||||
new o.ReadPropExpr(new o.ReadPropExpr(meta.useClass, 'ngInjectableDef'), 'factory');
|
if (meta.useClass.isEquivalent(meta.type)) {
|
||||||
ret = new o.InvokeFunctionExpr(factory, []);
|
// For the injectable compiler, useClass represents a foreign type that should be
|
||||||
} else if (meta.useValue !== undefined) {
|
// instantiated to satisfy construction of the given type. It's not valid to specify
|
||||||
ret = meta.useValue;
|
// useClass === type, since the useClass type is expected to already be compiled.
|
||||||
} else if (meta.useExisting !== undefined) {
|
throw new Error(
|
||||||
ret = o.importExpr(Identifiers.inject).callFn([meta.useExisting]);
|
`useClass is the same as the type, but no deps specified, which is invalid.`);
|
||||||
|
}
|
||||||
|
factory =
|
||||||
|
makeFn(new o.ReadPropExpr(new o.ReadPropExpr(meta.useClass, 'ngInjectableDef'), 'factory')
|
||||||
|
.callFn([]));
|
||||||
} else if (meta.useFactory !== undefined) {
|
} else if (meta.useFactory !== undefined) {
|
||||||
const args = meta.useFactory.deps.map(dep => injectDep(dep));
|
// Special case for useFactory where no arguments are passed.
|
||||||
ret = new o.InvokeFunctionExpr(meta.useFactory.factory, args);
|
factory = meta.useFactory.callFn([]);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No instructions for injectable compiler!');
|
// Can't happen - outer conditional guards against both useClass and useFactory being
|
||||||
|
// undefined.
|
||||||
|
throw new Error('Reached unreachable block in injectable compiler.');
|
||||||
|
}
|
||||||
|
} else if (meta.useValue !== undefined) {
|
||||||
|
// Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for
|
||||||
|
// client code because meta.useValue is an Expression which will be defined even if the actual
|
||||||
|
// value is undefined.
|
||||||
|
factory = makeFn(meta.useValue);
|
||||||
|
} else if (meta.useExisting !== undefined) {
|
||||||
|
// useExisting is an `inject` call on the existing token.
|
||||||
|
factory = makeFn(o.importExpr(Identifiers.inject).callFn([meta.useExisting]));
|
||||||
|
} else {
|
||||||
|
// A strict type is compiled according to useClass semantics, except the dependencies are
|
||||||
|
// required.
|
||||||
|
if (meta.deps === undefined) {
|
||||||
|
throw new Error(`Type compilation of an injectable requires dependencies.`);
|
||||||
|
}
|
||||||
|
factory = compileFactoryFunction({
|
||||||
|
name: meta.name,
|
||||||
|
fnOrClass: meta.type,
|
||||||
|
useNew: true,
|
||||||
|
injectFn: Identifiers.inject,
|
||||||
|
useOptionalParam: true,
|
||||||
|
deps: meta.deps,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = meta.type;
|
const token = meta.type;
|
||||||
const providedIn = meta.providedIn;
|
const providedIn = meta.providedIn;
|
||||||
const factory =
|
|
||||||
o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`);
|
|
||||||
|
|
||||||
const expression = o.importExpr({
|
const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression(
|
||||||
moduleName: '@angular/core',
|
{token, factory, providedIn})]);
|
||||||
name: 'defineInjectable',
|
const type = new o.ExpressionType(
|
||||||
}).callFn([mapToMapExpression({token, factory, providedIn})]);
|
o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)]));
|
||||||
const type = new o.ExpressionType(o.importExpr(
|
|
||||||
{
|
|
||||||
moduleName: '@angular/core',
|
|
||||||
name: 'InjectableDef',
|
|
||||||
},
|
|
||||||
[new o.ExpressionType(meta.type)]));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expression, type,
|
expression, type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectDep(dep: IvyInjectableDep): o.Expression {
|
|
||||||
const defaultValue = dep.optional ? o.NULL_EXPR : o.literal(undefined);
|
|
||||||
const flags = o.literal(
|
|
||||||
InjectFlags.Default | (dep.self && InjectFlags.Self || 0) |
|
|
||||||
(dep.skipSelf && InjectFlags.SkipSelf || 0));
|
|
||||||
if (!dep.optional && !dep.skipSelf && !dep.self) {
|
|
||||||
return o.importExpr(Identifiers.inject).callFn([dep.token]);
|
|
||||||
} else {
|
|
||||||
return o.importExpr(Identifiers.inject).callFn([
|
|
||||||
dep.token,
|
|
||||||
defaultValue,
|
|
||||||
flags,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -312,7 +312,7 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
|||||||
ctx.print(expr, `)`);
|
ctx.print(expr, `)`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): never {
|
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
|
||||||
throw new Error('Abstract emitter cannot visit WrappedNodeExpr.');
|
throw new Error('Abstract emitter cannot visit WrappedNodeExpr.');
|
||||||
}
|
}
|
||||||
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): any {
|
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): any {
|
||||||
|
@ -70,9 +70,10 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
|
|||||||
ctx.println(stmt, `};`);
|
ctx.println(stmt, `};`);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): never {
|
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
|
||||||
throw new Error('Cannot emit a WrappedNodeExpr in Javascript.');
|
throw new Error('Cannot emit a WrappedNodeExpr in Javascript.');
|
||||||
}
|
}
|
||||||
|
|
||||||
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): string|null {
|
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): string|null {
|
||||||
if (ast.builtin === o.BuiltinVar.This) {
|
if (ast.builtin === o.BuiltinVar.This) {
|
||||||
ctx.print(ast, 'self');
|
ctx.print(ast, 'self');
|
||||||
|
@ -68,15 +68,12 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
|
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
|
||||||
const value = this.reflector.resolveExternalReference(ast.value);
|
this._emitReferenceToExternal(ast, this.reflector.resolveExternalReference(ast.value), ctx);
|
||||||
let id = this._evalArgValues.indexOf(value);
|
return null;
|
||||||
if (id === -1) {
|
|
||||||
id = this._evalArgValues.length;
|
|
||||||
this._evalArgValues.push(value);
|
|
||||||
const name = identifierName({reference: value}) || 'val';
|
|
||||||
this._evalArgNames.push(`jit_${name}_${id}`);
|
|
||||||
}
|
}
|
||||||
ctx.print(ast, this._evalArgNames[id]);
|
|
||||||
|
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
|
||||||
|
this._emitReferenceToExternal(ast, ast.node, ctx);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,4 +97,16 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
|||||||
}
|
}
|
||||||
return super.visitDeclareClassStmt(stmt, ctx);
|
return super.visitDeclareClassStmt(stmt, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _emitReferenceToExternal(ast: o.Expression, value: any, ctx: EmitterVisitorContext):
|
||||||
|
void {
|
||||||
|
let id = this._evalArgValues.indexOf(value);
|
||||||
|
if (id === -1) {
|
||||||
|
id = this._evalArgValues.length;
|
||||||
|
this._evalArgValues.push(value);
|
||||||
|
const name = identifierName({reference: value}) || 'val';
|
||||||
|
this._evalArgNames.push(`jit_${name}_${id}`);
|
||||||
|
}
|
||||||
|
ctx.print(ast, this._evalArgNames[id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,8 @@ export class Identifiers {
|
|||||||
moduleName: CORE,
|
moduleName: CORE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static defineNgModule: o.ExternalReference = {name: 'ɵdefineNgModule', moduleName: CORE};
|
||||||
|
|
||||||
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
||||||
|
|
||||||
static query: o.ExternalReference = {name: 'ɵQ', 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 {OutputContext} from '../util';
|
||||||
|
|
||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
import {convertMetaToOutput, mapToMapExpression} from './util';
|
||||||
|
|
||||||
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
export interface R3NgModuleDef {
|
||||||
if (Array.isArray(meta)) {
|
expression: o.Expression;
|
||||||
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
type: o.Type;
|
||||||
}
|
additionalStatements: o.Statement[];
|
||||||
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 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,
|
ctx: OutputContext, ngModule: CompileShallowModuleMetadata,
|
||||||
injectableCompiler: InjectableCompiler): void {
|
injectableCompiler: InjectableCompiler): void {
|
||||||
const className = identifierName(ngModule.type) !;
|
const className = identifierName(ngModule.type) !;
|
||||||
@ -58,3 +108,8 @@ export function compileNgModule(
|
|||||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||||
/* methods */[]));
|
/* 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 {ConstantPool} from '../../constant_pool';
|
||||||
import * as core from '../../core';
|
import * as core from '../../core';
|
||||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
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 * 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 {CssSelector, SelectorMatcher} from '../../selector';
|
||||||
|
import {BindingParser} from '../../template_parser/binding_parser';
|
||||||
import {OutputContext, error} from '../../util';
|
import {OutputContext, error} from '../../util';
|
||||||
import * as t from '../r3_ast';
|
import * as t from '../r3_ast';
|
||||||
import {Identifiers as R3} from '../r3_identifiers';
|
import {Identifiers as R3} from '../r3_identifiers';
|
||||||
|
import {htmlAstToRender3Ast} from '../r3_template_transform';
|
||||||
|
|
||||||
import {R3QueryMetadata} from './api';
|
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';
|
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}`);
|
error(`Invalid interpolation argument length ${args.length}`);
|
||||||
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
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(), [],
|
||||||
|
[]);
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ ng_module(
|
|||||||
module_name = "@angular/core",
|
module_name = "@angular/core",
|
||||||
deps = [
|
deps = [
|
||||||
"//packages:types",
|
"//packages:types",
|
||||||
|
"//packages/compiler",
|
||||||
"@rxjs",
|
"@rxjs",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -10,8 +10,9 @@ const resolve = require('rollup-plugin-node-resolve');
|
|||||||
const sourcemaps = require('rollup-plugin-sourcemaps');
|
const sourcemaps = require('rollup-plugin-sourcemaps');
|
||||||
|
|
||||||
const globals = {
|
const globals = {
|
||||||
|
'@angular/compiler': 'ng.compiler',
|
||||||
'rxjs': 'rxjs',
|
'rxjs': 'rxjs',
|
||||||
'rxjs/operators': 'rxjs.operators'
|
'rxjs/operators': 'rxjs.operators',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -6,10 +6,11 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ApplicationInitStatus} from './application_init';
|
import {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
|
||||||
import {ApplicationRef} from './application_ref';
|
import {ApplicationRef} from './application_ref';
|
||||||
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
||||||
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
|
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
|
||||||
|
import {forwardRef} from './di/forward_ref';
|
||||||
import {Inject, Optional, SkipSelf} from './di/metadata';
|
import {Inject, Optional, SkipSelf} from './di/metadata';
|
||||||
import {LOCALE_ID} from './i18n/tokens';
|
import {LOCALE_ID} from './i18n/tokens';
|
||||||
import {Compiler} from './linker/compiler';
|
import {Compiler} from './linker/compiler';
|
||||||
@ -46,7 +47,7 @@ export function _localeFactory(locale?: string): string {
|
|||||||
useFactory: _localeFactory,
|
useFactory: _localeFactory,
|
||||||
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
|
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class ApplicationModule {
|
export class ApplicationModule {
|
||||||
// Inject ApplicationRef to make it eager...
|
// Inject ApplicationRef to make it eager...
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {R3_COMPILE_INJECTABLE} from '../ivy_switch';
|
||||||
import {ReflectionCapabilities} from '../reflection/reflection_capabilities';
|
import {ReflectionCapabilities} from '../reflection/reflection_capabilities';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
import {makeDecorator, makeParamDecorator} from '../util/decorators';
|
import {makeDecorator, makeParamDecorator} from '../util/decorators';
|
||||||
@ -67,10 +68,7 @@ export interface InjectableDecorator {
|
|||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export interface Injectable {
|
export interface Injectable { providedIn?: Type<any>|'root'|null; }
|
||||||
providedIn?: Type<any>|'root'|null;
|
|
||||||
factory: () => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EMPTY_ARRAY: any[] = [];
|
const EMPTY_ARRAY: any[] = [];
|
||||||
|
|
||||||
@ -110,6 +108,20 @@ export function convertInjectableProviderToFactory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports @Injectable() in JIT mode for Render2.
|
||||||
|
*/
|
||||||
|
function preR3InjectableCompile(
|
||||||
|
injectableType: InjectableType<any>,
|
||||||
|
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider): void {
|
||||||
|
if (options && options.providedIn !== undefined && injectableType.ngInjectableDef === undefined) {
|
||||||
|
injectableType.ngInjectableDef = defineInjectable({
|
||||||
|
providedIn: options.providedIn,
|
||||||
|
factory: convertInjectableProviderToFactory(injectableType, options),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injectable decorator and metadata.
|
* Injectable decorator and metadata.
|
||||||
*
|
*
|
||||||
@ -118,16 +130,8 @@ export function convertInjectableProviderToFactory(
|
|||||||
*/
|
*/
|
||||||
export const Injectable: InjectableDecorator = makeDecorator(
|
export const Injectable: InjectableDecorator = makeDecorator(
|
||||||
'Injectable', undefined, undefined, undefined,
|
'Injectable', undefined, undefined, undefined,
|
||||||
(injectableType: InjectableType<any>,
|
(type: Type<any>, meta: Injectable) =>
|
||||||
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
|
(R3_COMPILE_INJECTABLE || preR3InjectableCompile)(type, meta));
|
||||||
if (options && options.providedIn !== undefined &&
|
|
||||||
injectableType.ngInjectableDef === undefined) {
|
|
||||||
injectableType.ngInjectableDef = defineInjectable({
|
|
||||||
providedIn: options.providedIn,
|
|
||||||
factory: convertInjectableProviderToFactory(injectableType, options)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type representing injectable service.
|
* Type representing injectable service.
|
||||||
|
@ -32,3 +32,5 @@
|
|||||||
* symbol in `./ivy_switch_false` and `./ivy_switch_false` depending on the compilation mode.
|
* symbol in `./ivy_switch_false` and `./ivy_switch_false` depending on the compilation mode.
|
||||||
*/
|
*/
|
||||||
export * from './ivy_switch_false';
|
export * from './ivy_switch_false';
|
||||||
|
|
||||||
|
// TODO(alxhub): debug why metadata doesn't properly propagate through this file.
|
||||||
|
@ -7,3 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const ivyEnabled = false;
|
export const ivyEnabled = false;
|
||||||
|
export const R3_COMPILE_COMPONENT: ((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;
|
||||||
|
@ -6,4 +6,11 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {compileComponentDecorator} from './render3/jit/directive';
|
||||||
|
import {compileInjectable} from './render3/jit/injectable';
|
||||||
|
import {compileNgModule} from './render3/jit/module';
|
||||||
|
|
||||||
export const ivyEnabled = true;
|
export const ivyEnabled = true;
|
||||||
|
export const R3_COMPILE_COMPONENT = compileComponentDecorator;
|
||||||
|
export const R3_COMPILE_INJECTABLE = compileInjectable;
|
||||||
|
export const R3_COMPILE_NGMODULE = compileNgModule;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
import {Attribute, ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
|
import {Attribute, ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
|
||||||
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
|
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
|
||||||
import {ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module';
|
import {ModuleWithProviders, NgModule, NgModuleDef, SchemaMetadata, defineNgModule} from './metadata/ng_module';
|
||||||
import {ViewEncapsulation} from './metadata/view';
|
import {ViewEncapsulation} from './metadata/view';
|
||||||
|
|
||||||
export {ANALYZE_FOR_ENTRY_COMPONENTS, Attribute, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';
|
export {ANALYZE_FOR_ENTRY_COMPONENTS, Attribute, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||||
import {Provider} from '../di';
|
import {Provider} from '../di';
|
||||||
|
import {R3_COMPILE_COMPONENT} from '../ivy_switch';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
|
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
|
||||||
|
|
||||||
import {ViewEncapsulation} from './view';
|
import {ViewEncapsulation} from './view';
|
||||||
|
|
||||||
|
|
||||||
@ -754,7 +754,8 @@ export interface Component extends Directive {
|
|||||||
*/
|
*/
|
||||||
export const Component: ComponentDecorator = makeDecorator(
|
export const Component: ComponentDecorator = makeDecorator(
|
||||||
'Component', (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}),
|
'Component', (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}),
|
||||||
Directive);
|
Directive, undefined,
|
||||||
|
(type: Type<any>, meta: Component) => (R3_COMPILE_COMPONENT || (() => {}))(type, meta));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of the Pipe decorator / constructor function.
|
* Type of the Pipe decorator / constructor function.
|
||||||
|
@ -9,9 +9,31 @@
|
|||||||
import {InjectorDef, InjectorType, defineInjector} from '../di/defs';
|
import {InjectorDef, InjectorType, defineInjector} from '../di/defs';
|
||||||
import {convertInjectableProviderToFactory} from '../di/injectable';
|
import {convertInjectableProviderToFactory} from '../di/injectable';
|
||||||
import {Provider} from '../di/provider';
|
import {Provider} from '../di/provider';
|
||||||
|
import {R3_COMPILE_NGMODULE} from '../ivy_switch';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
import {TypeDecorator, makeDecorator} from '../util/decorators';
|
import {TypeDecorator, makeDecorator} from '../util/decorators';
|
||||||
|
|
||||||
|
export interface NgModuleDef<T> {
|
||||||
|
type: T;
|
||||||
|
bootstrap: Type<any>[];
|
||||||
|
declarations: Type<any>[];
|
||||||
|
imports: Type<any>[];
|
||||||
|
exports: Type<any>[];
|
||||||
|
|
||||||
|
transitiveCompileScope: {directives: any[]; pipes: any[];}|undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): never {
|
||||||
|
const res: NgModuleDef<T> = {
|
||||||
|
type: def.type,
|
||||||
|
bootstrap: def.bootstrap || [],
|
||||||
|
declarations: def.declarations || [],
|
||||||
|
imports: def.imports || [],
|
||||||
|
exports: def.exports || [],
|
||||||
|
transitiveCompileScope: undefined,
|
||||||
|
};
|
||||||
|
return res as never;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around a module that also includes the providers.
|
* A wrapper around a module that also includes the providers.
|
||||||
@ -187,15 +209,7 @@ export interface NgModule {
|
|||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function preR3NgModuleCompile(moduleType: InjectorType<any>, metadata: NgModule): void {
|
||||||
* NgModule decorator and metadata.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @Annotation
|
|
||||||
*/
|
|
||||||
export const NgModule: NgModuleDecorator = makeDecorator(
|
|
||||||
'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined,
|
|
||||||
(moduleType: InjectorType<any>, metadata: NgModule) => {
|
|
||||||
let imports = (metadata && metadata.imports) || [];
|
let imports = (metadata && metadata.imports) || [];
|
||||||
if (metadata && metadata.exports) {
|
if (metadata && metadata.exports) {
|
||||||
imports = [...imports, metadata.exports];
|
imports = [...imports, metadata.exports];
|
||||||
@ -206,4 +220,14 @@ export const NgModule: NgModuleDecorator = makeDecorator(
|
|||||||
providers: metadata && metadata.providers,
|
providers: metadata && metadata.providers,
|
||||||
imports: imports,
|
imports: imports,
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NgModule decorator and metadata.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @Annotation
|
||||||
|
*/
|
||||||
|
export const NgModule: NgModuleDecorator = makeDecorator(
|
||||||
|
'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined,
|
||||||
|
(type: Type<any>, meta: NgModule) => (R3_COMPILE_NGMODULE || preR3NgModuleCompile)(type, meta));
|
||||||
|
97
packages/core/src/render3/jit/directive.ts
Normal file
97
packages/core/src/render3/jit/directive.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* @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 {compileComponent as compileIvyComponent, parseTemplate, ConstantPool, makeBindingParser, WrappedNodeExpr, jitPatchDefinition,} from '@angular/compiler';
|
||||||
|
|
||||||
|
import {Component} from '../../metadata/directives';
|
||||||
|
import {ReflectionCapabilities} from '../../reflection/reflection_capabilities';
|
||||||
|
import {Type} from '../../type';
|
||||||
|
|
||||||
|
import {angularCoreEnv} from './environment';
|
||||||
|
import {reflectDependencies} from './util';
|
||||||
|
|
||||||
|
let _pendingPromises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile an Angular component according to its decorator metadata, and patch the resulting
|
||||||
|
* ngComponentDef onto the component type.
|
||||||
|
*
|
||||||
|
* Compilation may be asynchronous (due to the need to resolve URLs for the component template or
|
||||||
|
* other resources, for example). In the event that compilation is not immediate, `compileComponent`
|
||||||
|
* will return a `Promise` which will resolve when compilation completes and the component becomes
|
||||||
|
* usable.
|
||||||
|
*/
|
||||||
|
export function compileComponent(type: Type<any>, metadata: Component): Promise<void>|null {
|
||||||
|
// TODO(alxhub): implement ResourceLoader support for template compilation.
|
||||||
|
if (!metadata.template) {
|
||||||
|
throw new Error('templateUrl not yet supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ConstantPool is a requirement of the JIT'er.
|
||||||
|
const constantPool = new ConstantPool();
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
// Patch the generated expression as ngComponentDef on the type.
|
||||||
|
jitPatchDefinition(type, 'ngComponentDef', res.expression, angularCoreEnv, constantPool);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around `compileComponent` which is intended to be used for the `@Component` decorator.
|
||||||
|
*
|
||||||
|
* This wrapper keeps track of the `Promise` returned by `compileComponent` and will cause
|
||||||
|
* `awaitCurrentlyCompilingComponents` to wait on the compilation to be finished.
|
||||||
|
*/
|
||||||
|
export function compileComponentDecorator(type: Type<any>, metadata: Component): void {
|
||||||
|
const res = compileComponent(type, metadata);
|
||||||
|
if (res !== null) {
|
||||||
|
_pendingPromises.push(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise which will await the compilation of any `@Component`s which have been defined
|
||||||
|
* since the last time `awaitCurrentlyCompilingComponents` was called.
|
||||||
|
*/
|
||||||
|
export function awaitCurrentlyCompilingComponents(): Promise<void> {
|
||||||
|
const res = Promise.all(_pendingPromises).then(() => undefined);
|
||||||
|
_pendingPromises = [];
|
||||||
|
return res;
|
||||||
|
}
|
39
packages/core/src/render3/jit/environment.ts
Normal file
39
packages/core/src/render3/jit/environment.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @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 {defineInjectable} from '../../di/defs';
|
||||||
|
import {inject} from '../../di/injector';
|
||||||
|
import {defineNgModule} from '../../metadata/ng_module';
|
||||||
|
import * as r3 from '../index';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping of the @angular/core API surface used in generated expressions to the actual symbols.
|
||||||
|
*
|
||||||
|
* This should be kept up to date with the public exports of @angular/core.
|
||||||
|
*/
|
||||||
|
export const angularCoreEnv = {
|
||||||
|
'ɵdefineComponent': r3.defineComponent,
|
||||||
|
'defineInjectable': defineInjectable,
|
||||||
|
'ɵdefineNgModule': defineNgModule,
|
||||||
|
'ɵdirectiveInject': r3.directiveInject,
|
||||||
|
'inject': inject,
|
||||||
|
'ɵC': r3.C,
|
||||||
|
'ɵE': r3.E,
|
||||||
|
'ɵe': r3.e,
|
||||||
|
'ɵi1': r3.i1,
|
||||||
|
'ɵi2': r3.i2,
|
||||||
|
'ɵi3': r3.i3,
|
||||||
|
'ɵi4': r3.i4,
|
||||||
|
'ɵi5': r3.i5,
|
||||||
|
'ɵi6': r3.i6,
|
||||||
|
'ɵi7': r3.i7,
|
||||||
|
'ɵi8': r3.i8,
|
||||||
|
'ɵT': r3.T,
|
||||||
|
'ɵt': r3.t,
|
||||||
|
};
|
114
packages/core/src/render3/jit/injectable.ts
Normal file
114
packages/core/src/render3/jit/injectable.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileIvyInjectable, jitPatchDefinition} from '@angular/compiler';
|
||||||
|
|
||||||
|
import {Injectable} from '../../di/injectable';
|
||||||
|
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../../di/provider';
|
||||||
|
import {Type} from '../../type';
|
||||||
|
import {getClosureSafeProperty} from '../../util/property';
|
||||||
|
|
||||||
|
import {angularCoreEnv} from './environment';
|
||||||
|
import {convertDependencies, reflectDependencies} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting
|
||||||
|
* `ngInjectableDef` onto the injectable type.
|
||||||
|
*/
|
||||||
|
export function compileInjectable(type: Type<any>, meta?: Injectable): void {
|
||||||
|
// TODO(alxhub): handle JIT of bare @Injectable().
|
||||||
|
if (!meta) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {expression} = compileIvyInjectable({
|
||||||
|
name: type.name,
|
||||||
|
type: new WrappedNodeExpr(type),
|
||||||
|
providedIn: computeProvidedIn(meta.providedIn),
|
||||||
|
useClass,
|
||||||
|
useFactory,
|
||||||
|
useValue,
|
||||||
|
useExisting,
|
||||||
|
deps,
|
||||||
|
});
|
||||||
|
|
||||||
|
jitPatchDefinition(type, 'ngInjectableDef', expression, angularCoreEnv);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeProvidedIn(providedIn: Type<any>| string | null | undefined): Expression {
|
||||||
|
if (providedIn == null || typeof providedIn === 'string') {
|
||||||
|
return new LiteralExpr(providedIn);
|
||||||
|
} else {
|
||||||
|
return new WrappedNodeExpr(providedIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]};
|
||||||
|
|
||||||
|
function isUseClassProvider(meta: Injectable): meta is UseClassProvider {
|
||||||
|
return (meta as UseClassProvider).useClass !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GET_PROPERTY_NAME = {} as any;
|
||||||
|
const USE_VALUE = getClosureSafeProperty<ValueProvider>(
|
||||||
|
{provide: String, useValue: GET_PROPERTY_NAME}, GET_PROPERTY_NAME);
|
||||||
|
|
||||||
|
function isUseValueProvider(meta: Injectable): meta is Injectable&ValueSansProvider {
|
||||||
|
return USE_VALUE in meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUseFactoryProvider(meta: Injectable): meta is Injectable&FactorySansProvider {
|
||||||
|
return (meta as FactorySansProvider).useFactory !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider {
|
||||||
|
return (meta as ExistingSansProvider).useExisting !== undefined;
|
||||||
|
}
|
94
packages/core/src/render3/jit/module.ts
Normal file
94
packages/core/src/render3/jit/module.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileIvyNgModule, jitPatchDefinition} from '@angular/compiler';
|
||||||
|
|
||||||
|
import {ModuleWithProviders, NgModule, NgModuleDef} from '../../metadata/ng_module';
|
||||||
|
import {Type} from '../../type';
|
||||||
|
import {ComponentDef} from '../interfaces/definition';
|
||||||
|
import {flatten} from '../util';
|
||||||
|
|
||||||
|
import {angularCoreEnv} from './environment';
|
||||||
|
|
||||||
|
const EMPTY_ARRAY: Type<any>[] = [];
|
||||||
|
|
||||||
|
export function compileNgModule(type: Type<any>, ngModule: NgModule): void {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Compute transitiveCompileScope
|
||||||
|
const transitiveCompileScope = {
|
||||||
|
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) => {
|
||||||
|
if (isNgModule(exp)) {
|
||||||
|
addExportsFrom(exp);
|
||||||
|
} else if (exp.ngPipeDef) {
|
||||||
|
transitiveCompileScope.pipes.push(exp);
|
||||||
|
} else {
|
||||||
|
transitiveCompileScope.directives.push(exp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
component.ngComponentDef.pipeDefs = () =>
|
||||||
|
module.ngModuleDef.transitiveCompileScope !.pipes.map(pipe => pipe.ngPipeDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandModuleWithProviders(value: Type<any>| ModuleWithProviders): Type<any> {
|
||||||
|
if (isModuleWithProviders(value)) {
|
||||||
|
return value.ngModule;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrap(value: Type<any>): Expression {
|
||||||
|
return new WrappedNodeExpr(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isModuleWithProviders(value: any): value is ModuleWithProviders {
|
||||||
|
return value.ngModule !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNgModule(value: any): value is Type<any>&{ngModuleDef: NgModuleDef<any>} {
|
||||||
|
return value.ngModuleDef !== undefined;
|
||||||
|
}
|
86
packages/core/src/render3/jit/util.ts
Normal file
86
packages/core/src/render3/jit/util.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* @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 {LiteralExpr, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
|
||||||
|
|
||||||
|
import {Injector} from '../../di/injector';
|
||||||
|
import {Host, Inject, Optional, Self, SkipSelf} from '../../di/metadata';
|
||||||
|
import {ElementRef} from '../../linker/element_ref';
|
||||||
|
import {TemplateRef} from '../../linker/template_ref';
|
||||||
|
import {ViewContainerRef} from '../../linker/view_container_ref';
|
||||||
|
import {Attribute} from '../../metadata/di';
|
||||||
|
import {ReflectionCapabilities} from '../../reflection/reflection_capabilities';
|
||||||
|
import {Type} from '../../type';
|
||||||
|
|
||||||
|
let _reflect: ReflectionCapabilities|null = null;
|
||||||
|
|
||||||
|
export function reflectDependencies(type: Type<any>): R3DependencyMetadata[] {
|
||||||
|
_reflect = _reflect || new ReflectionCapabilities();
|
||||||
|
return convertDependencies(_reflect.parameters(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertDependencies(deps: any[]): R3DependencyMetadata[] {
|
||||||
|
return deps.map(dep => reflectDependency(dep));
|
||||||
|
}
|
||||||
|
|
||||||
|
function reflectDependency(dep: any | any[]): R3DependencyMetadata {
|
||||||
|
const meta: R3DependencyMetadata = {
|
||||||
|
token: new LiteralExpr(null),
|
||||||
|
host: false,
|
||||||
|
optional: false,
|
||||||
|
resolved: R3ResolvedDependencyType.Token,
|
||||||
|
self: false,
|
||||||
|
skipSelf: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function setTokenAndResolvedType(token: any): void {
|
||||||
|
if (token === ElementRef) {
|
||||||
|
meta.resolved = R3ResolvedDependencyType.ElementRef;
|
||||||
|
} else if (token === Injector) {
|
||||||
|
meta.resolved = R3ResolvedDependencyType.Injector;
|
||||||
|
} else if (token === TemplateRef) {
|
||||||
|
meta.resolved = R3ResolvedDependencyType.TemplateRef;
|
||||||
|
} else if (token === ViewContainerRef) {
|
||||||
|
meta.resolved = R3ResolvedDependencyType.ViewContainerRef;
|
||||||
|
} else {
|
||||||
|
meta.resolved = R3ResolvedDependencyType.Token;
|
||||||
|
}
|
||||||
|
meta.token = new WrappedNodeExpr(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(dep)) {
|
||||||
|
if (dep.length === 0) {
|
||||||
|
throw new Error('Dependency array must have arguments.');
|
||||||
|
}
|
||||||
|
for (let j = 0; j < dep.length; j++) {
|
||||||
|
const param = dep[j];
|
||||||
|
if (param instanceof Optional || param.__proto__.ngMetadataName === 'Optional') {
|
||||||
|
meta.optional = true;
|
||||||
|
} else if (param instanceof SkipSelf || param.__proto__.ngMetadataName === 'SkipSelf') {
|
||||||
|
meta.skipSelf = true;
|
||||||
|
} else if (param instanceof Self || param.__proto__.ngMetadataName === 'Self') {
|
||||||
|
meta.self = true;
|
||||||
|
} else if (param instanceof Host || param.__proto__.ngMetadataName === 'Host') {
|
||||||
|
meta.host = true;
|
||||||
|
} else if (param instanceof Inject) {
|
||||||
|
meta.token = new WrappedNodeExpr(param.token);
|
||||||
|
} else if (param instanceof Attribute) {
|
||||||
|
if (param.attributeName === undefined) {
|
||||||
|
throw new Error(`Attribute name must be defined.`);
|
||||||
|
}
|
||||||
|
meta.token = new LiteralExpr(param.attributeName);
|
||||||
|
meta.resolved = R3ResolvedDependencyType.Attribute;
|
||||||
|
} else {
|
||||||
|
setTokenAndResolvedType(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTokenAndResolvedType(dep);
|
||||||
|
}
|
||||||
|
return meta;
|
||||||
|
}
|
58
packages/core/test/bundling/hello_world_jit/BUILD.bazel
Normal file
58
packages/core/test/bundling/hello_world_jit/BUILD.bazel
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load("//tools:defaults.bzl", "ts_library", "ivy_ng_module")
|
||||||
|
load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test")
|
||||||
|
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test", "rollup_bundle")
|
||||||
|
load("//tools/http-server:http_server.bzl", "http_server")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "hello_world_jit",
|
||||||
|
srcs = ["index.ts"],
|
||||||
|
deps = [
|
||||||
|
"//packages/core",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
rollup_bundle(
|
||||||
|
name = "bundle",
|
||||||
|
# TODO(alexeagle): This is inconsistent.
|
||||||
|
# We try to teach users to always have their workspace at the start of a
|
||||||
|
# path, to disambiguate from other workspaces.
|
||||||
|
# Here, the rule implementation is looking in an execroot where the layout
|
||||||
|
# has an "external" directory for external dependencies.
|
||||||
|
# This should probably start with "angular/" and let the rule deal with it.
|
||||||
|
entry_point = "packages/core/test/bundling/hello_world_jit/index.js",
|
||||||
|
deps = [
|
||||||
|
":hello_world_jit",
|
||||||
|
"//packages/core",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "test_lib",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = glob(["*_spec.ts"]),
|
||||||
|
deps = [
|
||||||
|
"//packages:types",
|
||||||
|
"//packages/core",
|
||||||
|
"//packages/core/testing",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
jasmine_node_test(
|
||||||
|
name = "test",
|
||||||
|
data = [
|
||||||
|
":bundle",
|
||||||
|
":bundle.js",
|
||||||
|
],
|
||||||
|
deps = [":test_lib"],
|
||||||
|
)
|
||||||
|
|
||||||
|
http_server(
|
||||||
|
name = "devserver",
|
||||||
|
data = [
|
||||||
|
"index.html",
|
||||||
|
":bundle.min.js",
|
||||||
|
":bundle.min_debug.js",
|
||||||
|
],
|
||||||
|
)
|
31
packages/core/test/bundling/hello_world_jit/index.html
Normal file
31
packages/core/test/bundling/hello_world_jit/index.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Angular Hello World Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- The Angular application will be bootstrapped into this element. -->
|
||||||
|
<hello-world></hello-world>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Script tag which bootstraps the application. Use `?debug` in URL to select
|
||||||
|
the debug version of the script.
|
||||||
|
|
||||||
|
There are two scripts sources: `bundle.min.js` and `bundle.min_debug.js` You can
|
||||||
|
switch between which bundle the browser loads to experiment with the application.
|
||||||
|
|
||||||
|
- `bundle.min.js`: Is what the site would serve to their users. It has gone
|
||||||
|
through rollup, build-optimizer, and uglify with tree shaking.
|
||||||
|
- `bundle.min_debug.js`: Is what the developer would like to see when debugging
|
||||||
|
the application. It has also done through full pipeline of rollup, build-optimizer,
|
||||||
|
and uglify, however special flags were passed to uglify to prevent inlining and
|
||||||
|
property renaming.
|
||||||
|
-->
|
||||||
|
<script>
|
||||||
|
document.write('<script src="' +
|
||||||
|
(document.location.search.endsWith('debug') ? '/bundle.min_debug.js' : '/bundle.min.js') +
|
||||||
|
'"></' + 'script>');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
38
packages/core/test/bundling/hello_world_jit/index.ts
Normal file
38
packages/core/test/bundling/hello_world_jit/index.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 'reflect-metadata';
|
||||||
|
|
||||||
|
import {Component, NgModule, ɵrenderComponent as renderComponent} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'greeting-cmp',
|
||||||
|
template: 'Hello World!',
|
||||||
|
})
|
||||||
|
export class Greeting {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Greeting],
|
||||||
|
exports: [Greeting],
|
||||||
|
})
|
||||||
|
export class GreetingModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'hello-world', template: '<greeting-cmp></greeting-cmp>'})
|
||||||
|
export class HelloWorld {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [HelloWorld],
|
||||||
|
imports: [GreetingModule],
|
||||||
|
})
|
||||||
|
export class HelloWorldModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
renderComponent(HelloWorld);
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @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 {ɵivyEnabled as ivyEnabled} from '@angular/core';
|
||||||
|
import {withBody} from '@angular/core/testing';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
const PACKAGE = 'angular/packages/core/test/bundling/hello_world_jit';
|
||||||
|
|
||||||
|
ivyEnabled && describe('Ivy JIT hello world', () => {
|
||||||
|
it('should render hello world', withBody('<hello-world></hello-world>', () => {
|
||||||
|
require(path.join(PACKAGE, 'bundle.js'));
|
||||||
|
expect(document.body.textContent).toEqual('Hello World!');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('ensure at least one spec exists', () => {});
|
@ -1325,6 +1325,9 @@
|
|||||||
{
|
{
|
||||||
"name": "Quote"
|
"name": "Quote"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "R3_COMPILE_INJECTABLE"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "REMOVE_EVENT_LISTENER"
|
"name": "REMOVE_EVENT_LISTENER"
|
||||||
},
|
},
|
||||||
@ -3620,6 +3623,9 @@
|
|||||||
{
|
{
|
||||||
"name": "platformCoreDynamic"
|
"name": "platformCoreDynamic"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "preR3InjectableCompile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "preparseElement"
|
"name": "preparseElement"
|
||||||
},
|
},
|
||||||
|
@ -12,6 +12,7 @@ ts_library(
|
|||||||
"**/*_perf.ts",
|
"**/*_perf.ts",
|
||||||
"domino.d.ts",
|
"domino.d.ts",
|
||||||
"load_domino.ts",
|
"load_domino.ts",
|
||||||
|
"jit_spec.ts",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
deps = [
|
deps = [
|
||||||
@ -29,19 +30,28 @@ ts_library(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ts_library(
|
ts_library(
|
||||||
name = "render3_node_lib",
|
name = "domino",
|
||||||
testonly = 1,
|
testonly = 1,
|
||||||
srcs = [
|
srcs = [
|
||||||
"domino.d.ts",
|
"domino.d.ts",
|
||||||
"load_domino.ts",
|
"load_domino.ts",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
":render3_lib",
|
|
||||||
"//packages/platform-browser",
|
"//packages/platform-browser",
|
||||||
"//packages/platform-server",
|
"//packages/platform-server",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "render3_node_lib",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = [],
|
||||||
|
deps = [
|
||||||
|
":domino",
|
||||||
|
":render3_lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
jasmine_node_test(
|
jasmine_node_test(
|
||||||
name = "render3",
|
name = "render3",
|
||||||
bootstrap = [
|
bootstrap = [
|
||||||
|
41
packages/core/test/render3/ivy/BUILD.bazel
Normal file
41
packages/core/test/render3/ivy/BUILD.bazel
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load("//tools:defaults.bzl", "ts_library", "ts_web_test")
|
||||||
|
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "ivy_lib",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = glob(["**/*.ts"]),
|
||||||
|
deps = [
|
||||||
|
"//packages:types",
|
||||||
|
"//packages/core",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "ivy_node_lib",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = [],
|
||||||
|
deps = [
|
||||||
|
":ivy_lib",
|
||||||
|
"//packages/core/test/render3:domino",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
jasmine_node_test(
|
||||||
|
name = "ivy",
|
||||||
|
bootstrap = [
|
||||||
|
"angular/packages/core/test/render3/load_domino",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
":ivy_node_lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
ts_web_test(
|
||||||
|
name = "ivy_web",
|
||||||
|
deps = [
|
||||||
|
":ivy_lib",
|
||||||
|
],
|
||||||
|
)
|
166
packages/core/test/render3/ivy/jit_spec.ts
Normal file
166
packages/core/test/render3/ivy/jit_spec.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Injectable} from '@angular/core/src/di/injectable';
|
||||||
|
import {inject, setCurrentInjector} from '@angular/core/src/di/injector';
|
||||||
|
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
||||||
|
import {Component} from '@angular/core/src/metadata/directives';
|
||||||
|
import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module';
|
||||||
|
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
|
||||||
|
|
||||||
|
ivyEnabled && describe('render3 jit', () => {
|
||||||
|
let injector: any;
|
||||||
|
beforeAll(() => { injector = setCurrentInjector(null); });
|
||||||
|
|
||||||
|
afterAll(() => { setCurrentInjector(injector); });
|
||||||
|
|
||||||
|
it('compiles a component', () => {
|
||||||
|
@Component({
|
||||||
|
template: 'test',
|
||||||
|
selector: 'test-cmp',
|
||||||
|
})
|
||||||
|
class SomeCmp {
|
||||||
|
}
|
||||||
|
const SomeCmpAny = SomeCmp as any;
|
||||||
|
|
||||||
|
expect(SomeCmpAny.ngComponentDef).toBeDefined();
|
||||||
|
expect(SomeCmpAny.ngComponentDef.factory() instanceof SomeCmp).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles an injectable with a type provider', () => {
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
class Service {
|
||||||
|
}
|
||||||
|
const ServiceAny = Service as any;
|
||||||
|
|
||||||
|
expect(ServiceAny.ngInjectableDef).toBeDefined();
|
||||||
|
expect(ServiceAny.ngInjectableDef.providedIn).toBe('root');
|
||||||
|
expect(inject(Service) instanceof Service).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles an injectable with a useValue provider', () => {
|
||||||
|
@Injectable({providedIn: 'root', useValue: 'test'})
|
||||||
|
class Service {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(inject(Service)).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles an injectable with a useExisting provider', () => {
|
||||||
|
@Injectable({providedIn: 'root', useValue: 'test'})
|
||||||
|
class Existing {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root', useExisting: Existing})
|
||||||
|
class Service {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(inject(Service)).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles an injectable with a useFactory provider, without deps', () => {
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root', useFactory: () => 'test'})
|
||||||
|
class Service {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(inject(Service)).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles an injectable with a useFactory provider, with deps', () => {
|
||||||
|
@Injectable({providedIn: 'root', useValue: 'test'})
|
||||||
|
class Existing {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root', useFactory: (existing: any) => existing, deps: [Existing]})
|
||||||
|
class Service {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(inject(Service)).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles an injectable with a useClass provider, with deps', () => {
|
||||||
|
@Injectable({providedIn: 'root', useValue: 'test'})
|
||||||
|
class Existing {
|
||||||
|
}
|
||||||
|
|
||||||
|
class Other {
|
||||||
|
constructor(public value: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root', useClass: Other, deps: [Existing]})
|
||||||
|
class Service {
|
||||||
|
get value(): any { return null; }
|
||||||
|
}
|
||||||
|
const ServiceAny = Service as any;
|
||||||
|
|
||||||
|
expect(inject(Service).value).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles an injectable with a useClass provider, without deps', () => {
|
||||||
|
let _value = 1;
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
class Existing {
|
||||||
|
readonly value = _value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root', useClass: Existing})
|
||||||
|
class Service {
|
||||||
|
get value(): number { return 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(inject(Existing).value).toBe(1);
|
||||||
|
const injected = inject(Service);
|
||||||
|
expect(injected instanceof Existing).toBe(true);
|
||||||
|
expect(injected.value).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compiles a module to a definition', () => {
|
||||||
|
@Component({
|
||||||
|
template: 'foo',
|
||||||
|
selector: 'foo',
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Cmp],
|
||||||
|
})
|
||||||
|
class Module {
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
|
||||||
|
expect(moduleDef).toBeDefined();
|
||||||
|
expect(moduleDef.declarations.length).toBe(1);
|
||||||
|
expect(moduleDef.declarations[0]).toBe(Cmp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('patches a module onto the component', () => {
|
||||||
|
@Component({
|
||||||
|
template: 'foo',
|
||||||
|
selector: 'foo',
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
}
|
||||||
|
const cmpDef: ComponentDef<Cmp> = (Cmp as any).ngComponentDef;
|
||||||
|
|
||||||
|
expect(cmpDef.directiveDefs).toBeNull();
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Cmp],
|
||||||
|
})
|
||||||
|
class Module {
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
|
||||||
|
expect(cmpDef.directiveDefs instanceof Function).toBe(true);
|
||||||
|
expect((cmpDef.directiveDefs as Function)()).toEqual([cmpDef]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ensure at least one spec exists', () => {});
|
Loading…
x
Reference in New Issue
Block a user