Skip to content

Commit 5e0073a

Browse files
filipesilvaZhicheng Wang
authored and
Zhicheng Wang
committed
fix(@ngtools/webpack): allow multiple transforms on the same file
Fix angular#8234 Fix angular#8207
1 parent a99009c commit 5e0073a

19 files changed

+771
-513
lines changed

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

+72-73
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import {
1818
} from './virtual_file_system_decorator';
1919
import { resolveEntryModuleFromMain } from './entry_resolver';
2020
import {
21-
TransformOperation,
22-
makeTransform,
2321
replaceBootstrap,
2422
exportNgFactory,
2523
exportLazyModuleMap,
@@ -41,15 +39,14 @@ import {
4139
CompilerOptions,
4240
CompilerHost,
4341
Diagnostics,
44-
CustomTransformers,
4542
EmitFlags,
4643
LazyRoute,
4744
createProgram,
4845
createCompilerHost,
4946
formatDiagnostics,
5047
readConfiguration,
5148
} from './ngtools_api';
52-
import { findAstNodes } from './transformers/ast_helpers';
49+
import { collectDeepNodes } from './transformers/ast_helpers';
5350

5451

5552
/**
@@ -95,8 +92,9 @@ export class AngularCompilerPlugin implements Tapable {
9592
private _lazyRoutes: LazyRouteMap = Object.create(null);
9693
private _tsConfigPath: string;
9794
private _entryModule: string;
95+
private _mainPath: string | undefined;
9896
private _basePath: string;
99-
private _transformMap: Map<string, TransformOperation[]> = new Map();
97+
private _transformers: ts.TransformerFactory<ts.SourceFile>[] = [];
10098
private _platform: PLATFORM;
10199
private _JitMode = false;
102100
private _emitSkipped = true;
@@ -128,6 +126,9 @@ export class AngularCompilerPlugin implements Tapable {
128126
get options() { return this._options; }
129127
get done() { return this._donePromise; }
130128
get entryModule() {
129+
if (!this._entryModule) {
130+
return undefined;
131+
}
131132
const splitted = this._entryModule.split('#');
132133
const path = splitted[0];
133134
const className = splitted[1] || 'default';
@@ -157,6 +158,7 @@ export class AngularCompilerPlugin implements Tapable {
157158
basePath = path.resolve(process.cwd(), options.basePath);
158159
}
159160

161+
// TODO: check if we can get this from readConfiguration
160162
this._basePath = basePath;
161163

162164
// Parse the tsconfig contents.
@@ -224,15 +226,6 @@ export class AngularCompilerPlugin implements Tapable {
224226
options.missingTranslation as 'error' | 'warning' | 'ignore';
225227
}
226228

227-
// Use entryModule if available in options, otherwise resolve it from mainPath after program
228-
// creation.
229-
if (this._options.entryModule) {
230-
this._entryModule = this._options.entryModule;
231-
} else if (this._compilerOptions.entryModule) {
232-
this._entryModule = path.resolve(this._basePath,
233-
this._compilerOptions.entryModule);
234-
}
235-
236229
// Create the webpack compiler host.
237230
const webpackCompilerHost = new WebpackCompilerHost(this._compilerOptions, this._basePath);
238231
webpackCompilerHost.enableCaching();
@@ -266,8 +259,26 @@ export class AngularCompilerPlugin implements Tapable {
266259
// Use an identity function as all our paths are absolute already.
267260
this._moduleResolutionCache = ts.createModuleResolutionCache(this._basePath, x => x);
268261

262+
// Resolve mainPath if provided.
263+
if (options.mainPath) {
264+
this._mainPath = this._compilerHost.resolve(options.mainPath);
265+
}
266+
267+
// Use entryModule if available in options, otherwise resolve it from mainPath after program
268+
// creation.
269+
if (this._options.entryModule) {
270+
this._entryModule = this._options.entryModule;
271+
} else if (this._compilerOptions.entryModule) {
272+
this._entryModule = path.resolve(this._basePath,
273+
this._compilerOptions.entryModule);
274+
}
275+
269276
// Set platform.
270277
this._platform = options.platform || PLATFORM.Browser;
278+
279+
// Make transformers.
280+
this._makeTransformers();
281+
271282
timeEnd('AngularCompilerPlugin._setupOptions');
272283
}
273284

@@ -332,11 +343,10 @@ export class AngularCompilerPlugin implements Tapable {
332343
})
333344
.then(() => {
334345
// If there's still no entryModule try to resolve from mainPath.
335-
if (!this._entryModule && this._options.mainPath) {
346+
if (!this._entryModule && this._mainPath) {
336347
time('AngularCompilerPlugin._make.resolveEntryModuleFromMain');
337-
const mainPath = path.resolve(this._basePath, this._options.mainPath);
338348
this._entryModule = resolveEntryModuleFromMain(
339-
mainPath, this._compilerHost, this._getTsProgram());
349+
this._mainPath, this._compilerHost, this._getTsProgram());
340350
timeEnd('AngularCompilerPlugin._make.resolveEntryModuleFromMain');
341351
}
342352
});
@@ -633,6 +643,41 @@ export class AngularCompilerPlugin implements Tapable {
633643
});
634644
}
635645

646+
private _makeTransformers() {
647+
648+
// TODO use compilerhost.denormalize when #8210 is merged.
649+
const isAppPath = (fileName: string) =>
650+
this._rootNames.includes(fileName.replace(/\//g, path.sep));
651+
const isMainPath = (fileName: string) => fileName === this._mainPath;
652+
const getEntryModule = () => this.entryModule;
653+
const getLazyRoutes = () => this._lazyRoutes;
654+
655+
if (this._JitMode) {
656+
// Replace resources in JIT.
657+
this._transformers.push(replaceResources(isAppPath));
658+
}
659+
660+
if (this._platform === PLATFORM.Browser) {
661+
// If we have a locale, auto import the locale data file.
662+
// This transform must go before replaceBootstrap because it looks for the entry module
663+
// import, which will be replaced.
664+
if (this._compilerOptions.i18nInLocale) {
665+
this._transformers.push(registerLocaleData(isAppPath, getEntryModule,
666+
this._compilerOptions.i18nInLocale));
667+
}
668+
669+
if (!this._JitMode) {
670+
// Replace bootstrap in browser AOT.
671+
this._transformers.push(replaceBootstrap(isAppPath, getEntryModule));
672+
}
673+
} else if (this._platform === PLATFORM.Server) {
674+
this._transformers.push(exportLazyModuleMap(isMainPath, getLazyRoutes));
675+
if (!this._JitMode) {
676+
this._transformers.push(exportNgFactory(isMainPath, getEntryModule));
677+
}
678+
}
679+
}
680+
636681
private _update() {
637682
time('AngularCompilerPlugin._update');
638683
// We only want to update on TS and template changes, but all kinds of files are on this
@@ -662,7 +707,7 @@ export class AngularCompilerPlugin implements Tapable {
662707
}
663708
})
664709
.then(() => {
665-
// Build transforms, emit and report errors.
710+
// Emit and report errors.
666711

667712
// We now have the final list of changed TS files.
668713
// Go through each changed file and add transforms as needed.
@@ -677,56 +722,9 @@ export class AngularCompilerPlugin implements Tapable {
677722
return sourceFile;
678723
});
679724

680-
time('AngularCompilerPlugin._update.transformOps');
681-
sourceFiles.forEach((sf) => {
682-
const fileName = this._compilerHost.resolve(sf.fileName);
683-
let transformOps = [];
684-
685-
if (this._JitMode) {
686-
transformOps.push(...replaceResources(sf));
687-
}
688-
689-
if (this._platform === PLATFORM.Browser) {
690-
if (!this._JitMode) {
691-
transformOps.push(...replaceBootstrap(sf, this.entryModule));
692-
}
693-
694-
// If we have a locale, auto import the locale data file.
695-
if (this._compilerOptions.i18nInLocale) {
696-
transformOps.push(...registerLocaleData(
697-
sf,
698-
this.entryModule,
699-
this._compilerOptions.i18nInLocale
700-
));
701-
}
702-
} else if (this._platform === PLATFORM.Server) {
703-
if (fileName === this._compilerHost.resolve(this._options.mainPath)) {
704-
transformOps.push(...exportLazyModuleMap(sf, this._lazyRoutes));
705-
if (!this._JitMode) {
706-
transformOps.push(...exportNgFactory(sf, this.entryModule));
707-
}
708-
}
709-
}
710-
711-
// We need to keep a map of transforms for each file, to reapply on each update.
712-
this._transformMap.set(fileName, transformOps);
713-
});
714-
715-
const transformOps: TransformOperation[] = [];
716-
for (let fileTransformOps of this._transformMap.values()) {
717-
transformOps.push(...fileTransformOps);
718-
}
719-
timeEnd('AngularCompilerPlugin._update.transformOps');
720-
721-
time('AngularCompilerPlugin._update.makeTransform');
722-
const transformers: CustomTransformers = {
723-
beforeTs: transformOps.length > 0 ? [makeTransform(transformOps)] : []
724-
};
725-
timeEnd('AngularCompilerPlugin._update.makeTransform');
726-
727725
// Emit files.
728726
time('AngularCompilerPlugin._update._emit');
729-
const { emitResult, diagnostics } = this._emit(sourceFiles, transformers);
727+
const { emitResult, diagnostics } = this._emit(sourceFiles);
730728
timeEnd('AngularCompilerPlugin._update._emit');
731729

732730
// Report diagnostics.
@@ -826,7 +824,7 @@ export class AngularCompilerPlugin implements Tapable {
826824
const host = this._compilerHost;
827825
const cache = this._moduleResolutionCache;
828826

829-
const esImports = findAstNodes<ts.ImportDeclaration>(null, sourceFile,
827+
const esImports = collectDeepNodes<ts.ImportDeclaration>(sourceFile,
830828
ts.SyntaxKind.ImportDeclaration)
831829
.map(decl => {
832830
const moduleName = (decl.moduleSpecifier as ts.StringLiteral).text;
@@ -858,10 +856,7 @@ export class AngularCompilerPlugin implements Tapable {
858856
// This code mostly comes from `performCompilation` in `@angular/compiler-cli`.
859857
// It skips the program creation because we need to use `loadNgStructureAsync()`,
860858
// and uses CustomTransformers.
861-
private _emit(
862-
sourceFiles: ts.SourceFile[],
863-
customTransformers: ts.CustomTransformers & CustomTransformers
864-
) {
859+
private _emit(sourceFiles: ts.SourceFile[]) {
865860
time('AngularCompilerPlugin._emit');
866861
const program = this._program;
867862
const allDiagnostics: Diagnostics = [];
@@ -888,7 +883,7 @@ export class AngularCompilerPlugin implements Tapable {
888883
const timeLabel = `AngularCompilerPlugin._emit.ts+${sf.fileName}+.emit`;
889884
time(timeLabel);
890885
emitResult = tsProgram.emit(sf, undefined, undefined, undefined,
891-
{ before: customTransformers.beforeTs }
886+
{ before: this._transformers }
892887
);
893888
allDiagnostics.push(...emitResult.diagnostics);
894889
timeEnd(timeLabel);
@@ -923,7 +918,11 @@ export class AngularCompilerPlugin implements Tapable {
923918
time('AngularCompilerPlugin._emit.ng.emit');
924919
const extractI18n = !!this._compilerOptions.i18nOutFile;
925920
const emitFlags = extractI18n ? EmitFlags.I18nBundle : EmitFlags.Default;
926-
emitResult = angularProgram.emit({ emitFlags, customTransformers });
921+
emitResult = angularProgram.emit({
922+
emitFlags, customTransformers: {
923+
beforeTs: this._transformers
924+
}
925+
});
927926
allDiagnostics.push(...emitResult.diagnostics);
928927
if (extractI18n) {
929928
this.writeI18nOutFile();

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

+53-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,63 @@
33
import * as path from 'path';
44
import * as ts from 'typescript';
55
import {SourceMapConsumer, SourceMapGenerator} from 'source-map';
6-
import { findAstNodes } from './transformers';
76

87
const MagicString = require('magic-string');
98

109

10+
/**
11+
* Find all nodes from the AST in the subtree of node of SyntaxKind kind.
12+
* @param node The root node to check, or null if the whole tree should be searched.
13+
* @param sourceFile The source file where the node is.
14+
* @param kind The kind of nodes to find.
15+
* @param recursive Whether to go in matched nodes to keep matching.
16+
* @param max The maximum number of items to return.
17+
* @return all nodes of kind, or [] if none is found
18+
*/
19+
export function findAstNodes<T extends ts.Node>(
20+
node: ts.Node | null,
21+
sourceFile: ts.SourceFile,
22+
kind: ts.SyntaxKind,
23+
recursive = false,
24+
max = Infinity
25+
): T[] {
26+
// TODO: refactor operations that only need `refactor.findAstNodes()` to use this instead.
27+
if (max == 0) {
28+
return [];
29+
}
30+
if (!node) {
31+
node = sourceFile;
32+
}
33+
34+
let arr: T[] = [];
35+
if (node.kind === kind) {
36+
// If we're not recursively looking for children, stop here.
37+
if (!recursive) {
38+
return [node as T];
39+
}
40+
41+
arr.push(node as T);
42+
max--;
43+
}
44+
45+
if (max > 0) {
46+
for (const child of node.getChildren(sourceFile)) {
47+
findAstNodes(child, sourceFile, kind, recursive, max)
48+
.forEach((node: ts.Node) => {
49+
if (max > 0) {
50+
arr.push(node as T);
51+
}
52+
max--;
53+
});
54+
55+
if (max <= 0) {
56+
break;
57+
}
58+
}
59+
}
60+
return arr;
61+
}
62+
1163
export interface TranspileOutput {
1264
outputText: string;
1365
sourceMap: any | null;

0 commit comments

Comments
 (0)