Skip to content

Commit b6d693f

Browse files
clydinalan-agius4
authored andcommitted
refactor(@ngtools/webpack): reduce complexity of resource loader processing
The resource loader used to process component styles and templates has been changed to be fully typed and to leverage more of Webpack's child compilation APIs. The later of which removes the need to manually propagate most information from the child compilation to the parent compilation.
1 parent 1b89617 commit b6d693f

File tree

1 file changed

+82
-71
lines changed

1 file changed

+82
-71
lines changed

packages/ngtools/webpack/src/resource_loader.ts

Lines changed: 82 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
// TODO: fix typings.
9-
// tslint:disable-next-line:no-global-tslint-disable
10-
// tslint:disable:no-any
11-
import * as path from 'path';
128
import * as vm from 'vm';
9+
import { Compiler, compilation } from 'webpack';
1310
import { RawSource } from 'webpack-sources';
1411
import { normalizePath } from './ivy/paths';
1512

@@ -18,26 +15,43 @@ const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
1815
const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
1916
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
2017

18+
type WebpackCompilation = compilation.Compilation & {
19+
createChildCompiler(
20+
name: string,
21+
outputOptions: {},
22+
plugins: ((compiler: Compiler) => void)[],
23+
): WebpackCompiler;
24+
};
25+
26+
type WebpackCompiler = Compiler & {
27+
runAsChild(
28+
callback: (
29+
error?: Error,
30+
entries?: unknown,
31+
compilation?: compilation.Compilation,
32+
) => void,
33+
): void;
34+
};
2135

2236
interface CompilationOutput {
23-
outputName: string;
24-
source: string;
37+
content: string;
38+
map?: string;
2539
success: boolean;
2640
}
2741

2842
export class WebpackResourceLoader {
29-
private _parentCompilation: any;
43+
private _parentCompilation?: WebpackCompilation;
3044
private _fileDependencies = new Map<string, Set<string>>();
3145
private _reverseDependencies = new Map<string, Set<string>>();
3246

33-
private cache = new Map<string, string>();
47+
private cache = new Map<string, CompilationOutput>();
3448
private modifiedResources = new Set<string>();
3549

3650
update(
37-
parentCompilation: import('webpack').compilation.Compilation,
51+
parentCompilation: compilation.Compilation,
3852
changedFiles?: Iterable<string>,
3953
) {
40-
this._parentCompilation = parentCompilation;
54+
this._parentCompilation = parentCompilation as WebpackCompilation;
4155

4256
// Update resource cache and modified resources
4357
this.modifiedResources.clear();
@@ -82,28 +96,32 @@ export class WebpackResourceLoader {
8296
}
8397

8498
const outputOptions = { filename: filePath };
85-
const context = this._parentCompilation.context;
86-
const relativePath = path.relative(context || '', filePath);
87-
const childCompiler = this._parentCompilation.createChildCompiler(relativePath, outputOptions);
88-
childCompiler.context = context;
89-
90-
new NodeTemplatePlugin(outputOptions).apply(childCompiler);
91-
new NodeTargetPlugin().apply(childCompiler);
92-
new SingleEntryPlugin(context, filePath).apply(childCompiler);
93-
new LibraryTemplatePlugin('resource', 'var').apply(childCompiler);
94-
95-
childCompiler.hooks.thisCompilation.tap('ngtools-webpack', (compilation: any) => {
96-
compilation.hooks.additionalAssets.tap('ngtools-webpack', () => {
99+
const context = this._parentCompilation.compiler.context;
100+
const childCompiler = this._parentCompilation.createChildCompiler(
101+
'angular-compiler:resource',
102+
outputOptions,
103+
[
104+
new NodeTemplatePlugin(outputOptions),
105+
new NodeTargetPlugin(),
106+
new SingleEntryPlugin(context, filePath, 'resource'),
107+
new LibraryTemplatePlugin('resource', 'var'),
108+
],
109+
);
110+
111+
childCompiler.hooks.thisCompilation.tap('angular-compiler', (compilation) => {
112+
compilation.hooks.additionalAssets.tap('angular-compiler', () => {
97113
const asset = compilation.assets[filePath];
98114
if (!asset) {
99115
return;
100116
}
101117

102118
try {
103-
const output = this._evaluate(filePath, asset.source());
119+
const output = this._evaluate(filePath, asset.source().toString());
104120

105121
if (typeof output === 'string') {
106-
compilation.assets[filePath] = new RawSource(output);
122+
// `webpack-sources` package has incomplete typings
123+
// tslint:disable-next-line: no-any
124+
compilation.assets[filePath] = new RawSource(output) as any;
107125
}
108126
} catch (error) {
109127
// Use compilation errors, as otherwise webpack will choke
@@ -112,53 +130,47 @@ export class WebpackResourceLoader {
112130
});
113131
});
114132

115-
// Compile and return a promise
116-
const childCompilation = await new Promise<any>((resolve, reject) => {
117-
childCompiler.compile((err: Error, childCompilation: any) => {
118-
if (err) {
119-
reject(err);
120-
} else {
121-
resolve(childCompilation);
122-
}
123-
});
133+
let finalContent: string | undefined;
134+
let finalMap: string | undefined;
135+
childCompiler.hooks.afterCompile.tap('angular-compiler', (childCompilation) => {
136+
finalContent = childCompilation.assets[filePath]?.source().toString();
137+
finalMap = childCompilation.assets[filePath + '.map']?.source().toString();
138+
139+
delete childCompilation.assets[filePath];
140+
delete childCompilation.assets[filePath + '.map'];
124141
});
125142

126-
// Propagate warnings to parent compilation.
127-
const { warnings, errors } = childCompilation;
128-
if (warnings && warnings.length) {
129-
this._parentCompilation.warnings.push(...warnings);
130-
}
131-
if (errors && errors.length) {
132-
this._parentCompilation.errors.push(...errors);
133-
}
143+
return new Promise<CompilationOutput>((resolve, reject) => {
144+
childCompiler.runAsChild((error, _, childCompilation) => {
145+
if (error) {
146+
reject(error);
134147

135-
Object.keys(childCompilation.assets).forEach((assetName) => {
136-
// Add all new assets to the parent compilation, with the exception of
137-
// the file we're loading and its sourcemap.
138-
if (
139-
assetName !== filePath &&
140-
assetName !== `${filePath}.map` &&
141-
this._parentCompilation.assets[assetName] == undefined
142-
) {
143-
this._parentCompilation.assets[assetName] = childCompilation.assets[assetName];
144-
}
145-
});
148+
return;
149+
} else if (!childCompilation) {
150+
reject(new Error('Unknown child compilation error'));
146151

147-
// Save the dependencies for this resource.
148-
this._fileDependencies.set(filePath, new Set(childCompilation.fileDependencies));
149-
for (const file of childCompilation.fileDependencies) {
150-
const resolvedFile = normalizePath(file);
151-
const entry = this._reverseDependencies.get(resolvedFile);
152-
if (entry) {
153-
entry.add(filePath);
154-
} else {
155-
this._reverseDependencies.set(resolvedFile, new Set([filePath]));
156-
}
157-
}
152+
return;
153+
}
158154

159-
const finalOutput = childCompilation.assets[filePath]?.source();
155+
// Save the dependencies for this resource.
156+
this._fileDependencies.set(filePath, new Set(childCompilation.fileDependencies));
157+
for (const file of childCompilation.fileDependencies) {
158+
const resolvedFile = normalizePath(file);
159+
const entry = this._reverseDependencies.get(resolvedFile);
160+
if (entry) {
161+
entry.add(filePath);
162+
} else {
163+
this._reverseDependencies.set(resolvedFile, new Set([filePath]));
164+
}
165+
}
160166

161-
return { outputName: filePath, source: finalOutput ?? '', success: !errors?.length };
167+
resolve({
168+
content: finalContent ?? '',
169+
map: finalMap,
170+
success: childCompilation.errors?.length === 0,
171+
});
172+
});
173+
});
162174
}
163175

164176
private _evaluate(filename: string, source: string): string | null {
@@ -183,19 +195,18 @@ export class WebpackResourceLoader {
183195

184196
async get(filePath: string): Promise<string> {
185197
const normalizedFile = normalizePath(filePath);
186-
let data = this.cache.get(normalizedFile);
198+
let compilationResult = this.cache.get(normalizedFile);
187199

188-
if (data === undefined) {
200+
if (compilationResult === undefined) {
189201
// cache miss so compile resource
190-
const compilationResult = await this._compile(filePath);
191-
data = compilationResult.source;
202+
compilationResult = await this._compile(filePath);
192203

193204
// Only cache if compilation was successful
194205
if (compilationResult.success) {
195-
this.cache.set(normalizedFile, data);
206+
this.cache.set(normalizedFile, compilationResult);
196207
}
197208
}
198209

199-
return data;
210+
return compilationResult.content;
200211
}
201212
}

0 commit comments

Comments
 (0)