fix(ivy): check semantics of NgModule for consistency (#27604)
`NgModule` requires that `Component`s/`Directive`s/`Pipe`s are listed in declarations, and that each `Component`s/`Directive`s/`Pipe` is declared in exactly one `NgModule`. This change adds runtime checks to ensure that these sementics are true at runtime. There will need to be seperate set of checks for the AoT path of the codebase to verify that same set of semantics hold. Due to current design there does not seem to be an easy way to share the two checks because JIT deal with references where as AoT deals with AST nodes. PR Close #27604
This commit is contained in:
@ -6,6 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModuleFactory as R3NgModuleFactory, NgModuleType} from '../render3/ng_module_ref';
|
||||
import {Type} from '../type';
|
||||
import {stringify} from '../util';
|
||||
import {NgModuleFactory} from './ng_module_factory';
|
||||
|
||||
/**
|
||||
@ -17,23 +20,50 @@ export abstract class NgModuleFactoryLoader {
|
||||
abstract load(path: string): Promise<NgModuleFactory<any>>;
|
||||
}
|
||||
|
||||
let moduleFactories = new Map<string, NgModuleFactory<any>>();
|
||||
/**
|
||||
* Map of module-id to the corresponding NgModule.
|
||||
* - In pre Ivy we track NgModuleFactory,
|
||||
* - In post Ivy we track the NgModuleType
|
||||
*/
|
||||
const modules = new Map<string, NgModuleFactory<any>|NgModuleType>();
|
||||
|
||||
/**
|
||||
* Registers a loaded module. Should only be called from generated NgModuleFactory code.
|
||||
* @publicApi
|
||||
*/
|
||||
export function registerModuleFactory(id: string, factory: NgModuleFactory<any>) {
|
||||
const existing = moduleFactories.get(id);
|
||||
if (existing) {
|
||||
throw new Error(`Duplicate module registered for ${id
|
||||
} - ${existing.moduleType.name} vs ${factory.moduleType.name}`);
|
||||
}
|
||||
moduleFactories.set(id, factory);
|
||||
const existing = modules.get(id) as NgModuleFactory<any>;
|
||||
assertNotExisting(id, existing && existing.moduleType);
|
||||
modules.set(id, factory);
|
||||
}
|
||||
|
||||
export function clearModulesForTest() {
|
||||
moduleFactories = new Map<string, NgModuleFactory<any>>();
|
||||
function assertNotExisting(id: string, type: Type<any>| null): void {
|
||||
if (type) {
|
||||
throw new Error(
|
||||
`Duplicate module registered for ${id} - ${stringify(type)} vs ${stringify(type.name)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerNgModuleType(id: string, ngModuleType: NgModuleType) {
|
||||
const existing = modules.get(id) as NgModuleType | null;
|
||||
assertNotExisting(id, existing);
|
||||
modules.set(id, ngModuleType);
|
||||
}
|
||||
|
||||
export function clearModulesForTest(): void {
|
||||
modules.clear();
|
||||
}
|
||||
|
||||
export function getModuleFactory__PRE_R3__(id: string): NgModuleFactory<any> {
|
||||
const factory = modules.get(id) as NgModuleFactory<any>| null;
|
||||
if (!factory) throw noModuleError(id);
|
||||
return factory;
|
||||
}
|
||||
|
||||
export function getModuleFactory__POST_R3__(id: string): NgModuleFactory<any> {
|
||||
const type = modules.get(id) as NgModuleType | null;
|
||||
if (!type) throw noModuleError(id);
|
||||
return new R3NgModuleFactory(type);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,8 +72,8 @@ export function clearModulesForTest() {
|
||||
* cannot be found.
|
||||
* @publicApi
|
||||
*/
|
||||
export function getModuleFactory(id: string): NgModuleFactory<any> {
|
||||
const factory = moduleFactories.get(id);
|
||||
if (!factory) throw new Error(`No module with ID ${id} loaded`);
|
||||
return factory;
|
||||
export const getModuleFactory: (id: string) => NgModuleFactory<any> = getModuleFactory__PRE_R3__;
|
||||
|
||||
function noModuleError(id: string, ): Error {
|
||||
return new Error(`No module with ID ${id} loaded`);
|
||||
}
|
||||
|
Reference in New Issue
Block a user