diff --git a/packages/ngtools/webpack/src/transformers/import_factory.ts b/packages/ngtools/webpack/src/transformers/import_factory.ts index 7637c72f00bb..28257d847022 100644 --- a/packages/ngtools/webpack/src/transformers/import_factory.ts +++ b/packages/ngtools/webpack/src/transformers/import_factory.ts @@ -10,6 +10,9 @@ import * as ts from 'typescript'; import { forwardSlashPath } from '../utils'; +// Check if a ts.Symbol is an alias. +const isAlias = (symbol: ts.Symbol) => symbol.flags & ts.SymbolFlags.Alias; + /** * Given this original source code: * @@ -193,15 +196,26 @@ function replaceImport( // Try to resolve the import. It might be a reexport from somewhere and the ngfactory will only // be present next to the original module. - const exportedSymbol = typeChecker.getSymbolAtLocation(exportNameId); + let exportedSymbol = typeChecker.getSymbolAtLocation(exportNameId); if (!exportedSymbol) { return warnAndBail(); } + // Named exports are also a declaration in the re-exporting module so we have to follow the + // re-exports to find the original symbol. + if (isAlias(exportedSymbol)) { + exportedSymbol = typeChecker.getAliasedSymbol(exportedSymbol); + if (!exportedSymbol) { + return warnAndBail(); + } + } + + // Find declarations of the original symbol so we can get their source file name. const exportedSymbolDecl = exportedSymbol.getDeclarations(); if (!exportedSymbolDecl || exportedSymbolDecl.length === 0) { return warnAndBail(); } + // Let's guess the first declaration is the one we want, because we don't have a better criteria. // Get the relative path from the containing module to the imported module. const relativePath = relative(dirname(fileName), exportedSymbolDecl[0].getSourceFile().fileName); diff --git a/packages/ngtools/webpack/src/transformers/import_factory_spec.ts b/packages/ngtools/webpack/src/transformers/import_factory_spec.ts index e0ab9422290b..06745ca9272c 100644 --- a/packages/ngtools/webpack/src/transformers/import_factory_spec.ts +++ b/packages/ngtools/webpack/src/transformers/import_factory_spec.ts @@ -64,7 +64,7 @@ describe('@ngtools/webpack transformers', () => { expect(warningCalled).toBeTruthy(); }); - it('should support resolving reexports', () => { + it('should support resolving * re-exports', () => { const additionalFiles: Record = { 'shared/index.ts': ` export * from './path/to/lazy/lazy.module'; @@ -95,5 +95,79 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); + + it('should support resolving named re-exports', () => { + const additionalFiles: Record = { + 'shared/index.ts': ` + export { LazyModule } from './path/to/lazy/lazy.module'; + `, + 'shared/path/to/lazy/lazy.module.ts': ` + export const LazyModule = {}; + `, + }; + const input = tags.stripIndent` + const ɵ0 = () => import('./shared').then(m => m.LazyModule); + const routes = [{ + path: 'lazy', + loadChildren: ɵ0 + }]; + `; + + const output = tags.stripIndent` + const ɵ0 = () => import("./shared/path/to/lazy/lazy.module.ngfactory").then(m => m.LazyModuleNgFactory); + const routes = [{ + path: 'lazy', + loadChildren: ɵ0 + }]; + `; + + const { program, compilerHost } = createTypescriptContext(input, additionalFiles, true); + const transformer = importFactory(() => { }, () => program.getTypeChecker()); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + + + it('should support resolving re-export chains', () => { + const additionalFiles: Record = { + 'shared/index.ts': ` + export { LazyModule } from './index2'; + `, + 'shared/index2.ts': ` + export * from './index3'; + `, + 'shared/index3.ts': ` + export { LazyModule } from './index4'; + `, + 'shared/index4.ts': ` + export * from './path/to/lazy/lazy.module'; + `, + 'shared/path/to/lazy/lazy.module.ts': ` + export const LazyModule = {}; + `, + }; + const input = tags.stripIndent` + const ɵ0 = () => import('./shared').then(m => m.LazyModule); + const routes = [{ + path: 'lazy', + loadChildren: ɵ0 + }]; + `; + + const output = tags.stripIndent` + const ɵ0 = () => import("./shared/path/to/lazy/lazy.module.ngfactory").then(m => m.LazyModuleNgFactory); + const routes = [{ + path: 'lazy', + loadChildren: ɵ0 + }]; + `; + + const { program, compilerHost } = createTypescriptContext(input, additionalFiles, true); + const transformer = importFactory(() => { }, () => program.getTypeChecker()); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); }); });