Skip to content

Commit 0ebcd17

Browse files
committedJul 11, 2018
feat(compiler): Use timestamps to verify cache validity
1 parent dfe1d10 commit 0ebcd17

File tree

6 files changed

+116
-15
lines changed

6 files changed

+116
-15
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/node_modules/
22
/dist/
3+
/coverage/
34
npm-debug.*.log

‎index.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ class HtmlWebpackPlugin {
9797
// Clear the cache once a new HtmlWebpackPlugin is added
9898
childCompiler.clearCache(compiler);
9999

100-
compiler.hooks.compile.tap('HtmlWebpackPlugin', () => {
100+
compiler.hooks.compilation.tap('HtmlWebpackPlugin', (compilation) => {
101+
if (childCompiler.hasOutDatedTemplateCache(compilation)) {
102+
childCompiler.clearCache(compiler);
103+
}
101104
childCompiler.addTemplateToCompiler(compiler, this.options.template);
102105
});
103106

@@ -111,12 +114,13 @@ class HtmlWebpackPlugin {
111114
compilation.errors.push(prettyError(err, compiler.context).toString());
112115
return {
113116
content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
114-
outputName: self.options.filename
117+
outputName: self.options.filename,
118+
hash: ''
115119
};
116120
})
117121
.then(compilationResult => {
118122
// If the compilation change didnt change the cache is valid
119-
isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash;
123+
isCompilationCached = Boolean(compilationResult.hash) && self.childCompilerHash === compilationResult.hash;
120124
self.childCompilerHash = compilationResult.hash;
121125
self.childCompilationOutputName = compilationResult.outputName;
122126
callback();
@@ -131,9 +135,6 @@ class HtmlWebpackPlugin {
131135
* @param {() => void} callback
132136
*/
133137
(compilation, callback) => {
134-
// Clear the childCompilerCache
135-
childCompiler.clearCache(compiler);
136-
137138
// Get all entry point names for this html file
138139
const entryNames = Array.from(compilation.entrypoints.keys());
139140
const filteredEntryNames = self.filterChunks(entryNames, self.options.chunks, self.options.excludeChunks);

‎lib/compiler.js

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

4651
/**
@@ -62,6 +67,7 @@ class HtmlWebpackChildCompiler {
6267
// Add the template to the childCompiler
6368
const newTemplateId = this.templates.length;
6469
this.templates.push(template);
70+
// Mark the cache invalid
6571
return newTemplateId;
6672
}
6773

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

119-
this.compilationStarted = new Date();
125+
this.compilationStartedTimestamp = new Date().getTime();
120126
this.compilationPromise = new Promise((resolve, reject) => {
121127
childCompiler.runAsChild((err, entries, childCompilation) => {
122128
// Extract templates
@@ -158,6 +164,37 @@ class HtmlWebpackChildCompiler {
158164

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

163200
/**
@@ -214,10 +251,13 @@ const childCompilerCache = new WeakMap();
214251
* @param {WebpackCompiler} mainCompiler
215252
*/
216253
function getChildCompiler (mainCompiler) {
217-
if (!childCompilerCache[mainCompiler]) {
218-
childCompilerCache[mainCompiler] = new HtmlWebpackChildCompiler();
254+
const cachedChildCompiler = childCompilerCache.get(mainCompiler);
255+
if (cachedChildCompiler) {
256+
return cachedChildCompiler;
219257
}
220-
return childCompilerCache[mainCompiler];
258+
const newCompiler = new HtmlWebpackChildCompiler();
259+
childCompilerCache.set(mainCompiler, newCompiler);
260+
return newCompiler;
221261
}
222262

223263
/**
@@ -226,7 +266,7 @@ function getChildCompiler (mainCompiler) {
226266
* @param {WebpackCompiler} mainCompiler
227267
*/
228268
function clearCache (mainCompiler) {
229-
delete (childCompilerCache[mainCompiler]);
269+
childCompilerCache.delete(mainCompiler);
230270
}
231271

232272
/**
@@ -252,6 +292,7 @@ function addTemplateToCompiler (mainCompiler, templatePath) {
252292
function compileTemplate (templatePath, outputFilename, mainCompilation) {
253293
const childCompiler = getChildCompiler(mainCompilation.compiler);
254294
return childCompiler.compileTemplates(mainCompilation).then((compiledTemplates) => {
295+
if (!compiledTemplates[templatePath]) console.log(Object.keys(compiledTemplates), templatePath);
255296
const compiledTemplate = compiledTemplates[templatePath];
256297
// Replace [hash] placeholders in filename
257298
const outputName = mainCompilation.mainTemplate.hooks.assetPath.call(outputFilename, {
@@ -269,8 +310,20 @@ function compileTemplate (templatePath, outputFilename, mainCompilation) {
269310
});
270311
}
271312

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

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"posttest": "tsc",
1616
"commit": "git-cz",
1717
"build-examples": "node examples/build-examples.js",
18-
"test": "jest --runInBand",
18+
"test": "jest --runInBand --verbose --coverage",
1919
"test-watch": "jest --runInBand --watch",
2020
"release": "standard-version"
2121
},

‎spec/caching.spec.js

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

‎tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"node_modules",
3232
"spec",
3333
"examples",
34-
"dist"
34+
"dist",
35+
"coverage"
3536
]
3637
}

0 commit comments

Comments
 (0)
Please sign in to comment.