Skip to content

Commit 1cd9594

Browse files
committed
refactor(@ngtools/webpack): use emit when available
1 parent 694deb9 commit 1cd9594

File tree

12 files changed

+578
-159
lines changed

12 files changed

+578
-159
lines changed

packages/@angular/cli/models/webpack-configs/typescript.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) {
7070
locale: buildOptions.locale,
7171
replaceExport: appConfig.platform === 'server',
7272
hostReplacementPaths,
73+
sourceMap: buildOptions.sourcemaps,
7374
// If we don't explicitely list excludes, it will default to `['**/*.spec.ts']`.
7475
exclude: []
7576
}, options));

packages/@ngtools/webpack/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ The loader works with the webpack plugin to compile your TypeScript. It's import
3838
* `skipCodeGeneration`. Optional, defaults to false. Disable code generation and do not refactor the code to bootstrap. This replaces `templateUrl: "string"` with `template: require("string")` (and similar for styles) to allow for webpack to properly link the resources.
3939
* `typeChecking`. Optional, defaults to true. Enable type checking through your application. This will slow down compilation, but show syntactic and semantic errors in webpack.
4040
* `exclude`. Optional. Extra files to exclude from TypeScript compilation.
41+
* `sourceMap`. Optional. Include sourcemaps.
4142
* `compilerOptions`. Optional. Override options in `tsconfig.json`.
4243

4344
## Features

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,12 @@ 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+
getChangedFilePaths(tsOnly = true): string[] {
219+
// Only get changed ts files by default.
220+
// That's what we mostly care about and want to transpile, but all kinds of files are on this
221+
// list, like package.json and .ngsummary.json files.
222+
return Object.keys(this._changedFiles).filter(k => !tsOnly || k.endsWith('.ts'));
219223
}
220224

221225
invalidate(fileName: string): void {

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import {join} from 'path';
33
import * as ts from 'typescript';
44

55
import {TypeScriptFileRefactor} from './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 = new TypeScriptFileRefactor(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 = new TypeScriptFileRefactor(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 = new TypeScriptFileRefactor(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 = new TypeScriptFileRefactor(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 = new TypeScriptFileRefactor(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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {dirname, join} from 'path';
22
import * as ts from 'typescript';
33

44
import {TypeScriptFileRefactor} from './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 = new TypeScriptFileRefactor(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: 21 additions & 17 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';
43
import {TypeScriptFileRefactor} from './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 = new TypeScriptFileRefactor('/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 = new TypeScriptFileRefactor('/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 = new TypeScriptFileRefactor('/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: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ function _addCtorParameters(classNode: ts.ClassDeclaration,
150150
});
151151

152152
const ctorParametersDecl = `static ctorParameters() { return [ ${params.join(', ')} ]; }`;
153-
refactor.prependBefore(classNode.getLastToken(refactor.sourceFile), ctorParametersDecl);
153+
refactor.prependNode(classNode.getLastToken(refactor.sourceFile), ctorParametersDecl);
154154
}
155155

156156

@@ -342,6 +342,7 @@ function _getResourceRequest(element: ts.Expression, sourceFile: ts.SourceFile)
342342

343343
function _replaceResources(refactor: TypeScriptFileRefactor): void {
344344
const sourceFile = refactor.sourceFile;
345+
let refactored = false;
345346

346347
_getResourceNodes(refactor)
347348
// Get the full text of the initializer.
@@ -351,9 +352,11 @@ function _replaceResources(refactor: TypeScriptFileRefactor): void {
351352
if (key == 'templateUrl') {
352353
refactor.replaceNode(node,
353354
`template: require(${_getResourceRequest(node.initializer, sourceFile)})`);
355+
refactored = true;
354356
} else if (key == 'styleUrls') {
355357
const arr = <ts.ArrayLiteralExpression[]>(
356358
refactor.findAstNodes(node, ts.SyntaxKind.ArrayLiteralExpression, false));
359+
refactored = true;
357360
if (!arr || arr.length == 0 || arr[0].elements.length == 0) {
358361
return;
359362
}
@@ -362,8 +365,17 @@ function _replaceResources(refactor: TypeScriptFileRefactor): void {
362365
return _getResourceRequest(element, sourceFile);
363366
});
364367
refactor.replaceNode(node, `styles: [require(${initializer.join('), require(')})]`);
368+
refactored = true;
365369
}
366370
});
371+
372+
if (refactored) {
373+
// If we added a require call, we need to also add typings for it.
374+
// The typings need to be compatible with node typings, but also work by themselves.
375+
refactor.prependNode(refactor.getFirstNode(),
376+
'declare var require: NodeRequire;interface NodeRequire {(id: string): any;}'
377+
);
378+
}
367379
}
368380

369381

@@ -460,7 +472,7 @@ export function _replaceExport(plugin: AotPlugin, refactor: TypeScriptFileRefact
460472
const factoryPath = _getNgFactoryPath(plugin, refactor);
461473
const factoryClassName = plugin.entryModule.className + 'NgFactory';
462474
const exportStatement = `export \{ ${factoryClassName} \} from '${factoryPath}'`;
463-
refactor.appendAfter(node, exportStatement);
475+
refactor.appendNode(node, exportStatement);
464476
});
465477
}
466478

@@ -497,15 +509,15 @@ export function _exportModuleMap(plugin: AotPlugin, refactor: TypeScriptFileRefa
497509

498510
modules.forEach((module, index) => {
499511
const relativePath = path.relative(dirName, module.modulePath).replace(/\\/g, '/');
500-
refactor.prependBefore(node, `import * as __lazy_${index}__ from './${relativePath}'`);
512+
refactor.prependNode(node, `import * as __lazy_${index}__ from './${relativePath}'`);
501513
});
502514

503515
const jsonContent: string = modules
504516
.map((module, index) =>
505517
`"${module.loadChildrenString}": __lazy_${index}__.${module.moduleName}`)
506518
.join();
507519

508-
refactor.appendAfter(node, `export const LAZY_MODULE_MAP = {${jsonContent}};`);
520+
refactor.appendNode(node, `export const LAZY_MODULE_MAP = {${jsonContent}};`);
509521
});
510522
}
511523

@@ -532,7 +544,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
532544
source = null;
533545
}
534546
const refactor = new TypeScriptFileRefactor(
535-
sourceFileName, plugin.compilerHost, plugin.program, source);
547+
sourceFileName, plugin.compilerHost, plugin.programManager, source);
536548

537549
Promise.resolve()
538550
.then(() => {
@@ -589,14 +601,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
589601
}
590602
}
591603

592-
// Force a few compiler options to make sure we get the result we want.
593-
const compilerOptions: ts.CompilerOptions = Object.assign({}, plugin.compilerOptions, {
594-
inlineSources: true,
595-
inlineSourceMap: false,
596-
sourceRoot: plugin.basePath
597-
});
598-
599-
const result = refactor.transpile(compilerOptions);
604+
const result = refactor.transpile();
600605
cb(null, result.outputText, result.sourceMap);
601606
})
602607
.catch(err => cb(err));

0 commit comments

Comments
 (0)