From aa6b35f35c5fead45d7cdfae4d816f4a9cb6423b Mon Sep 17 00:00:00 2001 From: Payam Valadkhan Date: Tue, 16 Jan 2024 02:37:13 -0500 Subject: [PATCH] feat(compiler-cli): make it configurable to generate alias reexports (#53937) At the moment when unified host is selected (through option `_useHostForImportGeneration`) the compiler always generates alias reexports. Such reexports are mainly generated to satisfy strict dependency condition for generated files. Such condition is no longer the case for G3. At the same time, these alias reexports make it impossible to mix locally compiled targets with globally compiled targets. More precisely, a globally compiled target may not be able to consume a locally compiled target as its dependency since the former may import from the alias reexports which do not exist in the latter due to local compilation mode. So, to make global-local compilation interop possible, it is required to be able to turn off alias reexport generation. PR Close #53937 --- .../src/ngtsc/core/src/compiler.ts | 11 +- packages/compiler-cli/test/ngtsc/env.ts | 3 +- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 171 +++++++++++++++--- 3 files changed, 154 insertions(+), 31 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index 93c43ac867374..e4fd849109a52 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -985,7 +985,9 @@ export class NgCompiler { // Construct the ReferenceEmitter. let refEmitter: ReferenceEmitter; let aliasingHost: AliasingHost|null = null; - if (this.adapter.unifiedModulesHost === null || !this.options['_useHostForImportGeneration']) { + if (this.adapter.unifiedModulesHost === null || + (!this.options['_useHostForImportGeneration'] && + !this.options['_useHostForImportAndAliasGeneration'])) { let localImportStrategy: ReferenceEmitStrategy; // The strategy used for local, in-project imports depends on whether TS has been configured @@ -1031,11 +1033,14 @@ export class NgCompiler { // First, try to use local identifiers if available. new LocalIdentifierStrategy(), // Then use aliased references (this is a workaround to StrictDeps checks). - new AliasStrategy(), + ...(this.options['_useHostForImportAndAliasGeneration'] ? [new AliasStrategy()] : []), // Then use fileNameToModuleName to emit imports. new UnifiedModulesStrategy(reflector, this.adapter.unifiedModulesHost), ]); - aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost); + + if (this.options['_useHostForImportAndAliasGeneration']) { + aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost); + } } const evaluator = diff --git a/packages/compiler-cli/test/ngtsc/env.ts b/packages/compiler-cli/test/ngtsc/env.ts index 1b2beacaca106..567e83821de23 100644 --- a/packages/compiler-cli/test/ngtsc/env.ts +++ b/packages/compiler-cli/test/ngtsc/env.ts @@ -204,7 +204,8 @@ export class NgtscTestEnvironment { } this.write('tsconfig.json', JSON.stringify(tsconfig, null, 2)); - if (extraOpts['_useHostForImportGeneration'] === true) { + if (extraOpts['_useHostForImportGeneration'] || + extraOpts['_useHostForImportAndAliasGeneration']) { setWrapHostForTest(makeWrapHost(new FileNameToModuleNameHost(this.fs))); } } diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index fbaac611217fa..ec015e5775a1d 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -6823,11 +6823,12 @@ function allTests(os: string) { }); describe('NgModule export aliasing', () => { - it('should use an alias to import a directive from a deep dependency', () => { - env.tsconfig({'_useHostForImportGeneration': true}); + it('should use an alias to import a directive from a deep dependency when _useHostForImportAndAliasGeneration is set', + () => { + env.tsconfig({'_useHostForImportAndAliasGeneration': true}); - // 'alpha' declares the directive which will ultimately be imported. - env.write('alpha.d.ts', ` + // 'alpha' declares the directive which will ultimately be imported. + env.write('alpha.d.ts', ` import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; export declare class ExternalDir { @@ -6839,8 +6840,8 @@ function allTests(os: string) { } `); - // 'beta' re-exports AlphaModule from alpha. - env.write('beta.d.ts', ` + // 'beta' re-exports AlphaModule from alpha. + env.write('beta.d.ts', ` import {ɵɵNgModuleDeclaration} from '@angular/core'; import {AlphaModule} from './alpha'; @@ -6849,9 +6850,9 @@ function allTests(os: string) { } `); - // The application imports BetaModule from beta, gaining visibility of - // ExternalDir from alpha. - env.write('test.ts', ` + // The application imports BetaModule from beta, gaining visibility of + // ExternalDir from alpha. + env.write('test.ts', ` import {Component, NgModule} from '@angular/core'; import {BetaModule} from './beta'; @@ -6867,17 +6868,71 @@ function allTests(os: string) { }) export class Module {} `); - env.driveMain(); - const jsContents = env.getContents('test.js'); + env.driveMain(); + const jsContents = env.getContents('test.js'); - // Expect that ExternalDir from alpha is imported via the re-export from beta. - expect(jsContents).toContain('import * as i1 from "root/beta";'); - expect(jsContents).toContain('dependencies: [i1.\u0275ng$root$alpha$$ExternalDir]'); - }); + // Expect that ExternalDir from alpha is imported via the re-export from beta. + expect(jsContents).toContain('import * as i1 from "root/beta";'); + expect(jsContents).toContain('dependencies: [i1.\u0275ng$root$alpha$$ExternalDir]'); + }); + + it('should directly import a directive from a deep dependency when _useHostForImportGeneration is set', + () => { + env.tsconfig({'_useHostForImportGeneration': true}); - it('should write alias ES2015 exports for NgModule exported directives', () => { - env.tsconfig({'_useHostForImportGeneration': true}); - env.write('external.d.ts', ` + // 'alpha' declares the directive which will ultimately be imported. + env.write('alpha.d.ts', ` + import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; + + export declare class ExternalDir { + static ɵdir: ɵɵDirectiveDeclaration; + } + + export declare class AlphaModule { + static ɵmod: ɵɵNgModuleDeclaration; + } + `); + + // 'beta' re-exports AlphaModule from alpha. + env.write('beta.d.ts', ` + import {ɵɵNgModuleDeclaration} from '@angular/core'; + import {AlphaModule} from './alpha'; + + export declare class BetaModule { + static ɵmod: ɵɵNgModuleDeclaration; + } + `); + + // The application imports BetaModule from beta, gaining visibility of + // ExternalDir from alpha. + env.write('test.ts', ` + import {Component, NgModule} from '@angular/core'; + import {BetaModule} from './beta'; + + @Component({ + selector: 'cmp', + template: '
', + }) + export class Cmp {} + + @NgModule({ + declarations: [Cmp], + imports: [BetaModule], + }) + export class Module {} + `); + env.driveMain(); + const jsContents = env.getContents('test.js'); + + // Expect that ExternalDir from alpha is imported via the re-export from beta. + expect(jsContents).toContain('import * as i1 from "root/alpha";'); + expect(jsContents).toContain('dependencies: [i1.ExternalDir]'); + }); + + it('should write alias ES2015 exports for NgModule exported directives when _useHostForImportAndAliasGeneration is set', + () => { + env.tsconfig({'_useHostForImportAndAliasGeneration': true}); + env.write('external.d.ts', ` import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; import {LibModule} from './lib'; @@ -6889,7 +6944,7 @@ function allTests(os: string) { static ɵmod: ɵɵNgModuleDeclaration; } `); - env.write('lib.d.ts', ` + env.write('lib.d.ts', ` import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; export declare class LibDir { @@ -6900,7 +6955,7 @@ function allTests(os: string) { static ɵmod: ɵɵNgModuleDeclaration; } `); - env.write('foo.ts', ` + env.write('foo.ts', ` import {Directive, NgModule} from '@angular/core'; import {ExternalModule} from './external'; @@ -6913,7 +6968,7 @@ function allTests(os: string) { }) export class FooModule {} `); - env.write('index.ts', ` + env.write('index.ts', ` import {Component, NgModule} from '@angular/core'; import {FooModule} from './foo'; @@ -6929,14 +6984,76 @@ function allTests(os: string) { }) export class IndexModule {} `); - env.driveMain(); - const jsContents = env.getContents('index.js'); - expect(jsContents) - .toContain('export { FooDir as \u0275ng$root$foo$$FooDir } from "root/foo";'); - }); + env.driveMain(); + const jsContents = env.getContents('index.js'); + expect(jsContents) + .toContain('export { FooDir as \u0275ng$root$foo$$FooDir } from "root/foo";'); + }); + + it('should not write alias ES2015 exports for NgModule exported directives when _useHostForImportGeneration is set', + () => { + env.tsconfig({'_useHostForImportGeneration': true}); + env.write('external.d.ts', ` + import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; + import {LibModule} from './lib'; + + export declare class ExternalDir { + static ɵdir: ɵɵDirectiveDeclaration; + } + + export declare class ExternalModule { + static ɵmod: ɵɵNgModuleDeclaration; + } + `); + env.write('lib.d.ts', ` + import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core'; + + export declare class LibDir { + static ɵdir: ɵɵDirectiveDeclaration; + } + + export declare class LibModule { + static ɵmod: ɵɵNgModuleDeclaration; + } + `); + env.write('foo.ts', ` + import {Directive, NgModule} from '@angular/core'; + import {ExternalModule} from './external'; + + @Directive({selector: '[foo]'}) + export class FooDir {} + + @NgModule({ + declarations: [FooDir], + exports: [FooDir, ExternalModule] + }) + export class FooModule {} + `); + env.write('index.ts', ` + import {Component, NgModule} from '@angular/core'; + import {FooModule} from './foo'; + + @Component({ + selector: 'index', + template: '
', + }) + export class IndexCmp {} + + @NgModule({ + declarations: [IndexCmp], + exports: [FooModule], + }) + export class IndexModule {} + `); + env.driveMain(); + const jsContents = env.getContents('index.js'); + expect(jsContents) + .not.toMatch( + /export\s+\{\s*FooDir\s+as\s+ \u0275ng$root$foo$$FooDir\s*\}\s+from\s+"root\/foo";/); + }); it('should escape unusual characters in aliased filenames', () => { - env.tsconfig({'_useHostForImportGeneration': true}); + env.tsconfig({'_useHostForImportAndAliasGeneration': true}); env.write('other._$test.ts', ` import {Directive, NgModule} from '@angular/core';