Skip to content

Commit

Permalink
refactor(compiler-cli): add a compiler option to enable checking for …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
pmvald authored and atscott committed Oct 10, 2023
1 parent da056a1 commit e8201a5
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 0 deletions.
1 change: 1 addition & 0 deletions goldens/public-api/compiler-cli/compiler_options.md
Expand Up @@ -55,6 +55,7 @@ export interface LegacyNgcOptions {
export interface MiscOptions {
compileNonExportedClasses?: boolean;
disableTypeScriptVersionCheck?: boolean;
forbidOrphanComponents?: boolean;
}

// @public
Expand Down
Expand Up @@ -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;
}
8 changes: 8 additions & 0 deletions packages/compiler-cli/src/ngtsc/core/src/compiler.ts
Expand Up @@ -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(
Expand Down
15 changes: 15 additions & 0 deletions packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Expand Up @@ -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:`.
Expand Down

0 comments on commit e8201a5

Please sign in to comment.