Previously, injector definitions contained a `factory` property that was used to create a new instance of the associated NgModule class. Now this factory has been moved to its own `ɵfac` static property on the NgModule class itself. This is inline with how directives, components and pipes are created. There is a small size increase to bundle sizes for each NgModule class, because the `ɵfac` takes up a bit more space: Before: ```js let a = (() => { class n {} return n.\u0275mod = c.Cb({type: n}), n.\u0275inj = c.Bb({factory: function(t) { return new (t || n) }, imports: [[e.a.forChild(s)], e.a]}), n })(), ``` After: ```js let a = (() => { class n {} return n.\u0275fac = function(t) { return new (t || n) }, n.\u0275mod = c.Cb({type: n}), n.\u0275inj = c.Bb({imports: [[r.a.forChild(s)], r.a]}), n })(), ``` In other words `n.\u0275fac = ` is longer than `factory: ` (by 5 characters) and only because the tooling insists on encoding `ɵ` as `\u0275`. This can be mitigated in a future PR by only generating the `ɵfac` property if it is actually needed. PR Close #41022
258 lines
7.6 KiB
TypeScript
258 lines
7.6 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 * as o from '../output/output_ast';
|
|
|
|
import {R3DependencyMetadata, R3FactoryFn} from './r3_factory';
|
|
import {Identifiers as R3} from './r3_identifiers';
|
|
import {jitOnlyGuardedExpression, mapToMapExpression, R3Reference} from './util';
|
|
|
|
export interface R3NgModuleDef {
|
|
expression: o.Expression;
|
|
type: o.Type;
|
|
additionalStatements: o.Statement[];
|
|
}
|
|
|
|
/**
|
|
* Metadata required by the module compiler to generate a module def (`ɵmod`) for a type.
|
|
*/
|
|
export interface R3NgModuleMetadata {
|
|
/**
|
|
* An expression representing the module type being compiled.
|
|
*/
|
|
type: R3Reference;
|
|
|
|
/**
|
|
* An expression representing the module type being compiled, intended for use within a class
|
|
* definition itself.
|
|
*
|
|
* This can differ from the outer `type` if the class is being compiled by ngcc and is inside
|
|
* an IIFE structure that uses a different name internally.
|
|
*/
|
|
internalType: o.Expression;
|
|
|
|
/**
|
|
* An expression intended for use by statements that are adjacent (i.e. tightly coupled) to but
|
|
* not internal to a class definition.
|
|
*
|
|
* This can differ from the outer `type` if the class is being compiled by ngcc and is inside
|
|
* an IIFE structure that uses a different name internally.
|
|
*/
|
|
adjacentType: o.Expression;
|
|
|
|
/**
|
|
* An array of expressions representing the bootstrap components specified by the module.
|
|
*/
|
|
bootstrap: R3Reference[];
|
|
|
|
/**
|
|
* An array of expressions representing the directives and pipes declared by the module.
|
|
*/
|
|
declarations: R3Reference[];
|
|
|
|
/**
|
|
* An array of expressions representing the imports of the module.
|
|
*/
|
|
imports: R3Reference[];
|
|
|
|
/**
|
|
* An array of expressions representing the exports of the module.
|
|
*/
|
|
exports: R3Reference[];
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Whether to generate closure wrappers for bootstrap, declarations, imports, and exports.
|
|
*/
|
|
containsForwardDecls: boolean;
|
|
|
|
/**
|
|
* The set of schemas that declare elements to be allowed in the NgModule.
|
|
*/
|
|
schemas: R3Reference[]|null;
|
|
|
|
/** Unique ID or expression representing the unique ID of an NgModule. */
|
|
id: o.Expression|null;
|
|
}
|
|
|
|
/**
|
|
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
|
*/
|
|
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
|
const {
|
|
internalType,
|
|
type: moduleType,
|
|
bootstrap,
|
|
declarations,
|
|
imports,
|
|
exports,
|
|
schemas,
|
|
containsForwardDecls,
|
|
emitInline,
|
|
id
|
|
} = meta;
|
|
|
|
const additionalStatements: o.Statement[] = [];
|
|
const definitionMap = {type: internalType} as {
|
|
type: o.Expression,
|
|
bootstrap: o.Expression,
|
|
declarations: o.Expression,
|
|
imports: o.Expression,
|
|
exports: o.Expression,
|
|
schemas: o.LiteralArrayExpr,
|
|
id: o.Expression
|
|
};
|
|
|
|
// Only generate the keys in the metadata if the arrays have values.
|
|
if (bootstrap.length) {
|
|
definitionMap.bootstrap = refsToArray(bootstrap, containsForwardDecls);
|
|
}
|
|
|
|
// If requested to emit scope information inline, pass the declarations, imports and exports to
|
|
// the `ɵɵdefineNgModule` call. The JIT compilation uses this.
|
|
if (emitInline) {
|
|
if (declarations.length) {
|
|
definitionMap.declarations = refsToArray(declarations, containsForwardDecls);
|
|
}
|
|
|
|
if (imports.length) {
|
|
definitionMap.imports = refsToArray(imports, containsForwardDecls);
|
|
}
|
|
|
|
if (exports.length) {
|
|
definitionMap.exports = refsToArray(exports, containsForwardDecls);
|
|
}
|
|
}
|
|
|
|
// If not emitting inline, the scope information is not passed into `ɵɵdefineNgModule` as it would
|
|
// prevent tree-shaking of the declarations, imports and exports references.
|
|
else {
|
|
const setNgModuleScopeCall = generateSetNgModuleScopeCall(meta);
|
|
if (setNgModuleScopeCall !== null) {
|
|
additionalStatements.push(setNgModuleScopeCall);
|
|
}
|
|
}
|
|
|
|
if (schemas && schemas.length) {
|
|
definitionMap.schemas = o.literalArr(schemas.map(ref => ref.value));
|
|
}
|
|
|
|
if (id) {
|
|
definitionMap.id = id;
|
|
}
|
|
|
|
const expression =
|
|
o.importExpr(R3.defineNgModule).callFn([mapToMapExpression(definitionMap)], undefined, true);
|
|
const type = new o.ExpressionType(o.importExpr(R3.NgModuleDefWithMeta, [
|
|
new o.ExpressionType(moduleType.type), tupleTypeOf(declarations), tupleTypeOf(imports),
|
|
tupleTypeOf(exports)
|
|
]));
|
|
|
|
|
|
return {expression, type, additionalStatements};
|
|
}
|
|
|
|
/**
|
|
* Generates a function call to `ɵɵsetNgModuleScope` with all necessary information so that the
|
|
* transitive module scope can be computed during runtime in JIT mode. This call is marked pure
|
|
* such that the references to declarations, imports and exports may be elided causing these
|
|
* symbols to become tree-shakeable.
|
|
*/
|
|
function generateSetNgModuleScopeCall(meta: R3NgModuleMetadata): o.Statement|null {
|
|
const {adjacentType: moduleType, declarations, imports, exports, containsForwardDecls} = meta;
|
|
|
|
const scopeMap = {} as {
|
|
declarations: o.Expression,
|
|
imports: o.Expression,
|
|
exports: o.Expression,
|
|
};
|
|
|
|
if (declarations.length) {
|
|
scopeMap.declarations = refsToArray(declarations, containsForwardDecls);
|
|
}
|
|
|
|
if (imports.length) {
|
|
scopeMap.imports = refsToArray(imports, containsForwardDecls);
|
|
}
|
|
|
|
if (exports.length) {
|
|
scopeMap.exports = refsToArray(exports, containsForwardDecls);
|
|
}
|
|
|
|
if (Object.keys(scopeMap).length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// setNgModuleScope(...)
|
|
const fnCall = new o.InvokeFunctionExpr(
|
|
/* fn */ o.importExpr(R3.setNgModuleScope),
|
|
/* args */[moduleType, mapToMapExpression(scopeMap)]);
|
|
|
|
// (ngJitMode guard) && setNgModuleScope(...)
|
|
const guardedCall = jitOnlyGuardedExpression(fnCall);
|
|
|
|
// function() { (ngJitMode guard) && setNgModuleScope(...); }
|
|
const iife = new o.FunctionExpr(
|
|
/* params */[],
|
|
/* statements */[guardedCall.toStmt()]);
|
|
|
|
// (function() { (ngJitMode guard) && setNgModuleScope(...); })()
|
|
const iifeCall = new o.InvokeFunctionExpr(
|
|
/* fn */ iife,
|
|
/* args */[]);
|
|
|
|
return iifeCall.toStmt();
|
|
}
|
|
|
|
export interface R3InjectorDef {
|
|
expression: o.Expression;
|
|
type: o.Type;
|
|
}
|
|
|
|
export interface R3InjectorMetadata {
|
|
name: string;
|
|
type: R3Reference;
|
|
internalType: o.Expression;
|
|
providers: o.Expression|null;
|
|
imports: o.Expression[];
|
|
}
|
|
|
|
export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef {
|
|
const definitionMap: Record<string, o.Expression> = {};
|
|
|
|
if (meta.providers !== null) {
|
|
definitionMap.providers = meta.providers;
|
|
}
|
|
|
|
if (meta.imports.length > 0) {
|
|
definitionMap.imports = o.literalArr(meta.imports);
|
|
}
|
|
|
|
const expression =
|
|
o.importExpr(R3.defineInjector).callFn([mapToMapExpression(definitionMap)], undefined, true);
|
|
const type =
|
|
new o.ExpressionType(o.importExpr(R3.InjectorDef, [new o.ExpressionType(meta.type.type)]));
|
|
return {expression, type};
|
|
}
|
|
|
|
function tupleTypeOf(exp: R3Reference[]): o.Type {
|
|
const types = exp.map(ref => o.typeofExpr(ref.type));
|
|
return exp.length > 0 ? o.expressionType(o.literalArr(types)) : o.NONE_TYPE;
|
|
}
|
|
|
|
function refsToArray(refs: R3Reference[], shouldForwardDeclare: boolean): o.Expression {
|
|
const values = o.literalArr(refs.map(ref => ref.value));
|
|
return shouldForwardDeclare ? o.fn([], [new o.ReturnStatement(values)]) : values;
|
|
}
|