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:
parent
1625d86178
commit
73da2792c9
|
@ -23,7 +23,7 @@ import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
|
||||||
import {ResourceLoader} from './api';
|
import {ResourceLoader} from './api';
|
||||||
import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive';
|
import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive';
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {findAngularDecorator, isAngularCoreReference, unwrapExpression} from './util';
|
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, unwrapExpression} from './util';
|
||||||
|
|
||||||
const EMPTY_MAP = new Map<string, Expression>();
|
const EMPTY_MAP = new Map<string, Expression>();
|
||||||
const EMPTY_ARRAY: any[] = [];
|
const EMPTY_ARRAY: any[] = [];
|
||||||
|
@ -616,20 +616,6 @@ function getTemplateRange(templateExpr: ts.Expression) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExpressionForwardReference(
|
|
||||||
expr: Expression, context: ts.Node, contextSource: ts.SourceFile): boolean {
|
|
||||||
if (isWrappedTsNodeExpr(expr)) {
|
|
||||||
const node = ts.getOriginalNode(expr.node);
|
|
||||||
return node.getSourceFile() === contextSource && context.pos < node.pos;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isWrappedTsNodeExpr(expr: Expression): expr is WrappedNodeExpr<ts.Node> {
|
|
||||||
return expr instanceof WrappedNodeExpr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sourceMapUrl(resourceUrl: string): string {
|
function sourceMapUrl(resourceUrl: string): string {
|
||||||
if (!tsSourceMapBug29300Fixed()) {
|
if (!tsSourceMapBug29300Fixed()) {
|
||||||
// By removing the template URL we are telling the translator not to try to
|
// By removing the template URL we are telling the translator not to try to
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {getSourceFile} from '../../util/src/typescript';
|
||||||
|
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {ReferencesRegistry} from './references_registry';
|
import {ReferencesRegistry} from './references_registry';
|
||||||
import {combineResolvers, findAngularDecorator, forwardRefResolver, getValidConstructorDependencies, toR3Reference, unwrapExpression} from './util';
|
import {combineResolvers, findAngularDecorator, forwardRefResolver, getValidConstructorDependencies, isExpressionForwardReference, toR3Reference, unwrapExpression} from './util';
|
||||||
|
|
||||||
export interface NgModuleAnalysis {
|
export interface NgModuleAnalysis {
|
||||||
ngModuleDef: R3NgModuleMetadata;
|
ngModuleDef: R3NgModuleMetadata;
|
||||||
|
@ -90,38 +90,39 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Extract the module declarations, imports, and exports.
|
// Extract the module declarations, imports, and exports.
|
||||||
let declarations: Reference<ts.Declaration>[] = [];
|
let declarationRefs: Reference<ts.Declaration>[] = [];
|
||||||
if (ngModule.has('declarations')) {
|
if (ngModule.has('declarations')) {
|
||||||
const expr = ngModule.get('declarations') !;
|
const expr = ngModule.get('declarations') !;
|
||||||
const declarationMeta = this.evaluator.evaluate(expr, forwardRefResolver);
|
const declarationMeta = this.evaluator.evaluate(expr, forwardRefResolver);
|
||||||
declarations = this.resolveTypeList(expr, declarationMeta, name, 'declarations');
|
declarationRefs = this.resolveTypeList(expr, declarationMeta, name, 'declarations');
|
||||||
}
|
}
|
||||||
let imports: Reference<ts.Declaration>[] = [];
|
let importRefs: Reference<ts.Declaration>[] = [];
|
||||||
let rawImports: ts.Expression|null = null;
|
let rawImports: ts.Expression|null = null;
|
||||||
if (ngModule.has('imports')) {
|
if (ngModule.has('imports')) {
|
||||||
rawImports = ngModule.get('imports') !;
|
rawImports = ngModule.get('imports') !;
|
||||||
const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers);
|
const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers);
|
||||||
imports = this.resolveTypeList(rawImports, importsMeta, name, 'imports');
|
importRefs = this.resolveTypeList(rawImports, importsMeta, name, 'imports');
|
||||||
}
|
}
|
||||||
let exports: Reference<ts.Declaration>[] = [];
|
let exportRefs: Reference<ts.Declaration>[] = [];
|
||||||
let rawExports: ts.Expression|null = null;
|
let rawExports: ts.Expression|null = null;
|
||||||
if (ngModule.has('exports')) {
|
if (ngModule.has('exports')) {
|
||||||
rawExports = ngModule.get('exports') !;
|
rawExports = ngModule.get('exports') !;
|
||||||
const exportsMeta = this.evaluator.evaluate(rawExports, moduleResolvers);
|
const exportsMeta = this.evaluator.evaluate(rawExports, moduleResolvers);
|
||||||
exports = this.resolveTypeList(rawExports, exportsMeta, name, 'exports');
|
exportRefs = this.resolveTypeList(rawExports, exportsMeta, name, 'exports');
|
||||||
this.referencesRegistry.add(node, ...exports);
|
this.referencesRegistry.add(node, ...exportRefs);
|
||||||
}
|
}
|
||||||
let bootstrap: Reference<ts.Declaration>[] = [];
|
let bootstrapRefs: Reference<ts.Declaration>[] = [];
|
||||||
if (ngModule.has('bootstrap')) {
|
if (ngModule.has('bootstrap')) {
|
||||||
const expr = ngModule.get('bootstrap') !;
|
const expr = ngModule.get('bootstrap') !;
|
||||||
const bootstrapMeta = this.evaluator.evaluate(expr, forwardRefResolver);
|
const bootstrapMeta = this.evaluator.evaluate(expr, forwardRefResolver);
|
||||||
bootstrap = this.resolveTypeList(expr, bootstrapMeta, name, 'bootstrap');
|
bootstrapRefs = this.resolveTypeList(expr, bootstrapMeta, name, 'bootstrap');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register this module's information with the LocalModuleScopeRegistry. This ensures that
|
// Register this module's information with the LocalModuleScopeRegistry. This ensures that
|
||||||
// during the compile() phase, the module's metadata is available for selector scope
|
// during the compile() phase, the module's metadata is available for selector scope
|
||||||
// computation.
|
// computation.
|
||||||
this.scopeRegistry.registerNgModule(node, {declarations, imports, exports});
|
this.scopeRegistry.registerNgModule(
|
||||||
|
node, {declarations: declarationRefs, imports: importRefs, exports: exportRefs});
|
||||||
|
|
||||||
const valueContext = node.getSourceFile();
|
const valueContext = node.getSourceFile();
|
||||||
|
|
||||||
|
@ -131,13 +132,26 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
typeContext = typeNode.getSourceFile();
|
typeContext = typeNode.getSourceFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bootstrap =
|
||||||
|
bootstrapRefs.map(bootstrap => this._toR3Reference(bootstrap, valueContext, typeContext));
|
||||||
|
const declarations =
|
||||||
|
declarationRefs.map(decl => this._toR3Reference(decl, valueContext, typeContext));
|
||||||
|
const imports = importRefs.map(imp => this._toR3Reference(imp, valueContext, typeContext));
|
||||||
|
const exports = exportRefs.map(exp => this._toR3Reference(exp, valueContext, typeContext));
|
||||||
|
|
||||||
|
const isForwardReference = (ref: R3Reference) =>
|
||||||
|
isExpressionForwardReference(ref.value, node.name !, valueContext);
|
||||||
|
const containsForwardDecls = bootstrap.some(isForwardReference) ||
|
||||||
|
declarations.some(isForwardReference) || imports.some(isForwardReference) ||
|
||||||
|
exports.some(isForwardReference);
|
||||||
|
|
||||||
const ngModuleDef: R3NgModuleMetadata = {
|
const ngModuleDef: R3NgModuleMetadata = {
|
||||||
type: new WrappedNodeExpr(node.name !),
|
type: new WrappedNodeExpr(node.name !),
|
||||||
bootstrap:
|
bootstrap,
|
||||||
bootstrap.map(bootstrap => this._toR3Reference(bootstrap, valueContext, typeContext)),
|
declarations,
|
||||||
declarations: declarations.map(decl => this._toR3Reference(decl, valueContext, typeContext)),
|
exports,
|
||||||
exports: exports.map(exp => this._toR3Reference(exp, valueContext, typeContext)),
|
imports,
|
||||||
imports: imports.map(imp => this._toR3Reference(imp, valueContext, typeContext)),
|
containsForwardDecls,
|
||||||
emitInline: false,
|
emitInline: false,
|
||||||
// TODO: to be implemented as a part of FW-1004.
|
// TODO: to be implemented as a part of FW-1004.
|
||||||
schemas: [],
|
schemas: [],
|
||||||
|
@ -173,7 +187,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
analysis: {
|
analysis: {
|
||||||
ngModuleDef,
|
ngModuleDef,
|
||||||
ngInjectorDef,
|
ngInjectorDef,
|
||||||
declarations,
|
declarations: declarationRefs,
|
||||||
metadataStmt: generateSetClassMetadataCall(
|
metadataStmt: generateSetClassMetadataCall(
|
||||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||||
},
|
},
|
||||||
|
|
|
@ -278,3 +278,17 @@ export function combineResolvers(resolvers: ForeignFunctionResolver[]): ForeignF
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isExpressionForwardReference(
|
||||||
|
expr: Expression, context: ts.Node, contextSource: ts.SourceFile): boolean {
|
||||||
|
if (isWrappedTsNodeExpr(expr)) {
|
||||||
|
const node = ts.getOriginalNode(expr.node);
|
||||||
|
return node.getSourceFile() === contextSource && context.pos < node.pos;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWrappedTsNodeExpr(expr: Expression): expr is WrappedNodeExpr<ts.Node> {
|
||||||
|
return expr instanceof WrappedNodeExpr;
|
||||||
|
}
|
||||||
|
|
|
@ -643,6 +643,86 @@ describe('ngtsc behavioral tests', () => {
|
||||||
expect(dtsContents).toContain('as i1 from "foo";');
|
expect(dtsContents).toContain('as i1 from "foo";');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should compile NgModules with references to forward declared bootstrap components', () => {
|
||||||
|
env.tsconfig();
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, forwardRef, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
bootstrap: [forwardRef(() => Foo)],
|
||||||
|
})
|
||||||
|
export class FooModule {}
|
||||||
|
|
||||||
|
@Component({selector: 'foo', template: 'foo'})
|
||||||
|
export class Foo {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = env.getContents('test.js');
|
||||||
|
expect(jsContents).toContain('bootstrap: function () { return [Foo]; }');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compile NgModules with references to forward declared directives', () => {
|
||||||
|
env.tsconfig();
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Directive, forwardRef, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [forwardRef(() => Foo)],
|
||||||
|
})
|
||||||
|
export class FooModule {}
|
||||||
|
|
||||||
|
@Directive({selector: 'foo'})
|
||||||
|
export class Foo {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = env.getContents('test.js');
|
||||||
|
expect(jsContents).toContain('declarations: function () { return [Foo]; }');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compile NgModules with references to forward declared imports', () => {
|
||||||
|
env.tsconfig();
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {forwardRef, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [forwardRef(() => BarModule)],
|
||||||
|
})
|
||||||
|
export class FooModule {}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class BarModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = env.getContents('test.js');
|
||||||
|
expect(jsContents).toContain('imports: function () { return [BarModule]; }');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compile NgModules with references to forward declared exports', () => {
|
||||||
|
env.tsconfig();
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {forwardRef, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [forwardRef(() => BarModule)],
|
||||||
|
})
|
||||||
|
export class FooModule {}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class BarModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = env.getContents('test.js');
|
||||||
|
expect(jsContents).toContain('exports: function () { return [BarModule]; }');
|
||||||
|
});
|
||||||
|
|
||||||
it('should compile Pipes without errors', () => {
|
it('should compile Pipes without errors', () => {
|
||||||
env.tsconfig();
|
env.tsconfig();
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
|
|
|
@ -88,6 +88,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
imports: facade.imports.map(wrapReference),
|
imports: facade.imports.map(wrapReference),
|
||||||
exports: facade.exports.map(wrapReference),
|
exports: facade.exports.map(wrapReference),
|
||||||
emitInline: true,
|
emitInline: true,
|
||||||
|
containsForwardDecls: false,
|
||||||
schemas: facade.schemas ? facade.schemas.map(wrapReference) : null,
|
schemas: facade.schemas ? facade.schemas.map(wrapReference) : null,
|
||||||
};
|
};
|
||||||
const res = compileNgModule(meta);
|
const res = compileNgModule(meta);
|
||||||
|
|
|
@ -58,6 +58,11 @@ export interface R3NgModuleMetadata {
|
||||||
*/
|
*/
|
||||||
emitInline: boolean;
|
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.
|
* 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`.
|
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
||||||
*/
|
*/
|
||||||
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
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 = {
|
const definitionMap = {
|
||||||
type: moduleType
|
type: moduleType
|
||||||
} as{
|
} as{
|
||||||
type: o.Expression,
|
type: o.Expression,
|
||||||
bootstrap: o.LiteralArrayExpr,
|
bootstrap: o.Expression,
|
||||||
declarations: o.LiteralArrayExpr,
|
declarations: o.Expression,
|
||||||
imports: o.LiteralArrayExpr,
|
imports: o.Expression,
|
||||||
exports: o.LiteralArrayExpr,
|
exports: o.Expression,
|
||||||
schemas: o.LiteralArrayExpr
|
schemas: o.LiteralArrayExpr
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only generate the keys in the metadata if the arrays have values.
|
// Only generate the keys in the metadata if the arrays have values.
|
||||||
if (bootstrap.length) {
|
if (bootstrap.length) {
|
||||||
definitionMap.bootstrap = o.literalArr(bootstrap.map(ref => ref.value));
|
definitionMap.bootstrap = refsToArray(bootstrap, containsForwardDecls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declarations.length) {
|
if (declarations.length) {
|
||||||
definitionMap.declarations = o.literalArr(declarations.map(ref => ref.value));
|
definitionMap.declarations = refsToArray(declarations, containsForwardDecls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imports.length) {
|
if (imports.length) {
|
||||||
definitionMap.imports = o.literalArr(imports.map(ref => ref.value));
|
definitionMap.imports = refsToArray(imports, containsForwardDecls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exports.length) {
|
if (exports.length) {
|
||||||
definitionMap.exports = o.literalArr(exports.map(ref => ref.value));
|
definitionMap.exports = refsToArray(exports, containsForwardDecls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schemas && schemas.length) {
|
if (schemas && schemas.length) {
|
||||||
|
@ -182,3 +196,8 @@ function tupleTypeOf(exp: R3Reference[]): o.Type {
|
||||||
const types = exp.map(ref => o.typeofExpr(ref.type));
|
const types = exp.map(ref => o.typeofExpr(ref.type));
|
||||||
return exp.length > 0 ? o.expressionType(o.literalArr(types)) : o.NONE_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;
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import {ViewEncapsulation} from '../metadata';
|
||||||
import {ComponentFactory as ComponentFactoryR3} from '../render3/component_ref';
|
import {ComponentFactory as ComponentFactoryR3} from '../render3/component_ref';
|
||||||
import {getComponentDef, getNgModuleDef} from '../render3/definition';
|
import {getComponentDef, getNgModuleDef} from '../render3/definition';
|
||||||
import {NgModuleFactory as NgModuleFactoryR3} from '../render3/ng_module_ref';
|
import {NgModuleFactory as NgModuleFactoryR3} from '../render3/ng_module_ref';
|
||||||
|
import {maybeUnwrapFn} from '../render3/util/misc_utils';
|
||||||
|
|
||||||
import {ComponentFactory} from './component_factory';
|
import {ComponentFactory} from './component_factory';
|
||||||
import {NgModuleFactory} from './ng_module_factory';
|
import {NgModuleFactory} from './ng_module_factory';
|
||||||
|
@ -60,11 +61,13 @@ export const Compiler_compileModuleAndAllComponentsSync__POST_R3__: <T>(moduleTy
|
||||||
ModuleWithComponentFactories<T> {
|
ModuleWithComponentFactories<T> {
|
||||||
const ngModuleFactory = Compiler_compileModuleSync__POST_R3__(moduleType);
|
const ngModuleFactory = Compiler_compileModuleSync__POST_R3__(moduleType);
|
||||||
const moduleDef = getNgModuleDef(moduleType) !;
|
const moduleDef = getNgModuleDef(moduleType) !;
|
||||||
const componentFactories = moduleDef.declarations.reduce((factories, declaration) => {
|
const componentFactories =
|
||||||
const componentDef = getComponentDef(declaration);
|
maybeUnwrapFn(moduleDef.declarations)
|
||||||
componentDef && factories.push(new ComponentFactoryR3(componentDef));
|
.reduce((factories: ComponentFactory<any>[], declaration: Type<any>) => {
|
||||||
return factories;
|
const componentDef = getComponentDef(declaration);
|
||||||
}, [] as ComponentFactory<any>[]);
|
componentDef && factories.push(new ComponentFactoryR3(componentDef));
|
||||||
|
return factories;
|
||||||
|
}, [] as ComponentFactory<any>[]);
|
||||||
return new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
|
return new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
|
||||||
};
|
};
|
||||||
const Compiler_compileModuleAndAllComponentsSync =
|
const Compiler_compileModuleAndAllComponentsSync =
|
||||||
|
|
|
@ -49,19 +49,19 @@ export interface NgModuleDef<T> {
|
||||||
type: T;
|
type: T;
|
||||||
|
|
||||||
/** List of components to bootstrap. */
|
/** List of components to bootstrap. */
|
||||||
bootstrap: Type<any>[];
|
bootstrap: Type<any>[]|(() => Type<any>[]);
|
||||||
|
|
||||||
/** List of components, directives, and pipes declared by this module. */
|
/** 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. */
|
/** 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
|
* List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this
|
||||||
* module.
|
* module.
|
||||||
*/
|
*/
|
||||||
exports: Type<any>[];
|
exports: Type<any>[]|(() => Type<any>[]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached value of computed `transitiveCompileScopes` for this module.
|
* 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 {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from '../fields';
|
||||||
import {ComponentDef} from '../interfaces/definition';
|
import {ComponentDef} from '../interfaces/definition';
|
||||||
import {NgModuleType} from '../ng_module_ref';
|
import {NgModuleType} from '../ng_module_ref';
|
||||||
import {renderStringify} from '../util/misc_utils';
|
import {maybeUnwrapFn, renderStringify} from '../util/misc_utils';
|
||||||
|
|
||||||
import {angularCoreEnv} from './environment';
|
import {angularCoreEnv} from './environment';
|
||||||
|
|
||||||
|
@ -156,14 +156,17 @@ function verifySemanticsOfNgModuleDef(moduleType: NgModuleType): void {
|
||||||
moduleType = resolveForwardRef(moduleType);
|
moduleType = resolveForwardRef(moduleType);
|
||||||
const ngModuleDef = getNgModuleDef(moduleType, true);
|
const ngModuleDef = getNgModuleDef(moduleType, true);
|
||||||
const errors: string[] = [];
|
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>[] = [
|
const combinedDeclarations: Type<any>[] = [
|
||||||
...ngModuleDef.declarations.map(resolveForwardRef), //
|
...declarations.map(resolveForwardRef), //
|
||||||
...flatten(ngModuleDef.imports.map(computeCombinedExports), resolveForwardRef),
|
...flatten(imports.map(computeCombinedExports), resolveForwardRef),
|
||||||
];
|
];
|
||||||
ngModuleDef.exports.forEach(verifyExportsAreDeclaredOrReExported);
|
exports.forEach(verifyExportsAreDeclaredOrReExported);
|
||||||
ngModuleDef.declarations.forEach(verifyDeclarationIsUnique);
|
declarations.forEach(verifyDeclarationIsUnique);
|
||||||
ngModuleDef.declarations.forEach(verifyComponentEntryComponentsIsPartOfNgModule);
|
declarations.forEach(verifyComponentEntryComponentsIsPartOfNgModule);
|
||||||
|
|
||||||
const ngModule = getAnnotation<NgModule>(moduleType, 'NgModule');
|
const ngModule = getAnnotation<NgModule>(moduleType, 'NgModule');
|
||||||
if (ngModule) {
|
if (ngModule) {
|
||||||
|
@ -297,15 +300,14 @@ export function resetCompiledComponents(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the combined declarations of explicit declarations, as well as declarations inherited
|
* Computes the combined declarations of explicit declarations, as well as declarations inherited by
|
||||||
* by
|
|
||||||
* traversing the exports of imported modules.
|
* traversing the exports of imported modules.
|
||||||
* @param type
|
* @param type
|
||||||
*/
|
*/
|
||||||
function computeCombinedExports(type: Type<any>): Type<any>[] {
|
function computeCombinedExports(type: Type<any>): Type<any>[] {
|
||||||
type = resolveForwardRef(type);
|
type = resolveForwardRef(type);
|
||||||
const ngModuleDef = getNgModuleDef(type, true);
|
const ngModuleDef = getNgModuleDef(type, true);
|
||||||
return [...flatten(ngModuleDef.exports.map((type) => {
|
return [...flatten(maybeUnwrapFn(ngModuleDef.exports).map((type) => {
|
||||||
const ngModuleDef = getNgModuleDef(type);
|
const ngModuleDef = getNgModuleDef(type);
|
||||||
if (ngModuleDef) {
|
if (ngModuleDef) {
|
||||||
verifySemanticsOfNgModuleDef(type as any as NgModuleType);
|
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; };
|
const declaredWithDefs = declared as Type<any>& { ngPipeDef?: any; };
|
||||||
|
|
||||||
if (getPipeDef(declaredWithDefs)) {
|
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>& {
|
const importedType = imported as Type<I>& {
|
||||||
// If imported is an @NgModule:
|
// If imported is an @NgModule:
|
||||||
ngModuleDef?: NgModuleDef<I>;
|
ngModuleDef?: NgModuleDef<I>;
|
||||||
|
@ -422,7 +424,7 @@ export function transitiveScopesFor<T>(
|
||||||
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
|
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>& {
|
const exportedType = exported as Type<E>& {
|
||||||
// Components, Directives, NgModules, and Pipes can all be exported.
|
// Components, Directives, NgModules, and Pipes can all be exported.
|
||||||
ngComponentDef?: any;
|
ngComponentDef?: any;
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {assertDefined} from '../util/assert';
|
||||||
import {stringify} from '../util/stringify';
|
import {stringify} from '../util/stringify';
|
||||||
import {ComponentFactoryResolver} from './component_ref';
|
import {ComponentFactoryResolver} from './component_ref';
|
||||||
import {getNgModuleDef} from './definition';
|
import {getNgModuleDef} from './definition';
|
||||||
|
import {maybeUnwrapFn} from './util/misc_utils';
|
||||||
|
|
||||||
export interface NgModuleType<T = any> extends Type<T> { ngModuleDef: NgModuleDef<T>; }
|
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,
|
ngModuleDef,
|
||||||
`NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`);
|
`NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`);
|
||||||
|
|
||||||
this._bootstrapComponents = ngModuleDef !.bootstrap;
|
this._bootstrapComponents = maybeUnwrapFn(ngModuleDef !.bootstrap);
|
||||||
const additionalProviders: StaticProvider[] = [
|
const additionalProviders: StaticProvider[] = [
|
||||||
{
|
{
|
||||||
provide: viewEngine_NgModuleRef,
|
provide: viewEngine_NgModuleRef,
|
||||||
|
|
|
@ -76,3 +76,14 @@ export const INTERPOLATION_DELIMITER = `<60>`;
|
||||||
export function isPropMetadataString(str: string): boolean {
|
export function isPropMetadataString(str: string): boolean {
|
||||||
return str.indexOf(INTERPOLATION_DELIMITER) >= 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -158,6 +158,9 @@ ivyEnabled && describe('render3 jit', () => {
|
||||||
|
|
||||||
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
|
const moduleDef: NgModuleDef<Module> = (Module as any).ngModuleDef;
|
||||||
expect(moduleDef).toBeDefined();
|
expect(moduleDef).toBeDefined();
|
||||||
|
if (!Array.isArray(moduleDef.declarations)) {
|
||||||
|
return fail('Expected an array');
|
||||||
|
}
|
||||||
expect(moduleDef.declarations.length).toBe(1);
|
expect(moduleDef.declarations.length).toBe(1);
|
||||||
expect(moduleDef.declarations[0]).toBe(Cmp);
|
expect(moduleDef.declarations[0]).toBe(Cmp);
|
||||||
});
|
});
|
||||||
|
|
|
@ -732,7 +732,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_getComponentFactories(moduleType: NgModuleType): ComponentFactory<any>[] {
|
_getComponentFactories(moduleType: NgModuleType): ComponentFactory<any>[] {
|
||||||
return moduleType.ngModuleDef.declarations.reduce((factories, declaration) => {
|
return maybeUnwrapFn(moduleType.ngModuleDef.declarations).reduce((factories, declaration) => {
|
||||||
const componentDef = (declaration as any).ngComponentDef;
|
const componentDef = (declaration as any).ngComponentDef;
|
||||||
componentDef && factories.push(new ComponentFactory(componentDef, this._moduleRef));
|
componentDef && factories.push(new ComponentFactory(componentDef, this._moduleRef));
|
||||||
return factories;
|
return factories;
|
||||||
|
@ -820,3 +820,14 @@ class R3TestCompiler implements Compiler {
|
||||||
class R3TestErrorHandler extends ErrorHandler {
|
class R3TestErrorHandler extends ErrorHandler {
|
||||||
handleError(error: any) { throw error; }
|
handleError(error: any) { throw error; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap a value which might be behind a closure (for forward declaration reasons).
|
||||||
|
*/
|
||||||
|
function maybeUnwrapFn<T>(value: T | (() => T)): T {
|
||||||
|
if (value instanceof Function) {
|
||||||
|
return value();
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue