refactor(compiler-cli): add a compiler option to enable checking for orphan component (#52061)

Orphan component is an anti-pattern in Angular where a component is rendered while the NgModule declaring it is not installed. It is not easy to capture this scenario, specially in compile time. But it is possible to capture a special case in runtime where the component is being rendered without its NgModule even loaded into the browser. This change adds a flag in cli compiler option to enable such checking, and throwing a runtime exception if it happens. Note that such check is only done in dev mode.

Currently the check requires some generated code that is behind ngJitMode flag (i.e., call to ɵɵsetNgModuleScope), and the new flag can be set only if JIT mode is enabled (i.e., supportJitMode=true) otherwise an error will be thrown.

The orphan component is a main blocker for rolling out local compilation in g3. This option is needed for identifying and isolating such cases.

PR Close #52061
This commit is contained in:
Payam Valadkhan 2023-10-05 15:49:29 -04:00 committed by Andrew Scott
parent da056a1fe2
commit e8201a5962
4 changed files with 33 additions and 0 deletions

View file

@ -55,6 +55,7 @@ export interface LegacyNgcOptions {
export interface MiscOptions {
compileNonExportedClasses?: boolean;
disableTypeScriptVersionCheck?: boolean;
forbidOrphanComponents?: boolean;
}
// @public

View file

@ -411,4 +411,13 @@ export interface MiscOptions {
* Disable TypeScript Version Check.
*/
disableTypeScriptVersionCheck?: boolean;
/**
* Enables the runtime check to guard against rendering a component without first loading its
* NgModule.
*
* This check is only applied to the current compilation unit, i.e., a component imported from
* another library without option set will not issue error if rendered in orphan way.
*/
forbidOrphanComponents?: boolean;
}

View file

@ -1085,6 +1085,14 @@ export class NgCompiler {
'JIT mode support ("supportJitMode" option) cannot be disabled in partial compilation mode.');
}
// Currently forbidOrphanComponents depends on the code generated behind ngJitMode flag. Until
// we come up with a better design for these flags, it is necessary to have the JIT mode in
// order for forbidOrphanComponents to be able to work properly.
if (supportJitMode === false && this.options.forbidOrphanComponents) {
throw new Error(
'JIT mode support ("supportJitMode" option) cannot be disabled when forbidOrphanComponents is set to true');
}
// Set up the IvyCompilation, which manages state for the Ivy transformer.
const handlers: DecoratorHandler<unknown, unknown, SemanticSymbol|null, unknown>[] = [
new ComponentDecoratorHandler(

View file

@ -537,6 +537,21 @@ function allTests(os: string) {
'never, never, never>');
});
it('should error when supportJitMode is false and forbidOrphanComponents is true', () => {
env.tsconfig({
supportJitMode: false,
forbidOrphanComponents: true,
});
env.write('test.ts', '');
const diagnostics = env.driveDiagnostics();
expect(diagnostics).toEqual([jasmine.objectContaining({
messageText: jasmine.stringMatching(
/JIT mode support \("supportJitMode" option\) cannot be disabled when forbidOrphanComponents is set to true/),
})]);
});
// This test triggers the Tsickle compiler which asserts that the file-paths
// are valid for the real OS. When on non-Windows systems it doesn't like paths
// that start with `C:`.