Skip to content

Commit 50d090f

Browse files
committed
fix(@ngtools/webpack): analyze the typescript file for additional dependencies
By default, Webpack only add dependencies it can see. Types or imports that are not kept in transpilations are removed, and webpack dont see them. By reading the AST directly we manually add the dependencies to webpack. Fixes #7995.
1 parent 46e3033 commit 50d090f

File tree

3 files changed

+71
-7
lines changed

3 files changed

+71
-7
lines changed

packages/@ngtools/webpack/src/loader.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,28 @@ function _getResourcesUrls(refactor: TypeScriptFileRefactor): string[] {
424424
}
425425

426426

427+
function _getImports(refactor: TypeScriptFileRefactor,
428+
compilerOptions: ts.CompilerOptions,
429+
host: ts.ModuleResolutionHost,
430+
cache: ts.ModuleResolutionCache): string[] {
431+
const containingFile = refactor.fileName;
432+
433+
return refactor.findAstNodes(null, ts.SyntaxKind.ImportDeclaration, false)
434+
.map((clause: ts.ImportDeclaration) => {
435+
const moduleName = (clause.moduleSpecifier as ts.StringLiteral).text;
436+
const resolved = ts.resolveModuleName(
437+
moduleName, containingFile, compilerOptions, host, cache);
438+
439+
if (resolved.resolvedModule) {
440+
return resolved.resolvedModule.resolvedFileName;
441+
} else {
442+
return null;
443+
}
444+
})
445+
.filter(x => x);
446+
}
447+
448+
427449
/**
428450
* Recursively calls diagnose on the plugins for all the reverse dependencies.
429451
* @private
@@ -539,6 +561,13 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
539561
const refactor = new TypeScriptFileRefactor(
540562
sourceFileName, plugin.compilerHost, plugin.program, source);
541563

564+
// Force a few compiler options to make sure we get the result we want.
565+
const compilerOptions: ts.CompilerOptions = Object.assign({}, plugin.compilerOptions, {
566+
inlineSources: true,
567+
inlineSourceMap: false,
568+
sourceRoot: plugin.basePath
569+
});
570+
542571
Promise.resolve()
543572
.then(() => {
544573
if (!plugin.skipCodeGeneration) {
@@ -570,6 +599,8 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
570599
_getResourcesUrls(refactor).forEach((url: string) => {
571600
this.addDependency(path.resolve(path.dirname(sourceFileName), url));
572601
});
602+
_getImports(refactor, compilerOptions, plugin.compilerHost, plugin.moduleResolutionCache)
603+
.forEach((importString: string) => this.addDependency(importString));
573604
})
574605
.then(() => {
575606
if (source) {
@@ -594,13 +625,6 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
594625
}
595626
}
596627

597-
// Force a few compiler options to make sure we get the result we want.
598-
const compilerOptions: ts.CompilerOptions = Object.assign({}, plugin.compilerOptions, {
599-
inlineSources: true,
600-
inlineSourceMap: false,
601-
sourceRoot: plugin.basePath
602-
});
603-
604628
const result = refactor.transpile(compilerOptions);
605629

606630
if (plugin.failedCompilation) {

packages/@ngtools/webpack/src/plugin.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export class AotPlugin implements Tapable {
5050
private _compilerOptions: ts.CompilerOptions;
5151
private _angularCompilerOptions: any;
5252
private _program: ts.Program;
53+
private _moduleResolutionCache: ts.ModuleResolutionCache;
5354
private _rootFilePath: string[];
5455
private _compilerHost: WebpackCompilerHost;
5556
private _resourceLoader: WebpackResourceLoader;
@@ -97,6 +98,7 @@ export class AotPlugin implements Tapable {
9798
}
9899
get genDir() { return this._genDir; }
99100
get program() { return this._program; }
101+
get moduleResolutionCache() { return this._moduleResolutionCache; }
100102
get skipCodeGeneration() { return this._skipCodeGeneration; }
101103
get replaceExport() { return this._replaceExport; }
102104
get typeCheck() { return this._typeCheck; }
@@ -223,6 +225,10 @@ export class AotPlugin implements Tapable {
223225
this._program = ts.createProgram(
224226
this._rootFilePath, this._compilerOptions, this._compilerHost);
225227

228+
// We use absolute paths everywhere.
229+
this._moduleResolutionCache = ts.createModuleResolutionCache(
230+
this._basePath, (fileName: string) => fileName);
231+
226232
// We enable caching of the filesystem in compilerHost _after_ the program has been created,
227233
// because we don't want SourceFile instances to be cached past this point.
228234
this._compilerHost.enableCaching();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
killAllProcesses,
3+
waitForAnyProcessOutputToMatch,
4+
execAndWaitForOutputToMatch,
5+
} from '../../utils/process';
6+
import { writeFile, prependToFile } from '../../utils/fs';
7+
import {getGlobalVariable} from '../../utils/env';
8+
9+
10+
const successRe = /webpack: Compiled successfully/;
11+
12+
export default async function() {
13+
if (process.platform.startsWith('win')) {
14+
return;
15+
}
16+
// Skip this in ejected tests.
17+
if (getGlobalVariable('argv').eject) {
18+
return;
19+
}
20+
21+
await writeFile('src/app/type.ts', `export type MyType = number;`);
22+
await prependToFile('src/app/app.component.ts', 'import { MyType } from "./type";\n');
23+
24+
try {
25+
await execAndWaitForOutputToMatch('ng', ['serve'], successRe);
26+
27+
await Promise.all([
28+
waitForAnyProcessOutputToMatch(successRe, 20000),
29+
writeFile('src/app/type.ts', `export type MyType = string;`),
30+
]);
31+
} finally {
32+
killAllProcesses();
33+
}
34+
}

0 commit comments

Comments
 (0)