Skip to content

Commit 18bccf5

Browse files
filipesilvadond2clouds
authored andcommitted
refactor(@ngtools/webpack): use cache for resource loader
1 parent 3671e9a commit 18bccf5

File tree

4 files changed

+67
-24
lines changed

4 files changed

+67
-24
lines changed

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class AngularCompilerPlugin implements Tapable {
8686
private _program: ts.Program | Program;
8787
private _compilerHost: WebpackCompilerHost;
8888
private _angularCompilerHost: WebpackCompilerHost & CompilerHost;
89+
private _resourceLoader: WebpackResourceLoader;
8990
// Contains `moduleImportPath#exportName` => `fullModulePath`.
9091
private _lazyRoutes: LazyRouteMap = Object.create(null);
9192
private _tsConfigPath: string;
@@ -267,6 +268,10 @@ export class AngularCompilerPlugin implements Tapable {
267268
this._compilerHost = new WebpackCompilerHost(this._compilerOptions, this._basePath);
268269
this._compilerHost.enableCaching();
269270

271+
// Create and set a new WebpackResourceLoader.
272+
this._resourceLoader = new WebpackResourceLoader();
273+
this._compilerHost.setResourceLoader(this._resourceLoader);
274+
270275
// Override some files in the FileSystem.
271276
if (this._options.hostOverrideFileSystem) {
272277
for (const filePath of Object.keys(this._options.hostOverrideFileSystem)) {
@@ -283,6 +288,7 @@ export class AngularCompilerPlugin implements Tapable {
283288
}
284289
}
285290

291+
// Set platform.
286292
this._platform = options.platform || PLATFORM.Browser;
287293
timeEnd('AngularCompilerPlugin._setupOptions');
288294
}
@@ -563,13 +569,11 @@ export class AngularCompilerPlugin implements Tapable {
563569
return cb(new Error('An @ngtools/webpack plugin already exist for this compilation.'));
564570
}
565571

572+
// Set a private variable for this plugin instance.
566573
this._compilation._ngToolsWebpackPluginInstance = this;
567574

568-
// Create the resource loader with the webpack compilation.
569-
time('AngularCompilerPlugin._make.setResourceLoader');
570-
const resourceLoader = new WebpackResourceLoader(compilation);
571-
this._compilerHost.setResourceLoader(resourceLoader);
572-
timeEnd('AngularCompilerPlugin._make.setResourceLoader');
575+
// Update the resource loader with the new webpack compilation.
576+
this._resourceLoader.update(compilation);
573577

574578
this._donePromise = Promise.resolve()
575579
.then(() => {

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export class ExtractI18nPlugin implements Tapable {
136136
}
137137
this._outFile = options.outFile;
138138
}
139+
140+
this._resourceLoader = new WebpackResourceLoader();
139141
}
140142

141143
apply(compiler: any) {
@@ -164,7 +166,7 @@ export class ExtractI18nPlugin implements Tapable {
164166

165167
this._compilation._ngToolsWebpackXi18nPluginInstance = this;
166168

167-
this._resourceLoader = new WebpackResourceLoader(compilation);
169+
this._resourceLoader.update(compilation);
168170

169171
this._donePromise = Promise.resolve()
170172
.then(() => {

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ export class AotPlugin implements Tapable {
256256
// because we don't want SourceFile instances to be cached past this point.
257257
this._compilerHost.enableCaching();
258258

259+
this._resourceLoader = new WebpackResourceLoader();
260+
259261
if (options.entryModule) {
260262
this._entryModule = options.entryModule;
261263
} else if ((tsConfig.raw['angularCompilerOptions'] as any)
@@ -533,7 +535,7 @@ export class AotPlugin implements Tapable {
533535

534536
this._compilation._ngToolsWebpackPluginInstance = this;
535537

536-
this._resourceLoader = new WebpackResourceLoader(compilation);
538+
this._resourceLoader.update(compilation);
537539

538540
this._donePromise = Promise.resolve()
539541
.then(() => {

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

+52-17
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
1-
import {readFileSync} from 'fs';
21
import * as vm from 'vm';
32
import * as path from 'path';
43

54
const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
65
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
76
const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
87
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
8+
const loaderUtils = require('loader-utils');
99

1010

11+
interface CompilationOutput {
12+
hash: string;
13+
outputName: string;
14+
source: string;
15+
newSource?: string;
16+
}
1117

1218
export class WebpackResourceLoader {
19+
private _parentCompilation: any;
1320
private _context: string;
1421
private _uniqueId = 0;
22+
private _cache = new Map<string, CompilationOutput>();
23+
24+
constructor() {}
1525

16-
constructor(private _parentCompilation: any) {
17-
this._context = _parentCompilation.context;
26+
update(parentCompilation: any) {
27+
this._parentCompilation = parentCompilation;
28+
this._context = parentCompilation.context;
29+
this._uniqueId = 0;
1830
}
1931

20-
private _compile(filePath: string, _content: string): Promise<any> {
32+
private _compile(filePath: string): Promise<CompilationOutput> {
33+
34+
if (!this._parentCompilation) {
35+
throw new Error('WebpackResourceLoader cannot be used without parentCompilation');
36+
}
37+
2138
const compilerName = `compiler(${this._uniqueId++})`;
2239
const outputOptions = { filename: filePath };
2340
const relativePath = path.relative(this._context || '', filePath);
@@ -63,9 +80,9 @@ export class WebpackResourceLoader {
6380
// Replace [hash] placeholders in filename
6481
const outputName = this._parentCompilation.mainTemplate.applyPluginsWaterfall(
6582
'asset-path', outputOptions.filename, {
66-
hash: childCompilation.hash,
67-
chunk: entries[0]
68-
});
83+
hash: childCompilation.hash,
84+
chunk: entries[0]
85+
});
6986

7087
// Restore the parent compilation to the state like it was before the child compilation.
7188
Object.keys(childCompilation.assets).forEach((fileName) => {
@@ -78,40 +95,58 @@ export class WebpackResourceLoader {
7895
}
7996
});
8097

98+
const source = childCompilation.assets[outputName].source();
99+
81100
resolve({
82-
// Hash of the template entry point.
83-
hash: entries[0].hash,
101+
// Hash of the source.
102+
hash: loaderUtils.getHashDigest(source),
84103
// Output name.
85-
outputName: outputName,
104+
outputName,
86105
// Compiled code.
87-
content: childCompilation.assets[outputName].source()
106+
source
88107
});
89108
}
90109
});
91110
});
92111
}
93112

94-
private _evaluate(fileName: string, source: string): Promise<string> {
113+
private _evaluate(output: CompilationOutput): Promise<string> {
95114
try {
96-
const vmContext = vm.createContext(Object.assign({require: require}, global));
97-
const vmScript = new vm.Script(source, {filename: fileName});
115+
const vmContext = vm.createContext(Object.assign({ require: require }, global));
116+
const vmScript = new vm.Script(output.source, { filename: output.outputName });
98117

99118
// Evaluate code and cast to string
100119
let newSource: string;
101120
newSource = vmScript.runInContext(vmContext);
102121

103122
if (typeof newSource == 'string') {
123+
this._cache.set(output.outputName, { ...output, newSource });
104124
return Promise.resolve(newSource);
105125
}
106126

107-
return Promise.reject('The loader "' + fileName + '" didn\'t return a string.');
127+
return Promise.reject('The loader "' + output.outputName + '" didn\'t return a string.');
108128
} catch (e) {
109129
return Promise.reject(e);
110130
}
111131
}
112132

113133
get(filePath: string): Promise<string> {
114-
return this._compile(filePath, readFileSync(filePath, 'utf8'))
115-
.then((result: any) => this._evaluate(result.outputName, result.content));
134+
return this._compile(filePath)
135+
.then((result: any) => {
136+
// Check cache.
137+
const outputName = result.outputName;
138+
const output = this._cache.get(outputName);
139+
if (output) {
140+
if (output.hash === result.hash && output.newSource) {
141+
// Return cached newSource.
142+
return Promise.resolve(output.newSource);
143+
} else {
144+
// Delete cache entry.
145+
this._cache.delete(outputName);
146+
}
147+
}
148+
149+
return this._evaluate(result);
150+
});
116151
}
117152
}

0 commit comments

Comments
 (0)