angular-cn/packages/compiler/src/injectable_compiler.ts
Igor Minar 9c210281d4 feat(compiler): emit @__PURE__ or @pureOrBreakMyCode annotations in the generated code (#41096)
This change marks all relevant define* callsites as pure, causing the compiler to
emmit either @__PURE__ or @pureOrBreakMyCode annotation based on whether we are
compiling code annotated for closure or terser.

This change is needed in g3 where we don't run build optimizer but we
need the code to be annotated for the closure compiler.

Additionally this change allows for simplification of CLI and build optimizer as they
will no longer need to rewrite the generated code (there are still other places where
a build optimizer rewrite will be necessary so we can't remove it, we can only simplify it).

PR Close #41096
2021-03-08 10:30:08 -08:00

138 lines
4.9 KiB
TypeScript

/**
* @license
* Copyright Google LLC 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 {CompileInjectableMetadata, CompileNgModuleMetadata, CompileProviderMetadata, identifierName} from './compile_metadata';
import {CompileReflector} from './compile_reflector';
import {InjectFlags, NodeFlags} from './core';
import {Identifiers} from './identifiers';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
import {typeSourceSpan} from './parse_util';
import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {OutputContext} from './util';
import {componentFactoryResolverProviderDef, depDef, providerDef} from './view_compiler/provider_compiler';
type MapEntry = {
key: string,
quoted: boolean,
value: o.Expression
};
type MapLiteral = MapEntry[];
function mapEntry(key: string, value: o.Expression): MapEntry {
return {key, value, quoted: false};
}
export class InjectableCompiler {
private tokenInjector: StaticSymbol;
constructor(private reflector: CompileReflector, private alwaysGenerateDef: boolean) {
this.tokenInjector = reflector.resolveExternalReference(Identifiers.Injector);
}
private depsArray(deps: any[], ctx: OutputContext): o.Expression[] {
return deps.map(dep => {
let token = dep;
let args = [token];
let flags: InjectFlags = InjectFlags.Default;
if (Array.isArray(dep)) {
for (let i = 0; i < dep.length; i++) {
const v = dep[i];
if (v) {
if (v.ngMetadataName === 'Optional') {
flags |= InjectFlags.Optional;
} else if (v.ngMetadataName === 'SkipSelf') {
flags |= InjectFlags.SkipSelf;
} else if (v.ngMetadataName === 'Self') {
flags |= InjectFlags.Self;
} else if (v.ngMetadataName === 'Inject') {
token = v.token;
} else {
token = v;
}
}
}
}
let tokenExpr: o.Expression;
if (typeof token === 'string') {
tokenExpr = o.literal(token);
} else if (token === this.tokenInjector) {
tokenExpr = o.importExpr(Identifiers.INJECTOR);
} else {
tokenExpr = ctx.importExpr(token);
}
if (flags !== InjectFlags.Default) {
args = [tokenExpr, o.literal(flags)];
} else {
args = [tokenExpr];
}
return o.importExpr(Identifiers.inject).callFn(args);
});
}
factoryFor(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
let retValue: o.Expression;
if (injectable.useExisting) {
retValue = o.importExpr(Identifiers.inject).callFn([ctx.importExpr(injectable.useExisting)]);
} else if (injectable.useFactory) {
const deps = injectable.deps || [];
if (deps.length > 0) {
retValue = ctx.importExpr(injectable.useFactory).callFn(this.depsArray(deps, ctx));
} else {
return ctx.importExpr(injectable.useFactory);
}
} else if (injectable.useValue) {
retValue = convertValueToOutputAst(ctx, injectable.useValue);
} else {
const clazz = injectable.useClass || injectable.symbol;
const depArgs = this.depsArray(this.reflector.parameters(clazz), ctx);
retValue = new o.InstantiateExpr(ctx.importExpr(clazz), depArgs);
}
return o.fn(
[], [new o.ReturnStatement(retValue)], undefined, undefined,
injectable.symbol.name + '_Factory');
}
injectableDef(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression {
let providedIn: o.Expression = o.NULL_EXPR;
if (injectable.providedIn !== undefined) {
if (injectable.providedIn === null) {
providedIn = o.NULL_EXPR;
} else if (typeof injectable.providedIn === 'string') {
providedIn = o.literal(injectable.providedIn);
} else {
providedIn = ctx.importExpr(injectable.providedIn);
}
}
const def: MapLiteral = [
mapEntry('factory', this.factoryFor(injectable, ctx)),
mapEntry('token', ctx.importExpr(injectable.type.reference)),
mapEntry('providedIn', providedIn),
];
return o.importExpr(Identifiers.ɵɵdefineInjectable)
.callFn([o.literalMap(def)], undefined, true);
}
compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void {
if (this.alwaysGenerateDef || injectable.providedIn !== undefined) {
const className = identifierName(injectable.type)!;
const clazz = new o.ClassStmt(
className, null,
[
new o.ClassField(
'ɵprov', o.INFERRED_TYPE, [o.StmtModifier.Static],
this.injectableDef(injectable, ctx)),
],
[], new o.ClassMethod(null, [], []), []);
ctx.statements.push(clazz);
}
}
}