Skip to content

Commit 13056a3

Browse files
committed
refactor(@ngtools/webpack): use ts transforms in refactor
1 parent 35c10df commit 13056a3

30 files changed

+1275
-125
lines changed

packages/@ngtools/webpack/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"dependencies": {
2828
"loader-utils": "^1.0.2",
2929
"magic-string": "^0.22.3",
30-
"source-map": "^0.5.6"
30+
"source-map": "^0.5.6",
31+
"semver": "^5.3.0"
3132
},
3233
"peerDependencies": {
3334
"enhanced-resolve": "^3.1.0",

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,14 @@ export class WebpackCompilerHost implements ts.CompilerHost {
214214
this._changedFiles = Object.create(null);
215215
this._changedDirs = Object.create(null);
216216
}
217-
getChangedFilePaths(): string[] {
218-
return Object.keys(this._changedFiles);
217+
218+
// TypeScriptFileRefactor will also write .js and .js.map to the compiler host, but we usually
219+
// only care about changed ts files.
220+
getChangedFilePaths(onlyTs = true): string[] {
221+
const files = Object.keys(this._changedFiles);
222+
return onlyTs
223+
? files.filter((f) => f.endsWith('.ts'))
224+
: files;
219225
}
220226

221227
invalidate(fileName: string): void {

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import * as fs from 'fs';
22
import {join} from 'path';
33
import * as ts from 'typescript';
44

5-
import {TypeScriptFileRefactor} from './refactor';
5+
import {TypeScriptFileRefactor, getTypeScriptFileRefactor} from './refactor/refactor';
6+
import {ProgramManager} from './program_manager';
67

78

89
function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
910
symbolName: string,
1011
host: ts.CompilerHost,
11-
program: ts.Program): string | null {
12+
programManager: ProgramManager): string | null {
1213
// Check this file.
1314
const hasSymbol = refactor.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
1415
.some((cd: ts.ClassDeclaration) => {
@@ -29,15 +30,16 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
2930

3031
const modulePath = (decl.moduleSpecifier as ts.StringLiteral).text;
3132
const resolvedModule = ts.resolveModuleName(
32-
modulePath, refactor.fileName, program.getCompilerOptions(), host);
33+
modulePath, refactor.fileName, programManager.program.getCompilerOptions(), host);
3334
if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
3435
return null;
3536
}
3637

3738
const module = resolvedModule.resolvedModule.resolvedFileName;
3839
if (!decl.exportClause) {
39-
const moduleRefactor = new TypeScriptFileRefactor(module, host, program);
40-
const maybeModule = _recursiveSymbolExportLookup(moduleRefactor, symbolName, host, program);
40+
const moduleRefactor = getTypeScriptFileRefactor(module, host, programManager);
41+
const maybeModule = _recursiveSymbolExportLookup(
42+
moduleRefactor, symbolName, host, programManager);
4143
if (maybeModule) {
4244
return maybeModule;
4345
}
@@ -51,17 +53,17 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
5153
if (fs.statSync(module).isDirectory()) {
5254
const indexModule = join(module, 'index.ts');
5355
if (fs.existsSync(indexModule)) {
54-
const indexRefactor = new TypeScriptFileRefactor(indexModule, host, program);
56+
const indexRefactor = getTypeScriptFileRefactor(indexModule, host, programManager);
5557
const maybeModule = _recursiveSymbolExportLookup(
56-
indexRefactor, symbolName, host, program);
58+
indexRefactor, symbolName, host, programManager);
5759
if (maybeModule) {
5860
return maybeModule;
5961
}
6062
}
6163
}
6264

6365
// Create the source and verify that the symbol is at least a class.
64-
const source = new TypeScriptFileRefactor(module, host, program);
66+
const source = getTypeScriptFileRefactor(module, host, programManager);
6567
const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
6668
.some((cd: ts.ClassDeclaration) => {
6769
return cd.name && cd.name.text == symbolName;
@@ -80,7 +82,7 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
8082
function _symbolImportLookup(refactor: TypeScriptFileRefactor,
8183
symbolName: string,
8284
host: ts.CompilerHost,
83-
program: ts.Program): string | null {
85+
programManager: ProgramManager): string | null {
8486
// We found the bootstrap variable, now we just need to get where it's imported.
8587
const imports = refactor.findAstNodes(null, ts.SyntaxKind.ImportDeclaration)
8688
.map(node => node as ts.ImportDeclaration);
@@ -95,7 +97,7 @@ function _symbolImportLookup(refactor: TypeScriptFileRefactor,
9597

9698
const resolvedModule = ts.resolveModuleName(
9799
(decl.moduleSpecifier as ts.StringLiteral).text,
98-
refactor.fileName, program.getCompilerOptions(), host);
100+
refactor.fileName, programManager.program.getCompilerOptions(), host);
99101
if (!resolvedModule.resolvedModule || !resolvedModule.resolvedModule.resolvedFileName) {
100102
continue;
101103
}
@@ -112,8 +114,9 @@ function _symbolImportLookup(refactor: TypeScriptFileRefactor,
112114
for (const specifier of binding.elements) {
113115
if (specifier.name.text == symbolName) {
114116
// 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+
const source = getTypeScriptFileRefactor(module, host, programManager);
118+
const maybeModule = _recursiveSymbolExportLookup(
119+
source, symbolName, host, programManager);
117120
if (maybeModule) {
118121
return maybeModule;
119122
}
@@ -127,8 +130,8 @@ function _symbolImportLookup(refactor: TypeScriptFileRefactor,
127130

128131
export function resolveEntryModuleFromMain(mainPath: string,
129132
host: ts.CompilerHost,
130-
program: ts.Program) {
131-
const source = new TypeScriptFileRefactor(mainPath, host, program);
133+
programManager: ProgramManager) {
134+
const source = getTypeScriptFileRefactor(mainPath, host, programManager);
132135

133136
const bootstrap = source.findAstNodes(source.sourceFile, ts.SyntaxKind.CallExpression, true)
134137
.map(node => node as ts.CallExpression)
@@ -148,7 +151,7 @@ export function resolveEntryModuleFromMain(mainPath: string,
148151
+ 'to the plugins options.');
149152
}
150153
const bootstrapSymbolName = bootstrap[0].text;
151-
const module = _symbolImportLookup(source, bootstrapSymbolName, host, program);
154+
const module = _symbolImportLookup(source, bootstrapSymbolName, host, programManager);
152155
if (module) {
153156
return `${module.replace(/\.ts$/, '')}#${bootstrapSymbolName}`;
154157
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {dirname, join} from 'path';
22
import * as ts from 'typescript';
33

4-
import {TypeScriptFileRefactor} from './refactor';
4+
import {getTypeScriptFileRefactor} from './refactor/refactor';
5+
import {ProgramManager} from './program_manager';
56

67

78
function _getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string {
@@ -21,9 +22,9 @@ export interface LazyRouteMap {
2122

2223

2324
export function findLazyRoutes(filePath: string,
24-
program: ts.Program,
25+
programManager: ProgramManager,
2526
host: ts.CompilerHost): LazyRouteMap {
26-
const refactor = new TypeScriptFileRefactor(filePath, host, program);
27+
const refactor = getTypeScriptFileRefactor(filePath, host, programManager);
2728

2829
return refactor
2930
// Find all object literals in the file.
@@ -50,7 +51,8 @@ export function findLazyRoutes(filePath: string,
5051
? ({
5152
resolvedModule: { resolvedFileName: join(dirname(filePath), moduleName) + '.ts' }
5253
} as any)
53-
: ts.resolveModuleName(moduleName, filePath, program.getCompilerOptions(), host);
54+
: ts.resolveModuleName(
55+
moduleName, filePath, programManager.program.getCompilerOptions(), host);
5456
if (resolvedModuleName.resolvedModule
5557
&& resolvedModuleName.resolvedModule.resolvedFileName
5658
&& host.fileExists(resolvedModuleName.resolvedModule.resolvedFileName)) {

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

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import * as ts from 'typescript';
21
import {removeModuleIdOnlyForTesting} from './loader';
32
import {WebpackCompilerHost} from './compiler_host';
4-
import {TypeScriptFileRefactor} from './refactor';
3+
import {getTypeScriptFileRefactor} from './refactor/refactor';
4+
import {ProgramManager} from './program_manager';
55

66
describe('@ngtools/webpack', () => {
77
describe('loader', () => {
@@ -20,25 +20,28 @@ describe('@ngtools/webpack', () => {
2020
@SomeDecorator({ otherValue4: 4, moduleId: 123 }) class CLS4 {}
2121
`, false);
2222

23-
const program = ts.createProgram(['/file.ts', '/file2.ts'], {}, host);
23+
const programManager = new ProgramManager(['/file.ts', '/file2.ts'], {}, host);
2424

25-
const refactor = new TypeScriptFileRefactor('/file.ts', host, program);
25+
const refactor = getTypeScriptFileRefactor('/file.ts', host, programManager);
2626
removeModuleIdOnlyForTesting(refactor);
27-
expect(refactor.sourceText).not.toMatch(/obj = \{\s+};/);
28-
expect(refactor.sourceText).not.toMatch(/\{\s*otherValue: 1\s*};/);
2927

30-
const refactor2 = new TypeScriptFileRefactor('/file2.ts', host, program);
28+
const outputText = refactor.transpile().outputText;
29+
expect(outputText).not.toMatch(/obj = \{\s+};/);
30+
expect(outputText).not.toMatch(/\{\s*otherValue: 1\s*};/);
31+
32+
const refactor2 = getTypeScriptFileRefactor('/file2.ts', host, programManager);
3133
removeModuleIdOnlyForTesting(refactor2);
32-
expect(refactor2.sourceText).toMatch(/\(\{\s+}\)/);
33-
expect(refactor2.sourceText).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
34-
expect(refactor2.sourceText).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
35-
expect(refactor2.sourceText).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
34+
const outputText2 = refactor2.transpile().outputText;
35+
expect(outputText2).toMatch(/\(\{\s*}\)/);
36+
expect(outputText2).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
37+
expect(outputText2).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
38+
expect(outputText2).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
3639
});
3740

3841
it('should work without a root name', () => {
3942
const host = new WebpackCompilerHost({}, '');
4043
host.writeFile('/file.ts', `
41-
import './file2.ts';
44+
import './file2';
4245
`, false);
4346
host.writeFile('/file2.ts', `
4447
@SomeDecorator({ moduleId: 123 }) class CLS {}
@@ -47,13 +50,14 @@ describe('@ngtools/webpack', () => {
4750
@SomeDecorator({ otherValue4: 4, moduleId: 123 }) class CLS4 {}
4851
`, false);
4952

50-
const program = ts.createProgram(['/file.ts'], {}, host);
51-
const refactor = new TypeScriptFileRefactor('/file2.ts', host, program);
53+
const programManager = new ProgramManager(['/file.ts'], {}, host);
54+
const refactor = getTypeScriptFileRefactor('/file2.ts', host, programManager);
5255
removeModuleIdOnlyForTesting(refactor);
53-
expect(refactor.sourceText).toMatch(/\(\{\s+}\)/);
54-
expect(refactor.sourceText).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
55-
expect(refactor.sourceText).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
56-
expect(refactor.sourceText).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
56+
const outputText = refactor.transpile().outputText;
57+
expect(outputText).toMatch(/\(\{\s*}\)/);
58+
expect(outputText).toMatch(/\(\{\s*otherValue1: 1\s*}\)/);
59+
expect(outputText).toMatch(/\(\{\s*otherValue2: 2\s*,\s*otherValue3: 3\s*}\)/);
60+
expect(outputText).toMatch(/\(\{\s*otherValue4: 4\s*}\)/);
5761
});
5862
});
5963
});

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

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'path';
22
import * as ts from 'typescript';
33
import {AotPlugin} from './plugin';
4-
import {TypeScriptFileRefactor} from './refactor';
4+
import {getTypeScriptFileRefactor, TypeScriptFileRefactor} from './refactor/refactor';
55
import {LoaderContext, ModuleReason} from './webpack';
66

77
interface Platform {
@@ -150,7 +150,11 @@ function _addCtorParameters(classNode: ts.ClassDeclaration,
150150
});
151151

152152
const ctorParametersDecl = `static ctorParameters() { return [ ${params.join(', ')} ]; }`;
153-
refactor.prependBefore(classNode.getLastToken(refactor.sourceFile), ctorParametersDecl);
153+
// ts.createIdentifier() with complex declarations is a hack to not have to manually
154+
// create the node with TS primitives.
155+
// TODO: replace with primitives over time.
156+
const ctorParametersNode = ts.createIdentifier(ctorParametersDecl);
157+
refactor.prependNode(classNode.getLastToken(refactor.sourceFile), ctorParametersNode);
154158
}
155159

156160

@@ -214,7 +218,7 @@ function _replacePlatform(
214218
const platform = changeMap[(call.expression as ts.Identifier).text];
215219

216220
// Replace with mapped replacement
217-
refactor.replaceNode(call.expression, platform.name);
221+
refactor.replaceNode(call.expression, ts.createIdentifier(platform.name));
218222

219223
// Add the appropriate import
220224
refactor.insertImport(platform.name, platform.importLocation);
@@ -247,7 +251,7 @@ function _replaceBootstrapOrRender(refactor: TypeScriptFileRefactor, call: ts.Ca
247251
}
248252

249253
if (identifier && identifier.text === replacementTarget) {
250-
refactor.replaceNode(identifier, replacementTarget + 'Factory');
254+
refactor.replaceNode(identifier, ts.createIdentifier(replacementTarget + 'Factory'));
251255
}
252256
}
253257

@@ -280,7 +284,7 @@ function _replaceEntryModule(plugin: AotPlugin, refactor: TypeScriptFileRefactor
280284

281285
modules
282286
.forEach(reference => {
283-
refactor.replaceNode(reference, factoryClassName);
287+
refactor.replaceNode(reference, ts.createIdentifier(factoryClassName));
284288
const caller = _getCaller(reference);
285289
_replaceBootstrapOrRender(refactor, caller);
286290
});
@@ -325,7 +329,7 @@ function _removeModuleId(refactor: TypeScriptFileRefactor) {
325329
// Get the trailing comma.
326330
const moduleIdCommaProp = moduleIdProp.parent
327331
? moduleIdProp.parent.getChildAt(1).getChildren()[1] : null;
328-
refactor.removeNodes(moduleIdProp, moduleIdCommaProp);
332+
refactor.removeNodes([moduleIdProp, moduleIdCommaProp]);
329333
});
330334
}
331335

@@ -349,8 +353,9 @@ function _replaceResources(refactor: TypeScriptFileRefactor): void {
349353
const key = _getContentOfKeyLiteral(sourceFile, node.name);
350354

351355
if (key == 'templateUrl') {
352-
refactor.replaceNode(node,
353-
`template: require(${_getResourceRequest(node.initializer, sourceFile)})`);
356+
refactor.replaceNode(node, ts.createIdentifier(
357+
`template: require(${_getResourceRequest(node.initializer, sourceFile)})`
358+
));
354359
} else if (key == 'styleUrls') {
355360
const arr = <ts.ArrayLiteralExpression[]>(
356361
refactor.findAstNodes(node, ts.SyntaxKind.ArrayLiteralExpression, false));
@@ -361,7 +366,9 @@ function _replaceResources(refactor: TypeScriptFileRefactor): void {
361366
const initializer = arr[0].elements.map((element: ts.Expression) => {
362367
return _getResourceRequest(element, sourceFile);
363368
});
364-
refactor.replaceNode(node, `styles: [require(${initializer.join('), require(')})]`);
369+
refactor.replaceNode(node, ts.createIdentifier(
370+
`styles: [require(${initializer.join('), require(')})]`
371+
));
365372
}
366373
});
367374
}
@@ -460,7 +467,7 @@ export function _replaceExport(plugin: AotPlugin, refactor: TypeScriptFileRefact
460467
const factoryPath = _getNgFactoryPath(plugin, refactor);
461468
const factoryClassName = plugin.entryModule.className + 'NgFactory';
462469
const exportStatement = `export \{ ${factoryClassName} \} from '${factoryPath}'`;
463-
refactor.appendAfter(node, exportStatement);
470+
refactor.appendNode(node, ts.createIdentifier(exportStatement));
464471
});
465472
}
466473

@@ -497,15 +504,19 @@ export function _exportModuleMap(plugin: AotPlugin, refactor: TypeScriptFileRefa
497504

498505
modules.forEach((module, index) => {
499506
const relativePath = path.relative(dirName, module.modulePath).replace(/\\/g, '/');
500-
refactor.prependBefore(node, `import * as __lazy_${index}__ from './${relativePath}'`);
507+
refactor.prependNode(node, ts.createIdentifier(
508+
`import * as __lazy_${index}__ from './${relativePath}'`
509+
));
501510
});
502511

503512
const jsonContent: string = modules
504513
.map((module, index) =>
505514
`"${module.loadChildrenString}": __lazy_${index}__.${module.moduleName}`)
506515
.join();
507516

508-
refactor.appendAfter(node, `export const LAZY_MODULE_MAP = {${jsonContent}};`);
517+
refactor.prependNode(node, ts.createIdentifier(
518+
`export const LAZY_MODULE_MAP = {${jsonContent}};`
519+
));
509520
});
510521
}
511522

@@ -531,8 +542,8 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
531542
// extra TS loaders and there's no need to do any trickery.
532543
source = null;
533544
}
534-
const refactor = new TypeScriptFileRefactor(
535-
sourceFileName, plugin.compilerHost, plugin.program, source);
545+
const refactor = getTypeScriptFileRefactor(
546+
sourceFileName, plugin.compilerHost, plugin.programManager, source);
536547

537548
Promise.resolve()
538549
.then(() => {
@@ -621,7 +632,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
621632
compilerOptions[key] = options[key];
622633
}
623634
const compilerHost = ts.createCompilerHost(compilerOptions);
624-
const refactor = new TypeScriptFileRefactor(sourceFileName, compilerHost);
635+
const refactor = getTypeScriptFileRefactor(sourceFileName, compilerHost);
625636
_replaceResources(refactor);
626637

627638
const result = refactor.transpile(compilerOptions);

0 commit comments

Comments
 (0)