angular/packages/compiler/src/injectable_compiler_2.ts
Alex Rickabaugh 919f42fea1 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
2018-05-21 19:13:50 -04:00

114 lines
4.3 KiB
TypeScript

/**
* @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 {InjectFlags} from './core';
import {Identifiers} from './identifiers';
import * as o from './output/output_ast';
import {R3DependencyMetadata, compileFactoryFunction} from './render3/r3_factory';
import {mapToMapExpression} from './render3/util';
export interface InjectableDef {
expression: o.Expression;
type: o.Type;
}
export interface R3InjectableMetadata {
name: string;
type: o.Expression;
providedIn: o.Expression;
useClass?: o.Expression;
useFactory?: o.Expression;
useExisting?: o.Expression;
useValue?: o.Expression;
deps?: R3DependencyMetadata[];
}
export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
let factory: o.Expression = o.NULL_EXPR;
function makeFn(ret: o.Expression): o.Expression {
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) {
// Special case for useClass where the factory from the class's ngInjectableDef is used.
if (meta.useClass.isEquivalent(meta.type)) {
// For the injectable compiler, useClass represents a foreign type that should be
// instantiated to satisfy construction of the given type. It's not valid to specify
// useClass === type, since the useClass type is expected to already be compiled.
throw new Error(
`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) {
// Special case for useFactory where no arguments are passed.
factory = meta.useFactory.callFn([]);
} else {
// 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 providedIn = meta.providedIn;
const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression(
{token, factory, providedIn})]);
const type = new o.ExpressionType(
o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)]));
return {
expression, type,
};
}