fix(ivy): properly compile NgModules with forward referenced types (#29198)
Previously, ngtsc would resolve forward references while evaluating the bootstrap, declaration, imports, and exports fields of NgModule types. However, when generating the resulting ngModuleDef, the forward nature of these references was not taken into consideration, and so the generated JS code would incorrectly reference types not yet declared. This commit fixes this issue by introducing function closures in the NgModuleDef type, similarly to how NgComponentDef uses them for forward declarations of its directives and pipes arrays. ngtsc will then generate closures when required, and the runtime will unwrap them if present. PR Close #29198
This commit is contained in:

committed by
Kara Erickson

parent
1625d86178
commit
73da2792c9
@ -88,6 +88,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||
imports: facade.imports.map(wrapReference),
|
||||
exports: facade.exports.map(wrapReference),
|
||||
emitInline: true,
|
||||
containsForwardDecls: false,
|
||||
schemas: facade.schemas ? facade.schemas.map(wrapReference) : null,
|
||||
};
|
||||
const res = compileNgModule(meta);
|
||||
|
@ -58,6 +58,11 @@ export interface R3NgModuleMetadata {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -68,33 +73,42 @@ export interface R3NgModuleMetadata {
|
||||
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
||||
*/
|
||||
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
||||
const {type: moduleType, bootstrap, declarations, imports, exports, schemas} = meta;
|
||||
const {
|
||||
type: moduleType,
|
||||
bootstrap,
|
||||
declarations,
|
||||
imports,
|
||||
exports,
|
||||
schemas,
|
||||
containsForwardDecls
|
||||
} = meta;
|
||||
|
||||
const definitionMap = {
|
||||
type: moduleType
|
||||
} as{
|
||||
type: o.Expression,
|
||||
bootstrap: o.LiteralArrayExpr,
|
||||
declarations: o.LiteralArrayExpr,
|
||||
imports: o.LiteralArrayExpr,
|
||||
exports: o.LiteralArrayExpr,
|
||||
bootstrap: o.Expression,
|
||||
declarations: o.Expression,
|
||||
imports: o.Expression,
|
||||
exports: o.Expression,
|
||||
schemas: o.LiteralArrayExpr
|
||||
};
|
||||
|
||||
// Only generate the keys in the metadata if the arrays have values.
|
||||
if (bootstrap.length) {
|
||||
definitionMap.bootstrap = o.literalArr(bootstrap.map(ref => ref.value));
|
||||
definitionMap.bootstrap = refsToArray(bootstrap, containsForwardDecls);
|
||||
}
|
||||
|
||||
if (declarations.length) {
|
||||
definitionMap.declarations = o.literalArr(declarations.map(ref => ref.value));
|
||||
definitionMap.declarations = refsToArray(declarations, containsForwardDecls);
|
||||
}
|
||||
|
||||
if (imports.length) {
|
||||
definitionMap.imports = o.literalArr(imports.map(ref => ref.value));
|
||||
definitionMap.imports = refsToArray(imports, containsForwardDecls);
|
||||
}
|
||||
|
||||
if (exports.length) {
|
||||
definitionMap.exports = o.literalArr(exports.map(ref => ref.value));
|
||||
definitionMap.exports = refsToArray(exports, containsForwardDecls);
|
||||
}
|
||||
|
||||
if (schemas && schemas.length) {
|
||||
@ -182,3 +196,8 @@ 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;
|
||||
}
|
Reference in New Issue
Block a user