Skip to content

Commit

Permalink
feat(compiler-cli): make it configurable to generate alias reexports (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
pmvald authored and pkozlowski-opensource committed Jan 22, 2024
1 parent ec03e46 commit b774e22
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 31 deletions.
11 changes: 8 additions & 3 deletions packages/compiler-cli/src/ngtsc/core/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler-cli/test/ngtsc/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}
Expand Down
171 changes: 144 additions & 27 deletions packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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';
Expand All @@ -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';
Expand All @@ -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<ExternalDir, '[test]', never, never, never, never>;
}
export declare class AlphaModule {
static ɵmod: ɵɵNgModuleDeclaration<AlphaModule, [typeof ExternalDir], never, [typeof ExternalDir]>;
}
`);

// '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<AlphaModule, never, never, [typeof AlphaModule]>;
}
`);

// 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: '<div test></div>',
})
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';
Expand All @@ -6889,7 +6944,7 @@ function allTests(os: string) {
static ɵmod: ɵɵNgModuleDeclaration<ExternalModule, [typeof ExternalDir], never, [typeof ExternalDir, typeof LibModule]>;
}
`);
env.write('lib.d.ts', `
env.write('lib.d.ts', `
import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core';
export declare class LibDir {
Expand All @@ -6900,7 +6955,7 @@ function allTests(os: string) {
static ɵmod: ɵɵNgModuleDeclaration<LibModule, [typeof LibDir], never, [typeof LibDir]>;
}
`);
env.write('foo.ts', `
env.write('foo.ts', `
import {Directive, NgModule} from '@angular/core';
import {ExternalModule} from './external';
Expand All @@ -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';
Expand All @@ -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<ExternalDir, '[test]', never, never, never, never>;
}
export declare class ExternalModule {
static ɵmod: ɵɵNgModuleDeclaration<ExternalModule, [typeof ExternalDir], never, [typeof ExternalDir, typeof LibModule]>;
}
`);
env.write('lib.d.ts', `
import {ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core';
export declare class LibDir {
static ɵdir: ɵɵDirectiveDeclaration<LibDir, '[lib]', never, never, never, never>;
}
export declare class LibModule {
static ɵmod: ɵɵNgModuleDeclaration<LibModule, [typeof LibDir], never, [typeof LibDir]>;
}
`);
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: '<div foo test lib></div>',
})
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';
Expand Down

0 comments on commit b774e22

Please sign in to comment.