Skip to content

Commit 598156a

Browse files
committed
fix(@ngtools/webpack): fixed path resolution for entry modules and lazy routes.
1 parent f6f24e7 commit 598156a

18 files changed

+397
-91
lines changed

packages/webpack/src/compiler_host.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as ts from 'typescript';
2-
import {basename, dirname} from 'path';
2+
import {basename, dirname, join} from 'path';
33
import * as fs from 'fs';
44

55

@@ -93,10 +93,23 @@ export class WebpackCompilerHost implements ts.CompilerHost {
9393
private _directories: {[path: string]: VirtualDirStats} = Object.create(null);
9494
private _changed = false;
9595

96-
constructor(private _options: ts.CompilerOptions, private _setParentNodes = true) {
96+
private _setParentNodes: boolean;
97+
98+
constructor(private _options: ts.CompilerOptions, private _basePath: string) {
99+
this._setParentNodes = true;
97100
this._delegate = ts.createCompilerHost(this._options, this._setParentNodes);
98101
}
99102

103+
private _resolve(path: string) {
104+
if (path[0] == '.') {
105+
return join(this.getCurrentDirectory(), path);
106+
} else if (path[0] == '/') {
107+
return path;
108+
} else {
109+
return join(this._basePath, path);
110+
}
111+
}
112+
100113
private _setFileContent(fileName: string, content: string) {
101114
this._files[fileName] = new VirtualFileStats(fileName, content);
102115

@@ -132,26 +145,31 @@ export class WebpackCompilerHost implements ts.CompilerHost {
132145
}
133146

134147
fileExists(fileName: string): boolean {
148+
fileName = this._resolve(fileName);
135149
return fileName in this._files || this._delegate.fileExists(fileName);
136150
}
137151

138152
readFile(fileName: string): string {
153+
fileName = this._resolve(fileName);
139154
return (fileName in this._files)
140155
? this._files[fileName].content
141156
: this._delegate.readFile(fileName);
142157
}
143158

144159
directoryExists(directoryName: string): boolean {
160+
directoryName = this._resolve(directoryName);
145161
return (directoryName in this._directories) || this._delegate.directoryExists(directoryName);
146162
}
147163

148164
getFiles(path: string): string[] {
165+
path = this._resolve(path);
149166
return Object.keys(this._files)
150167
.filter(fileName => dirname(fileName) == path)
151168
.map(path => basename(path));
152169
}
153170

154171
getDirectories(path: string): string[] {
172+
path = this._resolve(path);
155173
const subdirs = Object.keys(this._directories)
156174
.filter(fileName => dirname(fileName) == path)
157175
.map(path => basename(path));
@@ -166,6 +184,8 @@ export class WebpackCompilerHost implements ts.CompilerHost {
166184
}
167185

168186
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: OnErrorFn) {
187+
fileName = this._resolve(fileName);
188+
169189
if (!(fileName in this._files)) {
170190
return this._delegate.getSourceFile(fileName, languageVersion, onError);
171191
}
@@ -181,15 +201,22 @@ export class WebpackCompilerHost implements ts.CompilerHost {
181201
return this._delegate.getDefaultLibFileName(options);
182202
}
183203

184-
writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: OnErrorFn) {
185-
this._setFileContent(fileName, data);
204+
// This is due to typescript CompilerHost interface being weird on writeFile. This shuts down
205+
// typings in WebStorm.
206+
get writeFile() {
207+
return (fileName: string, data: string, writeByteOrderMark: boolean,
208+
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]): void => {
209+
fileName = this._resolve(fileName);
210+
this._setFileContent(fileName, data);
211+
}
186212
}
187213

188214
getCurrentDirectory(): string {
189-
return this._delegate.getCurrentDirectory();
215+
return this._basePath !== null ? this._basePath : this._delegate.getCurrentDirectory();
190216
}
191217

192218
getCanonicalFileName(fileName: string): string {
219+
fileName = this._resolve(fileName);
193220
return this._delegate.getCanonicalFileName(fileName);
194221
}
195222

packages/webpack/src/entry_resolver.ts

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,45 @@
11
import * as fs from 'fs';
2-
import {dirname, join, resolve} from 'path';
2+
import {dirname, join} from 'path';
33
import * as ts from 'typescript';
44

5+
import {TypeScriptFileRefactor} from './refactor';
56

6-
function _createSource(path: string): ts.SourceFile {
7-
return ts.createSourceFile(path, fs.readFileSync(path, 'utf-8'), ts.ScriptTarget.Latest);
8-
}
9-
10-
function _findNodes(sourceFile: ts.SourceFile, node: ts.Node, kind: ts.SyntaxKind,
11-
keepGoing = false): ts.Node[] {
12-
if (node.kind == kind && !keepGoing) {
13-
return [node];
14-
}
15-
16-
return node.getChildren(sourceFile).reduce((result, n) => {
17-
return result.concat(_findNodes(sourceFile, n, kind, keepGoing));
18-
}, node.kind == kind ? [node] : []);
19-
}
207

21-
function _recursiveSymbolExportLookup(sourcePath: string,
22-
sourceFile: ts.SourceFile,
23-
symbolName: string): string | null {
8+
function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
9+
symbolName: string,
10+
host: ts.CompilerHost,
11+
program: ts.Program): string | null {
2412
// Check this file.
25-
const hasSymbol = _findNodes(sourceFile, sourceFile, ts.SyntaxKind.ClassDeclaration)
13+
const hasSymbol = refactor.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
2614
.some((cd: ts.ClassDeclaration) => {
2715
return cd.name && cd.name.text == symbolName;
2816
});
2917
if (hasSymbol) {
30-
return sourcePath;
18+
return refactor.fileName;
3119
}
3220

3321
// We found the bootstrap variable, now we just need to get where it's imported.
34-
const exports = _findNodes(sourceFile, sourceFile, ts.SyntaxKind.ExportDeclaration, false)
22+
const exports = refactor.findAstNodes(null, ts.SyntaxKind.ExportDeclaration)
3523
.map(node => node as ts.ExportDeclaration);
3624

3725
for (const decl of exports) {
3826
if (!decl.moduleSpecifier || decl.moduleSpecifier.kind !== ts.SyntaxKind.StringLiteral) {
3927
continue;
4028
}
4129

42-
const module = resolve(dirname(sourcePath), (decl.moduleSpecifier as ts.StringLiteral).text);
30+
const modulePath = (decl.moduleSpecifier as ts.StringLiteral).text;
31+
const resolvedModule = ts.resolveModuleName(
32+
modulePath, refactor.fileName, program.getCompilerOptions(), host);
33+
if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
34+
return null;
35+
}
36+
37+
const module = resolvedModule.resolvedModule.resolvedFileName;
4338
if (!decl.exportClause) {
4439
const moduleTs = module + '.ts';
4540
if (fs.existsSync(moduleTs)) {
46-
const moduleSource = _createSource(moduleTs);
47-
const maybeModule = _recursiveSymbolExportLookup(module, moduleSource, symbolName);
41+
const moduleRefactor = new TypeScriptFileRefactor(moduleTs, host, program);
42+
const maybeModule = _recursiveSymbolExportLookup(moduleRefactor, symbolName, host, program);
4843
if (maybeModule) {
4944
return maybeModule;
5045
}
@@ -59,17 +54,18 @@ function _recursiveSymbolExportLookup(sourcePath: string,
5954
if (fs.statSync(module).isDirectory()) {
6055
const indexModule = join(module, 'index.ts');
6156
if (fs.existsSync(indexModule)) {
57+
const indexRefactor = new TypeScriptFileRefactor(indexModule, host, program);
6258
const maybeModule = _recursiveSymbolExportLookup(
63-
indexModule, _createSource(indexModule), symbolName);
59+
indexRefactor, symbolName, host, program);
6460
if (maybeModule) {
6561
return maybeModule;
6662
}
6763
}
6864
}
6965

7066
// Create the source and verify that the symbol is at least a class.
71-
const source = _createSource(module);
72-
const hasSymbol = _findNodes(source, source, ts.SyntaxKind.ClassDeclaration)
67+
const source = new TypeScriptFileRefactor(module, host, program);
68+
const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
7369
.some((cd: ts.ClassDeclaration) => {
7470
return cd.name && cd.name.text == symbolName;
7571
});
@@ -86,11 +82,12 @@ function _recursiveSymbolExportLookup(sourcePath: string,
8682
return null;
8783
}
8884

89-
function _symbolImportLookup(sourcePath: string,
90-
sourceFile: ts.SourceFile,
91-
symbolName: string): string | null {
85+
function _symbolImportLookup(refactor: TypeScriptFileRefactor,
86+
symbolName: string,
87+
host: ts.CompilerHost,
88+
program: ts.Program): string | null {
9289
// We found the bootstrap variable, now we just need to get where it's imported.
93-
const imports = _findNodes(sourceFile, sourceFile, ts.SyntaxKind.ImportDeclaration, false)
90+
const imports = refactor.findAstNodes(null, ts.SyntaxKind.ImportDeclaration)
9491
.map(node => node as ts.ImportDeclaration);
9592

9693
for (const decl of imports) {
@@ -101,8 +98,14 @@ function _symbolImportLookup(sourcePath: string,
10198
continue;
10299
}
103100

104-
const module = resolve(dirname(sourcePath), (decl.moduleSpecifier as ts.StringLiteral).text);
101+
const resolvedModule = ts.resolveModuleName(
102+
(decl.moduleSpecifier as ts.StringLiteral).text,
103+
refactor.fileName, program.getCompilerOptions(), host);
104+
if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
105+
return null;
106+
}
105107

108+
const module = resolvedModule.resolvedModule.resolvedFileName;
106109
if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamespaceImport) {
107110
const binding = decl.importClause.namedBindings as ts.NamespaceImport;
108111
if (binding.name.text == symbolName) {
@@ -118,16 +121,16 @@ function _symbolImportLookup(sourcePath: string,
118121
const indexModule = join(module, 'index.ts');
119122
if (fs.existsSync(indexModule)) {
120123
const maybeModule = _recursiveSymbolExportLookup(
121-
indexModule, _createSource(indexModule), symbolName);
124+
new TypeScriptFileRefactor(indexModule, host, program), symbolName, host, program);
122125
if (maybeModule) {
123126
return maybeModule;
124127
}
125128
}
126129
}
127130

128131
// Create the source and verify that the symbol is at least a class.
129-
const source = _createSource(module);
130-
const hasSymbol = _findNodes(source, source, ts.SyntaxKind.ClassDeclaration)
132+
const source = new TypeScriptFileRefactor(module, host, program);
133+
const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
131134
.some((cd: ts.ClassDeclaration) => {
132135
return cd.name && cd.name.text == symbolName;
133136
});
@@ -145,30 +148,33 @@ function _symbolImportLookup(sourcePath: string,
145148
}
146149

147150

148-
export function resolveEntryModuleFromMain(mainPath: string) {
149-
const source = _createSource(mainPath);
151+
export function resolveEntryModuleFromMain(mainPath: string,
152+
host: ts.CompilerHost,
153+
program: ts.Program) {
154+
const source = new TypeScriptFileRefactor(mainPath, host, program);
150155

151-
const bootstrap = _findNodes(source, source, ts.SyntaxKind.CallExpression, false)
156+
const bootstrap = source.findAstNodes(source.sourceFile, ts.SyntaxKind.CallExpression, false)
152157
.map(node => node as ts.CallExpression)
153158
.filter(call => {
154159
const access = call.expression as ts.PropertyAccessExpression;
155160
return access.kind == ts.SyntaxKind.PropertyAccessExpression
156161
&& access.name.kind == ts.SyntaxKind.Identifier
157162
&& (access.name.text == 'bootstrapModule'
158163
|| access.name.text == 'bootstrapModuleFactory');
159-
});
164+
})
165+
.map(node => node.arguments[0] as ts.Identifier)
166+
.filter(node => node.kind == ts.SyntaxKind.Identifier);
160167

161-
if (bootstrap.length != 1
162-
|| bootstrap[0].arguments[0].kind !== ts.SyntaxKind.Identifier) {
168+
if (bootstrap.length != 1) {
163169
throw new Error('Tried to find bootstrap code, but could not. Specify either '
164170
+ 'statically analyzable bootstrap code or pass in an entryModule '
165171
+ 'to the plugins options.');
166172
}
167173

168-
const bootstrapSymbolName = (bootstrap[0].arguments[0] as ts.Identifier).text;
169-
const module = _symbolImportLookup(mainPath, source, bootstrapSymbolName);
174+
const bootstrapSymbolName = bootstrap[0].text;
175+
const module = _symbolImportLookup(source, bootstrapSymbolName, host, program);
170176
if (module) {
171-
return `${resolve(dirname(mainPath), module)}#${bootstrapSymbolName}`;
177+
return `${module.replace(/\.ts$/, '')}#${bootstrapSymbolName}`;
172178
}
173179

174180
// shrug... something bad happened and we couldn't find the import statement.

0 commit comments

Comments
 (0)