Skip to content

Commit a97f509

Browse files
committed
feat(compiler): Use timestamps to verify cache validity
1 parent 18af83d commit a97f509

File tree

3 files changed

+111
-13
lines changed

3 files changed

+111
-13
lines changed

index.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ class HtmlWebpackPlugin {
8787
// Clear the cache once a new HtmlWebpackPlugin is added
8888
childCompiler.clearCache(compiler);
8989

90-
compiler.hooks.compile.tap('HtmlWebpackPlugin', () => {
90+
compiler.hooks.compilation.tap('HtmlWebpackPlugin', (compilation) => {
91+
if (childCompiler.hasOutDatedTemplateCache(compilation)) {
92+
childCompiler.clearCache(compiler);
93+
}
9194
childCompiler.addTemplateToCompiler(compiler, this.options.template);
9295
});
9396

@@ -101,12 +104,13 @@ class HtmlWebpackPlugin {
101104
compilation.errors.push(prettyError(err, compiler.context).toString());
102105
return {
103106
content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
104-
outputName: self.options.filename
107+
outputName: self.options.filename,
108+
hash: ''
105109
};
106110
})
107111
.then(compilationResult => {
108112
// If the compilation change didnt change the cache is valid
109-
isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash;
113+
isCompilationCached = Boolean(compilationResult.hash) && self.childCompilerHash === compilationResult.hash;
110114
self.childCompilerHash = compilationResult.hash;
111115
self.childCompilationOutputName = compilationResult.outputName;
112116
callback();
@@ -121,9 +125,6 @@ class HtmlWebpackPlugin {
121125
* @param {() => void} callback
122126
*/
123127
(compilation, callback) => {
124-
// Clear the childCompilerCache
125-
childCompiler.clearCache(compiler);
126-
127128
// Get all entry point names for this html file
128129
const entryNames = Array.from(compilation.entrypoints.keys());
129130
const filteredEntryNames = self.filterChunks(entryNames, self.options.chunks, self.options.excludeChunks);

lib/compiler.js

+60-7
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@ class HtmlWebpackChildCompiler {
3434
*/
3535
this.compilationPromise;
3636
/**
37-
* @type {Date}
37+
* @type {number}
3838
*/
39-
this.compilationStarted;
39+
this.compilationStartedTimestamp;
4040
/**
4141
* All file dependencies of the child compiler
4242
* @type {string[]}
4343
*/
4444
this.fileDependencies = [];
45+
/**
46+
* Store if the cache was already verified for the given compilation
47+
* @type {WeakMap<WebpackCompilation, boolean>}}
48+
*/
49+
this.cacheVerifiedForCompilation = new WeakMap();
4550
}
4651

4752
/**
@@ -63,6 +68,7 @@ class HtmlWebpackChildCompiler {
6368
// Add the template to the childCompiler
6469
const newTemplateId = this.templates.length;
6570
this.templates.push(template);
71+
// Mark the cache invalid
6672
return newTemplateId;
6773
}
6874

@@ -117,7 +123,7 @@ class HtmlWebpackChildCompiler {
117123
new SingleEntryPlugin(childCompiler.context, template, `HtmlWebpackPlugin_${index}`).apply(childCompiler);
118124
});
119125

120-
this.compilationStarted = new Date();
126+
this.compilationStartedTimestamp = new Date().getTime();
121127
this.compilationPromise = new Promise((resolve, reject) => {
122128
childCompiler.runAsChild((err, entries, childCompilation) => {
123129
// Extract templates
@@ -159,6 +165,37 @@ class HtmlWebpackChildCompiler {
159165

160166
return this.compilationPromise;
161167
}
168+
169+
/**
170+
* Returns `false` if any template file depenendencies has changed
171+
* for the given main compilation
172+
*
173+
* @param {WebpackCompilation} mainCompilation
174+
* @returns {boolean}
175+
*/
176+
hasOutDatedTemplateCache (mainCompilation) {
177+
// Check if cache validation was already computed
178+
const isCacheValid = this.cacheVerifiedForCompilation.get(mainCompilation);
179+
if (isCacheValid !== undefined) {
180+
return isCacheValid;
181+
}
182+
// If the compilation was never run there is no invalid cache
183+
if (!this.compilationStartedTimestamp) {
184+
this.cacheVerifiedForCompilation.set(mainCompilation, false);
185+
return false;
186+
}
187+
// Check if any dependent file was changed after the last compilation
188+
const fileTimestamps = mainCompilation.fileTimestamps;
189+
const isCacheOutOfDate = this.fileDependencies.some((fileDependency) => {
190+
const timestamp = fileTimestamps.get(fileDependency);
191+
// If the timestamp is not known the file is new
192+
// If the timestamp is larger then the file has changed
193+
// Otherwise the file is still the same
194+
return !timestamp || timestamp > this.compilationStartedTimestamp;
195+
});
196+
this.cacheVerifiedForCompilation.set(mainCompilation, isCacheOutOfDate);
197+
return isCacheOutOfDate;
198+
}
162199
}
163200

164201
/**
@@ -215,10 +252,13 @@ const childCompilerCache = new WeakMap();
215252
* @param {WebpackCompiler} mainCompiler
216253
*/
217254
function getChildCompiler (mainCompiler) {
218-
if (!childCompilerCache[mainCompiler]) {
219-
childCompilerCache[mainCompiler] = new HtmlWebpackChildCompiler();
255+
const cachedChildCompiler = childCompilerCache.get(mainCompiler);
256+
if (cachedChildCompiler) {
257+
return cachedChildCompiler;
220258
}
221-
return childCompilerCache[mainCompiler];
259+
const newCompiler = new HtmlWebpackChildCompiler();
260+
childCompilerCache.set(mainCompiler, newCompiler);
261+
return newCompiler;
222262
}
223263

224264
/**
@@ -227,7 +267,7 @@ function getChildCompiler (mainCompiler) {
227267
* @param {WebpackCompiler} mainCompiler
228268
*/
229269
function clearCache (mainCompiler) {
230-
delete (childCompilerCache[mainCompiler]);
270+
childCompilerCache.delete(mainCompiler);
231271
}
232272

233273
/**
@@ -253,6 +293,7 @@ function addTemplateToCompiler (mainCompiler, templatePath) {
253293
function compileTemplate (templatePath, outputFilename, mainCompilation) {
254294
const childCompiler = getChildCompiler(mainCompilation.compiler);
255295
return childCompiler.compileTemplates(mainCompilation).then((compiledTemplates) => {
296+
if (!compiledTemplates[templatePath]) console.log(Object.keys(compiledTemplates), templatePath);
256297
const compiledTemplate = compiledTemplates[templatePath];
257298
// Replace [hash] placeholders in filename
258299
const outputName = mainCompilation.mainTemplate.hooks.assetPath.call(outputFilename, {
@@ -270,8 +311,20 @@ function compileTemplate (templatePath, outputFilename, mainCompilation) {
270311
});
271312
}
272313

314+
/**
315+
* Returns false if the cache is not valid anymore
316+
*
317+
* @param {WebpackCompilation} compilation
318+
* @returns {boolean}
319+
*/
320+
function hasOutDatedTemplateCache (compilation) {
321+
const childCompiler = childCompilerCache.get(compilation.compiler);
322+
return childCompiler ? childCompiler.hasOutDatedTemplateCache(compilation) : false;
323+
}
324+
273325
module.exports = {
274326
addTemplateToCompiler,
275327
compileTemplate,
328+
hasOutDatedTemplateCache,
276329
clearCache
277330
};

spec/CachingSpec.js

+44
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,48 @@ describe('HtmlWebpackPluginCaching', function () {
202202
})
203203
.then(done);
204204
});
205+
206+
it('should keep watching the webpack html if only a js file was changed', function (done) {
207+
var template = path.join(__dirname, 'fixtures/plain.html');
208+
const jsFile = path.join(__dirname, 'fixtures/index.js');
209+
var htmlWebpackPlugin = new HtmlWebpackPlugin({
210+
template: template
211+
});
212+
var compiler = setUpCompiler(htmlWebpackPlugin);
213+
compiler.addTestFile(template);
214+
compiler.addTestFile(jsFile);
215+
// Build the template file for the first time
216+
compiler.startWatching()
217+
// Change the template file (second build)
218+
.then(() => {
219+
compiler.simulateFileChange(template, {footer: '<!-- 1 -->'});
220+
return compiler.waitForWatchRunComplete();
221+
})
222+
// Change js
223+
.then(() => {
224+
compiler.simulateFileChange(jsFile, {footer: '// 1'});
225+
return compiler.waitForWatchRunComplete();
226+
})
227+
// Change js
228+
.then(() => {
229+
compiler.simulateFileChange(jsFile, {footer: '// 2'});
230+
return compiler.waitForWatchRunComplete();
231+
})
232+
// Change js
233+
.then(() => {
234+
compiler.simulateFileChange(jsFile, {footer: '// 3'});
235+
return compiler.waitForWatchRunComplete();
236+
})
237+
// Change the template file (third build)
238+
.then(() => {
239+
compiler.simulateFileChange(template, {footer: '<!-- 2 -->'});
240+
return compiler.waitForWatchRunComplete();
241+
})
242+
.then(() => {
243+
// Verify that the html was processed trice
244+
expect(htmlWebpackPlugin.evaluateCompilationResult.calls.count())
245+
.toBe(3);
246+
})
247+
.then(done);
248+
});
205249
});

0 commit comments

Comments
 (0)