Skip to content

Commit 0486ba2

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

18 files changed

+408
-119
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: 54 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,45 @@
11
import * as fs from 'fs';
2-
import {dirname, join, resolve} from 'path';
2+
import {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-
}
157

16-
return node.getChildren(sourceFile).reduce((result, n) => {
17-
return result.concat(_findNodes(sourceFile, n, kind, keepGoing));
18-
}, node.kind == kind ? [node] : []);
19-
}
20-
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) {
44-
const moduleTs = module + '.ts';
45-
if (fs.existsSync(moduleTs)) {
46-
const moduleSource = _createSource(moduleTs);
47-
const maybeModule = _recursiveSymbolExportLookup(module, moduleSource, symbolName);
48-
if (maybeModule) {
49-
return maybeModule;
50-
}
39+
const moduleRefactor = new TypeScriptFileRefactor(module, host, program);
40+
const maybeModule = _recursiveSymbolExportLookup(moduleRefactor, symbolName, host, program);
41+
if (maybeModule) {
42+
return maybeModule;
5143
}
5244
continue;
5345
}
@@ -59,25 +51,24 @@ function _recursiveSymbolExportLookup(sourcePath: string,
5951
if (fs.statSync(module).isDirectory()) {
6052
const indexModule = join(module, 'index.ts');
6153
if (fs.existsSync(indexModule)) {
54+
const indexRefactor = new TypeScriptFileRefactor(indexModule, host, program);
6255
const maybeModule = _recursiveSymbolExportLookup(
63-
indexModule, _createSource(indexModule), symbolName);
56+
indexRefactor, symbolName, host, program);
6457
if (maybeModule) {
6558
return maybeModule;
6659
}
6760
}
6861
}
6962

7063
// 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)
64+
const source = new TypeScriptFileRefactor(module, host, program);
65+
const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
7366
.some((cd: ts.ClassDeclaration) => {
7467
return cd.name && cd.name.text == symbolName;
7568
});
7669

7770
if (hasSymbol) {
7871
return module;
79-
} else {
80-
return null;
8172
}
8273
}
8374
}
@@ -86,11 +77,12 @@ function _recursiveSymbolExportLookup(sourcePath: string,
8677
return null;
8778
}
8879

89-
function _symbolImportLookup(sourcePath: string,
90-
sourceFile: ts.SourceFile,
91-
symbolName: string): string | null {
80+
function _symbolImportLookup(refactor: TypeScriptFileRefactor,
81+
symbolName: string,
82+
host: ts.CompilerHost,
83+
program: ts.Program): string | null {
9284
// 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)
85+
const imports = refactor.findAstNodes(null, ts.SyntaxKind.ImportDeclaration)
9486
.map(node => node as ts.ImportDeclaration);
9587

9688
for (const decl of imports) {
@@ -101,8 +93,14 @@ function _symbolImportLookup(sourcePath: string,
10193
continue;
10294
}
10395

104-
const module = resolve(dirname(sourcePath), (decl.moduleSpecifier as ts.StringLiteral).text);
96+
const resolvedModule = ts.resolveModuleName(
97+
(decl.moduleSpecifier as ts.StringLiteral).text,
98+
refactor.fileName, program.getCompilerOptions(), host);
99+
if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
100+
return null;
101+
}
105102

103+
const module = resolvedModule.resolvedModule.resolvedFileName;
106104
if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamespaceImport) {
107105
const binding = decl.importClause.namedBindings as ts.NamespaceImport;
108106
if (binding.name.text == symbolName) {
@@ -113,29 +111,11 @@ function _symbolImportLookup(sourcePath: string,
113111
const binding = decl.importClause.namedBindings as ts.NamedImports;
114112
for (const specifier of binding.elements) {
115113
if (specifier.name.text == symbolName) {
116-
// If it's a directory, load its index and recursively lookup.
117-
if (fs.statSync(module).isDirectory()) {
118-
const indexModule = join(module, 'index.ts');
119-
if (fs.existsSync(indexModule)) {
120-
const maybeModule = _recursiveSymbolExportLookup(
121-
indexModule, _createSource(indexModule), symbolName);
122-
if (maybeModule) {
123-
return maybeModule;
124-
}
125-
}
126-
}
127-
128-
// 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)
131-
.some((cd: ts.ClassDeclaration) => {
132-
return cd.name && cd.name.text == symbolName;
133-
});
134-
135-
if (hasSymbol) {
136-
return module;
137-
} else {
138-
return null;
114+
// Create the source and recursively lookup the import.
115+
const source = new TypeScriptFileRefactor(module, host, program);
116+
const maybeModule = _recursiveSymbolExportLookup(source, symbolName, host, program);
117+
if (maybeModule) {
118+
return maybeModule;
139119
}
140120
}
141121
}
@@ -145,30 +125,32 @@ function _symbolImportLookup(sourcePath: string,
145125
}
146126

147127

148-
export function resolveEntryModuleFromMain(mainPath: string) {
149-
const source = _createSource(mainPath);
128+
export function resolveEntryModuleFromMain(mainPath: string,
129+
host: ts.CompilerHost,
130+
program: ts.Program) {
131+
const source = new TypeScriptFileRefactor(mainPath, host, program);
150132

151-
const bootstrap = _findNodes(source, source, ts.SyntaxKind.CallExpression, false)
133+
const bootstrap = source.findAstNodes(source.sourceFile, ts.SyntaxKind.CallExpression, false)
152134
.map(node => node as ts.CallExpression)
153135
.filter(call => {
154136
const access = call.expression as ts.PropertyAccessExpression;
155137
return access.kind == ts.SyntaxKind.PropertyAccessExpression
156138
&& access.name.kind == ts.SyntaxKind.Identifier
157139
&& (access.name.text == 'bootstrapModule'
158140
|| access.name.text == 'bootstrapModuleFactory');
159-
});
141+
})
142+
.map(node => node.arguments[0] as ts.Identifier)
143+
.filter(node => node.kind == ts.SyntaxKind.Identifier);
160144

161-
if (bootstrap.length != 1
162-
|| bootstrap[0].arguments[0].kind !== ts.SyntaxKind.Identifier) {
145+
if (bootstrap.length != 1) {
163146
throw new Error('Tried to find bootstrap code, but could not. Specify either '
164147
+ 'statically analyzable bootstrap code or pass in an entryModule '
165148
+ 'to the plugins options.');
166149
}
167-
168-
const bootstrapSymbolName = (bootstrap[0].arguments[0] as ts.Identifier).text;
169-
const module = _symbolImportLookup(mainPath, source, bootstrapSymbolName);
150+
const bootstrapSymbolName = bootstrap[0].text;
151+
const module = _symbolImportLookup(source, bootstrapSymbolName, host, program);
170152
if (module) {
171-
return `${resolve(dirname(mainPath), module)}#${bootstrapSymbolName}`;
153+
return `${module.replace(/\.ts$/, '')}#${bootstrapSymbolName}`;
172154
}
173155

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

0 commit comments

Comments
 (0)