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
@ -15,6 +15,7 @@ import {ViewEncapsulation} from '../metadata';
|
||||
import {ComponentFactory as ComponentFactoryR3} from '../render3/component_ref';
|
||||
import {getComponentDef, getNgModuleDef} from '../render3/definition';
|
||||
import {NgModuleFactory as NgModuleFactoryR3} from '../render3/ng_module_ref';
|
||||
import {maybeUnwrapFn} from '../render3/util/misc_utils';
|
||||
|
||||
import {ComponentFactory} from './component_factory';
|
||||
import {NgModuleFactory} from './ng_module_factory';
|
||||
@ -60,11 +61,13 @@ export const Compiler_compileModuleAndAllComponentsSync__POST_R3__: <T>(moduleTy
|
||||
ModuleWithComponentFactories<T> {
|
||||
const ngModuleFactory = Compiler_compileModuleSync__POST_R3__(moduleType);
|
||||
const moduleDef = getNgModuleDef(moduleType) !;
|
||||
const componentFactories = moduleDef.declarations.reduce((factories, declaration) => {
|
||||
const componentDef = getComponentDef(declaration);
|
||||
componentDef && factories.push(new ComponentFactoryR3(componentDef));
|
||||
return factories;
|
||||
}, [] as ComponentFactory<any>[]);
|
||||
const componentFactories =
|
||||
maybeUnwrapFn(moduleDef.declarations)
|
||||
.reduce((factories: ComponentFactory<any>[], declaration: Type<any>) => {
|
||||
const componentDef = getComponentDef(declaration);
|
||||
componentDef && factories.push(new ComponentFactoryR3(componentDef));
|
||||
return factories;
|
||||
}, [] as ComponentFactory<any>[]);
|
||||
return new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
|
||||
};
|
||||
const Compiler_compileModuleAndAllComponentsSync =
|
||||
|
@ -49,19 +49,19 @@ export interface NgModuleDef<T> {
|
||||
type: T;
|
||||
|
||||
/** List of components to bootstrap. */
|
||||
bootstrap: Type<any>[];
|
||||
bootstrap: Type<any>[]|(() => Type<any>[]);
|
||||
|
||||
/** List of components, directives, and pipes declared by this module. */
|
||||
declarations: Type<any>[];
|
||||
declarations: Type<any>[]|(() => Type<any>[]);
|
||||
|
||||
/** List of modules or `ModuleWithProviders` imported by this module. */
|
||||
imports: Type<any>[];
|
||||
imports: Type<any>[]|(() => Type<any>[]);
|
||||
|
||||
/**
|
||||
* List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this
|
||||
* module.
|
||||
*/
|
||||
exports: Type<any>[];
|
||||
exports: Type<any>[]|(() => Type<any>[]);
|
||||
|
||||
/**
|
||||
* Cached value of computed `transitiveCompileScopes` for this module.
|
||||
|
@ -19,7 +19,7 @@ import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../d
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from '../fields';
|
||||
import {ComponentDef} from '../interfaces/definition';
|
||||
import {NgModuleType} from '../ng_module_ref';
|
||||
import {renderStringify} from '../util/misc_utils';
|
||||
import {maybeUnwrapFn, renderStringify} from '../util/misc_utils';
|
||||
|
||||
import {angularCoreEnv} from './environment';
|
||||
|
||||
@ -156,14 +156,17 @@ function verifySemanticsOfNgModuleDef(moduleType: NgModuleType): void {
|
||||
moduleType = resolveForwardRef(moduleType);
|
||||
const ngModuleDef = getNgModuleDef(moduleType, true);
|
||||
const errors: string[] = [];
|
||||
ngModuleDef.declarations.forEach(verifyDeclarationsHaveDefinitions);
|
||||
const declarations = maybeUnwrapFn(ngModuleDef.declarations);
|
||||
const imports = maybeUnwrapFn(ngModuleDef.imports);
|
||||
const exports = maybeUnwrapFn(ngModuleDef.exports);
|
||||
declarations.forEach(verifyDeclarationsHaveDefinitions);
|
||||
const combinedDeclarations: Type<any>[] = [
|
||||
...ngModuleDef.declarations.map(resolveForwardRef), //
|
||||
...flatten(ngModuleDef.imports.map(computeCombinedExports), resolveForwardRef),
|
||||
...declarations.map(resolveForwardRef), //
|
||||
...flatten(imports.map(computeCombinedExports), resolveForwardRef),
|
||||
];
|
||||
ngModuleDef.exports.forEach(verifyExportsAreDeclaredOrReExported);
|
||||
ngModuleDef.declarations.forEach(verifyDeclarationIsUnique);
|
||||
ngModuleDef.declarations.forEach(verifyComponentEntryComponentsIsPartOfNgModule);
|
||||
exports.forEach(verifyExportsAreDeclaredOrReExported);
|
||||
declarations.forEach(verifyDeclarationIsUnique);
|
||||
declarations.forEach(verifyComponentEntryComponentsIsPartOfNgModule);
|
||||
|
||||
const ngModule = getAnnotation<NgModule>(moduleType, 'NgModule');
|
||||
if (ngModule) {
|
||||
@ -297,15 +300,14 @@ export function resetCompiledComponents(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the combined declarations of explicit declarations, as well as declarations inherited
|
||||
* by
|
||||
* Computes the combined declarations of explicit declarations, as well as declarations inherited by
|
||||
* traversing the exports of imported modules.
|
||||
* @param type
|
||||
*/
|
||||
function computeCombinedExports(type: Type<any>): Type<any>[] {
|
||||
type = resolveForwardRef(type);
|
||||
const ngModuleDef = getNgModuleDef(type, true);
|
||||
return [...flatten(ngModuleDef.exports.map((type) => {
|
||||
return [...flatten(maybeUnwrapFn(ngModuleDef.exports).map((type) => {
|
||||
const ngModuleDef = getNgModuleDef(type);
|
||||
if (ngModuleDef) {
|
||||
verifySemanticsOfNgModuleDef(type as any as NgModuleType);
|
||||
@ -388,7 +390,7 @@ export function transitiveScopesFor<T>(
|
||||
},
|
||||
};
|
||||
|
||||
def.declarations.forEach(declared => {
|
||||
maybeUnwrapFn(def.declarations).forEach(declared => {
|
||||
const declaredWithDefs = declared as Type<any>& { ngPipeDef?: any; };
|
||||
|
||||
if (getPipeDef(declaredWithDefs)) {
|
||||
@ -401,7 +403,7 @@ export function transitiveScopesFor<T>(
|
||||
}
|
||||
});
|
||||
|
||||
def.imports.forEach(<I>(imported: Type<I>) => {
|
||||
maybeUnwrapFn(def.imports).forEach(<I>(imported: Type<I>) => {
|
||||
const importedType = imported as Type<I>& {
|
||||
// If imported is an @NgModule:
|
||||
ngModuleDef?: NgModuleDef<I>;
|
||||
@ -422,7 +424,7 @@ export function transitiveScopesFor<T>(
|
||||
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
|
||||
});
|
||||
|
||||
def.exports.forEach(<E>(exported: Type<E>) => {
|
||||
maybeUnwrapFn(def.exports).forEach(<E>(exported: Type<E>) => {
|
||||
const exportedType = exported as Type<E>& {
|
||||
// Components, Directives, NgModules, and Pipes can all be exported.
|
||||
ngComponentDef?: any;
|
||||
|
@ -18,6 +18,7 @@ import {assertDefined} from '../util/assert';
|
||||
import {stringify} from '../util/stringify';
|
||||
import {ComponentFactoryResolver} from './component_ref';
|
||||
import {getNgModuleDef} from './definition';
|
||||
import {maybeUnwrapFn} from './util/misc_utils';
|
||||
|
||||
export interface NgModuleType<T = any> extends Type<T> { ngModuleDef: NgModuleDef<T>; }
|
||||
|
||||
@ -43,7 +44,7 @@ export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements Interna
|
||||
ngModuleDef,
|
||||
`NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`);
|
||||
|
||||
this._bootstrapComponents = ngModuleDef !.bootstrap;
|
||||
this._bootstrapComponents = maybeUnwrapFn(ngModuleDef !.bootstrap);
|
||||
const additionalProviders: StaticProvider[] = [
|
||||
{
|
||||
provide: viewEngine_NgModuleRef,
|
||||
|
@ -76,3 +76,14 @@ export const INTERPOLATION_DELIMITER = `<60>`;
|
||||
export function isPropMetadataString(str: string): boolean {
|
||||
return str.indexOf(INTERPOLATION_DELIMITER) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a value which might be behind a closure (for forward declaration reasons).
|
||||
*/
|
||||
export function maybeUnwrapFn<T>(value: T | (() => T)): T {
|
||||
if (value instanceof Function) {
|
||||
return value();
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user