html-webpack-plugin 4.0.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,379 @@
1
+ // @ts-check
2
+ /**
3
+ * @file
4
+ * Helper plugin manages the cached state of the child compilation
5
+ *
6
+ * To optimize performance the child compilation is running asyncronously.
7
+ * Therefore it needs to be started in the compiler.make phase and ends after
8
+ * the compilation.afterCompile phase.
9
+ *
10
+ * To prevent bugs from blocked hooks there is no promise or event based api
11
+ * for this plugin.
12
+ *
13
+ * Example usage:
14
+ *
15
+ * ```js
16
+ const childCompilerPlugin = new PersistentChildCompilerPlugin();
17
+ childCompilerPlugin.addEntry('./src/index.js');
18
+ compiler.hooks.afterCompile.tapAsync('MyPlugin', (compilation, callback) => {
19
+ console.log(childCompilerPlugin.getCompilationResult()['./src/index.js']));
20
+ return true;
21
+ });
22
+ * ```
23
+ */
24
+
25
+ // Import types
26
+ /** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */
27
+ /** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
28
+ /** @typedef {{hash: string, entry: any, content: string }} ChildCompilationResultEntry */
29
+ /** @typedef {import("./webpack4/file-watcher-api").Snapshot} Snapshot */
30
+ /** @typedef {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} FileDependencies */
31
+ /** @typedef {{
32
+ dependencies: FileDependencies,
33
+ compiledEntries: {[entryName: string]: ChildCompilationResultEntry}
34
+ } | {
35
+ dependencies: FileDependencies,
36
+ error: Error
37
+ }} ChildCompilationResult */
38
+ 'use strict';
39
+
40
+ const { HtmlWebpackChildCompiler } = require('./child-compiler');
41
+ const fileWatcherApi = require('./file-watcher-api');
42
+
43
+ /**
44
+ * This plugin is a singleton for performance reasons.
45
+ * To keep track if a plugin does already exist for the compiler they are cached
46
+ * in this map
47
+ * @type {WeakMap<WebpackCompiler, PersistentChildCompilerSingletonPlugin>}}
48
+ */
49
+ const compilerMap = new WeakMap();
50
+
51
+ class CachedChildCompilation {
52
+ /**
53
+ * @param {WebpackCompiler} compiler
54
+ */
55
+ constructor (compiler) {
56
+ /**
57
+ * @private
58
+ * @type {WebpackCompiler}
59
+ */
60
+ this.compiler = compiler;
61
+ // Create a singlton instance for the compiler
62
+ // if there is none
63
+ if (compilerMap.has(compiler)) {
64
+ return;
65
+ }
66
+ const persistentChildCompilerSingletonPlugin = new PersistentChildCompilerSingletonPlugin();
67
+ compilerMap.set(compiler, persistentChildCompilerSingletonPlugin);
68
+ persistentChildCompilerSingletonPlugin.apply(compiler);
69
+ }
70
+
71
+ /**
72
+ * apply is called by the webpack main compiler during the start phase
73
+ * @param {string} entry
74
+ */
75
+ addEntry (entry) {
76
+ const persistentChildCompilerSingletonPlugin = compilerMap.get(this.compiler);
77
+ if (!persistentChildCompilerSingletonPlugin) {
78
+ throw new Error(
79
+ 'PersistentChildCompilerSingletonPlugin instance not found.'
80
+ );
81
+ }
82
+ persistentChildCompilerSingletonPlugin.addEntry(entry);
83
+ }
84
+
85
+ getCompilationResult () {
86
+ const persistentChildCompilerSingletonPlugin = compilerMap.get(this.compiler);
87
+ if (!persistentChildCompilerSingletonPlugin) {
88
+ throw new Error(
89
+ 'PersistentChildCompilerSingletonPlugin instance not found.'
90
+ );
91
+ }
92
+ return persistentChildCompilerSingletonPlugin.getLatestResult();
93
+ }
94
+
95
+ /**
96
+ * Returns the result for the given entry
97
+ * @param {string} entry
98
+ * @returns {
99
+ | { mainCompilationHash: string, error: Error }
100
+ | { mainCompilationHash: string, compiledEntry: ChildCompilationResultEntry }
101
+ }
102
+ */
103
+ getCompilationEntryResult (entry) {
104
+ const latestResult = this.getCompilationResult();
105
+ const compilationResult = latestResult.compilationResult;
106
+ return 'error' in compilationResult ? {
107
+ mainCompilationHash: latestResult.mainCompilationHash,
108
+ error: compilationResult.error
109
+ } : {
110
+ mainCompilationHash: latestResult.mainCompilationHash,
111
+ compiledEntry: compilationResult.compiledEntries[entry]
112
+ };
113
+ }
114
+ }
115
+
116
+ class PersistentChildCompilerSingletonPlugin {
117
+ constructor () {
118
+ /**
119
+ * @private
120
+ * @type {
121
+ | {
122
+ isCompiling: false,
123
+ isVerifyingCache: false,
124
+ entries: string[],
125
+ compiledEntries: string[],
126
+ mainCompilationHash: string,
127
+ compilationResult: ChildCompilationResult
128
+ }
129
+ | Readonly<{
130
+ isCompiling: false,
131
+ isVerifyingCache: true,
132
+ entries: string[],
133
+ previousEntries: string[],
134
+ previousResult: ChildCompilationResult
135
+ }>
136
+ | Readonly <{
137
+ isVerifyingCache: false,
138
+ isCompiling: true,
139
+ entries: string[],
140
+ }>
141
+ } the internal compilation state */
142
+ this.compilationState = {
143
+ isCompiling: false,
144
+ isVerifyingCache: false,
145
+ entries: [],
146
+ compiledEntries: [],
147
+ mainCompilationHash: 'initial',
148
+ compilationResult: {
149
+ dependencies: {
150
+ fileDependencies: [],
151
+ contextDependencies: [],
152
+ missingDependencies: []
153
+ },
154
+ compiledEntries: {}
155
+ }
156
+ };
157
+ }
158
+
159
+ /**
160
+ * apply is called by the webpack main compiler during the start phase
161
+ * @param {WebpackCompiler} compiler
162
+ */
163
+ apply (compiler) {
164
+ /** @type Promise<ChildCompilationResult> */
165
+ let childCompilationResultPromise = Promise.resolve({
166
+ dependencies: {
167
+ fileDependencies: [],
168
+ contextDependencies: [],
169
+ missingDependencies: []
170
+ },
171
+ compiledEntries: {}
172
+ });
173
+ /**
174
+ * The main compilation hash which will only be updated
175
+ * if the childCompiler changes
176
+ */
177
+ let mainCompilationHashOfLastChildRecompile = '';
178
+ /** @typedef{Snapshot|undefined} */
179
+ let previousFileSystemSnapshot;
180
+ let compilationStartTime = new Date().getTime();
181
+
182
+ compiler.hooks.make.tapAsync(
183
+ 'PersistentChildCompilerSingletonPlugin',
184
+ (mainCompilation, callback) => {
185
+ if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) {
186
+ return callback(new Error('Child compilation has already started'));
187
+ }
188
+
189
+ // Update the time to the current compile start time
190
+ compilationStartTime = new Date().getTime();
191
+
192
+ // The compilation starts - adding new templates is now not possible anymore
193
+ this.compilationState = {
194
+ isCompiling: false,
195
+ isVerifyingCache: true,
196
+ previousEntries: this.compilationState.compiledEntries,
197
+ previousResult: this.compilationState.compilationResult,
198
+ entries: this.compilationState.entries
199
+ };
200
+
201
+ // Validate cache:
202
+ const isCacheValidPromise = this.isCacheValid(previousFileSystemSnapshot, mainCompilation);
203
+
204
+ let cachedResult = childCompilationResultPromise;
205
+ childCompilationResultPromise = isCacheValidPromise.then((isCacheValid) => {
206
+ // Reuse cache
207
+ if (isCacheValid) {
208
+ return cachedResult;
209
+ }
210
+ // Start the compilation
211
+ const compiledEntriesPromise = this.compileEntries(
212
+ mainCompilation,
213
+ this.compilationState.entries
214
+ );
215
+ // Update snapshot as soon as we know the filedependencies
216
+ // this might possibly cause bugs if files were changed inbetween
217
+ // compilation start and snapshot creation
218
+ compiledEntriesPromise.then((childCompilationResult) => {
219
+ return fileWatcherApi.createSnapshot(childCompilationResult.dependencies, mainCompilation, compilationStartTime);
220
+ }).then((snapshot) => {
221
+ previousFileSystemSnapshot = snapshot;
222
+ });
223
+ return compiledEntriesPromise;
224
+ });
225
+
226
+ // Add files to compilation which needs to be watched:
227
+ mainCompilation.hooks.optimizeTree.tapAsync(
228
+ 'PersistentChildCompilerSingletonPlugin',
229
+ (chunks, modules, callback) => {
230
+ const handleCompilationDonePromise = childCompilationResultPromise.then(
231
+ childCompilationResult => {
232
+ this.watchFiles(
233
+ mainCompilation,
234
+ childCompilationResult.dependencies
235
+ );
236
+ });
237
+ handleCompilationDonePromise.then(() => callback(null, chunks, modules), callback);
238
+ }
239
+ );
240
+
241
+ // Store the final compilation once the main compilation hash is known
242
+ mainCompilation.hooks.additionalAssets.tapAsync(
243
+ 'PersistentChildCompilerSingletonPlugin',
244
+ (callback) => {
245
+ const didRecompilePromise = Promise.all([childCompilationResultPromise, cachedResult]).then(
246
+ ([childCompilationResult, cachedResult]) => {
247
+ // Update if childCompilation changed
248
+ return (cachedResult !== childCompilationResult);
249
+ }
250
+ );
251
+
252
+ const handleCompilationDonePromise = Promise.all([childCompilationResultPromise, didRecompilePromise]).then(
253
+ ([childCompilationResult, didRecompile]) => {
254
+ // Update hash and snapshot if childCompilation changed
255
+ if (didRecompile) {
256
+ mainCompilationHashOfLastChildRecompile = mainCompilation.hash;
257
+ }
258
+ this.compilationState = {
259
+ isCompiling: false,
260
+ isVerifyingCache: false,
261
+ entries: this.compilationState.entries,
262
+ compiledEntries: this.compilationState.entries,
263
+ compilationResult: childCompilationResult,
264
+ mainCompilationHash: mainCompilationHashOfLastChildRecompile
265
+ };
266
+ });
267
+ handleCompilationDonePromise.then(() => callback(null), callback);
268
+ }
269
+ );
270
+
271
+ // Continue compilation:
272
+ callback(null);
273
+ }
274
+ );
275
+ }
276
+
277
+ /**
278
+ * Add a new entry to the next compile run
279
+ * @param {string} entry
280
+ */
281
+ addEntry (entry) {
282
+ if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) {
283
+ throw new Error(
284
+ 'The child compiler has already started to compile. ' +
285
+ "Please add entries before the main compiler 'make' phase has started or " +
286
+ 'after the compilation is done.'
287
+ );
288
+ }
289
+ if (this.compilationState.entries.indexOf(entry) === -1) {
290
+ this.compilationState.entries = [...this.compilationState.entries, entry];
291
+ }
292
+ }
293
+
294
+ getLatestResult () {
295
+ if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) {
296
+ throw new Error(
297
+ 'The child compiler is not done compiling. ' +
298
+ "Please access the result after the compiler 'make' phase has started or " +
299
+ 'after the compilation is done.'
300
+ );
301
+ }
302
+ return {
303
+ mainCompilationHash: this.compilationState.mainCompilationHash,
304
+ compilationResult: this.compilationState.compilationResult
305
+ };
306
+ }
307
+
308
+ /**
309
+ * Verify that the cache is still valid
310
+ * @private
311
+ * @param {Snapshot | undefined} snapshot
312
+ * @param {WebpackCompilation} mainCompilation
313
+ * @returns {Promise<boolean>}
314
+ */
315
+ isCacheValid (snapshot, mainCompilation) {
316
+ if (!this.compilationState.isVerifyingCache) {
317
+ return Promise.reject(new Error('Cache validation can only be done right before the compilation starts'));
318
+ }
319
+ // If there are no entries we don't need a new child compilation
320
+ if (this.compilationState.entries.length === 0) {
321
+ return Promise.resolve(true);
322
+ }
323
+ // If there are new entries the cache is invalid
324
+ if (this.compilationState.entries !== this.compilationState.previousEntries) {
325
+ return Promise.resolve(false);
326
+ }
327
+ // Mark the cache as invalid if there is no snapshot
328
+ if (!snapshot) {
329
+ return Promise.resolve(false);
330
+ }
331
+ return fileWatcherApi.isSnapShotValid(snapshot, mainCompilation);
332
+ }
333
+
334
+ /**
335
+ * Start to compile all templates
336
+ *
337
+ * @private
338
+ * @param {WebpackCompilation} mainCompilation
339
+ * @param {string[]} entries
340
+ * @returns {Promise<ChildCompilationResult>}
341
+ */
342
+ compileEntries (mainCompilation, entries) {
343
+ const compiler = new HtmlWebpackChildCompiler(entries);
344
+ return compiler.compileTemplates(mainCompilation).then((result) => {
345
+ return {
346
+ // The compiled sources to render the content
347
+ compiledEntries: result,
348
+ // The file dependencies to find out if a
349
+ // recompilation is required
350
+ dependencies: compiler.fileDependencies,
351
+ // The main compilation hash can be used to find out
352
+ // if this compilation was done during the current compilation
353
+ mainCompilationHash: mainCompilation.hash
354
+ };
355
+ }, error => ({
356
+ // The compiled sources to render the content
357
+ error,
358
+ // The file dependencies to find out if a
359
+ // recompilation is required
360
+ dependencies: compiler.fileDependencies,
361
+ // The main compilation hash can be used to find out
362
+ // if this compilation was done during the current compilation
363
+ mainCompilationHash: mainCompilation.hash
364
+ }));
365
+ }
366
+
367
+ /**
368
+ * @private
369
+ * @param {WebpackCompilation} mainCompilation
370
+ * @param {FileDependencies} files
371
+ */
372
+ watchFiles (mainCompilation, files) {
373
+ fileWatcherApi.watchFiles(mainCompilation, files);
374
+ }
375
+ }
376
+
377
+ module.exports = {
378
+ CachedChildCompilation
379
+ };
@@ -0,0 +1,183 @@
1
+ // @ts-check
2
+ /** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
3
+ /** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */
4
+ /** @typedef {import("webpack/lib/Chunk.js")} WebpackChunk */
5
+ 'use strict';
6
+ /**
7
+ * @file
8
+ * This file uses webpack to compile a template with a child compiler.
9
+ *
10
+ * [TEMPLATE] -> [JAVASCRIPT]
11
+ *
12
+ */
13
+ 'use strict';
14
+ const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
15
+ const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
16
+ const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
17
+ const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
18
+ const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
19
+
20
+ /**
21
+ * The HtmlWebpackChildCompiler is a helper to allow resusing one childCompiler
22
+ * for multile HtmlWebpackPlugin instances to improve the compilation performance.
23
+ */
24
+ class HtmlWebpackChildCompiler {
25
+ /**
26
+ *
27
+ * @param {string[]} templates
28
+ */
29
+ constructor (templates) {
30
+ /**
31
+ * @type {string[]} templateIds
32
+ * The template array will allow us to keep track which input generated which output
33
+ */
34
+ this.templates = templates;
35
+ /**
36
+ * @type {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>}
37
+ */
38
+ this.compilationPromise; // eslint-disable-line
39
+ /**
40
+ * @type {number}
41
+ */
42
+ this.compilationStartedTimestamp; // eslint-disable-line
43
+ /**
44
+ * @type {number}
45
+ */
46
+ this.compilationEndedTimestamp; // eslint-disable-line
47
+ /**
48
+ * All file dependencies of the child compiler
49
+ * @type {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}}
50
+ */
51
+ this.fileDependencies = { fileDependencies: [], contextDependencies: [], missingDependencies: [] };
52
+ }
53
+
54
+ /**
55
+ * Returns true if the childCompiler is currently compiling
56
+ * @retuns {boolean}
57
+ */
58
+ isCompiling () {
59
+ return !this.didCompile() && this.compilationStartedTimestamp !== undefined;
60
+ }
61
+
62
+ /**
63
+ * Returns true if the childCOmpiler is done compiling
64
+ */
65
+ didCompile () {
66
+ return this.compilationEndedTimestamp !== undefined;
67
+ }
68
+
69
+ /**
70
+ * This function will start the template compilation
71
+ * once it is started no more templates can be added
72
+ *
73
+ * @param {WebpackCompilation} mainCompilation
74
+ * @returns {Promise<{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}>}
75
+ */
76
+ compileTemplates (mainCompilation) {
77
+ // To prevent multiple compilations for the same template
78
+ // the compilation is cached in a promise.
79
+ // If it already exists return
80
+ if (this.compilationPromise) {
81
+ return this.compilationPromise;
82
+ }
83
+
84
+ // The entry file is just an empty helper as the dynamic template
85
+ // require is added in "loader.js"
86
+ const outputOptions = {
87
+ filename: '__child-[name]',
88
+ publicPath: mainCompilation.outputOptions.publicPath
89
+ };
90
+ const compilerName = 'HtmlWebpackCompiler';
91
+ // Create an additional child compiler which takes the template
92
+ // and turns it into an Node.JS html factory.
93
+ // This allows us to use loaders during the compilation
94
+ const childCompiler = mainCompilation.createChildCompiler(compilerName, outputOptions);
95
+ // The file path context which webpack uses to resolve all relative files to
96
+ childCompiler.context = mainCompilation.compiler.context;
97
+ // Compile the template to nodejs javascript
98
+ new NodeTemplatePlugin(outputOptions).apply(childCompiler);
99
+ new NodeTargetPlugin().apply(childCompiler);
100
+ new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var').apply(childCompiler);
101
+ new LoaderTargetPlugin('node').apply(childCompiler);
102
+
103
+ // Add all templates
104
+ this.templates.forEach((template, index) => {
105
+ new SingleEntryPlugin(childCompiler.context, template, `HtmlWebpackPlugin_${index}`).apply(childCompiler);
106
+ });
107
+
108
+ this.compilationStartedTimestamp = new Date().getTime();
109
+ this.compilationPromise = new Promise((resolve, reject) => {
110
+ childCompiler.runAsChild((err, entries, childCompilation) => {
111
+ // Extract templates
112
+ const compiledTemplates = entries
113
+ ? extractHelperFilesFromCompilation(mainCompilation, childCompilation, outputOptions.filename, entries)
114
+ : [];
115
+ // Extract file dependencies
116
+ if (entries) {
117
+ this.fileDependencies = { fileDependencies: Array.from(childCompilation.fileDependencies), contextDependencies: Array.from(childCompilation.contextDependencies), missingDependencies: Array.from(childCompilation.missingDependencies) };
118
+ }
119
+ // Reject the promise if the childCompilation contains error
120
+ if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
121
+ const errorDetails = childCompilation.errors.map(error => error.message + (error.error ? ':\n' + error.error : '')).join('\n');
122
+ reject(new Error('Child compilation failed:\n' + errorDetails));
123
+ return;
124
+ }
125
+ // Reject if the error object contains errors
126
+ if (err) {
127
+ reject(err);
128
+ return;
129
+ }
130
+ /**
131
+ * @type {{[templatePath: string]: { content: string, hash: string, entry: WebpackChunk }}}
132
+ */
133
+ const result = {};
134
+ compiledTemplates.forEach((templateSource, entryIndex) => {
135
+ // The compiledTemplates are generated from the entries added in
136
+ // the addTemplate function.
137
+ // Therefore the array index of this.templates should be the as entryIndex.
138
+ result[this.templates[entryIndex]] = {
139
+ content: templateSource,
140
+ hash: childCompilation.hash,
141
+ entry: entries[entryIndex]
142
+ };
143
+ });
144
+ this.compilationEndedTimestamp = new Date().getTime();
145
+ resolve(result);
146
+ });
147
+ });
148
+
149
+ return this.compilationPromise;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * The webpack child compilation will create files as a side effect.
155
+ * This function will extract them and clean them up so they won't be written to disk.
156
+ *
157
+ * Returns the source code of the compiled templates as string
158
+ *
159
+ * @returns Array<string>
160
+ */
161
+ function extractHelperFilesFromCompilation (mainCompilation, childCompilation, filename, childEntryChunks) {
162
+ const helperAssetNames = childEntryChunks.map((entryChunk, index) => {
163
+ return mainCompilation.mainTemplate.getAssetPath(filename, {
164
+ hash: childCompilation.hash,
165
+ chunk: entryChunk,
166
+ name: `HtmlWebpackPlugin_${index}`
167
+ });
168
+ });
169
+
170
+ helperAssetNames.forEach((helperFileName) => {
171
+ delete mainCompilation.assets[helperFileName];
172
+ });
173
+
174
+ const helperContents = helperAssetNames.map((helperFileName) => {
175
+ return childCompilation.assets[helperFileName].source();
176
+ });
177
+
178
+ return helperContents;
179
+ }
180
+
181
+ module.exports = {
182
+ HtmlWebpackChildCompiler
183
+ };
@@ -0,0 +1,14 @@
1
+ // @ts-check
2
+ /**
3
+ * To use the availble webpack core api
4
+ * we have to use different child compilers
5
+ * depending on the used webpack version
6
+ */
7
+ const webpackMajorVersion = Number(require('webpack/package.json').version.split('.')[0]);
8
+
9
+ // Typescript hack to test only the webpack 4 code
10
+ /** @type {import('./webpack4/file-watcher-api')} */
11
+ module.exports = webpackMajorVersion === 4
12
+ ? require('./webpack4/file-watcher-api.js')
13
+ // Hack to ignore './webpack5/file-watcher-api.js' from typescript:
14
+ : require('./webpack' + 5 + '/file-watcher-api.js');
package/lib/html-tags.js CHANGED
@@ -65,7 +65,31 @@ function createHtmlTagObject (tagName, attributes, innerHTML) {
65
65
  };
66
66
  }
67
67
 
68
+ /**
69
+ * The `HtmlTagArray Array with a custom `.toString()` method.
70
+ *
71
+ * This allows the following:
72
+ * ```
73
+ * const tags = HtmlTagArray.from([tag1, tag2]);
74
+ * const scriptTags = tags.filter((tag) => tag.tagName === 'script');
75
+ * const html = scriptTags.toString();
76
+ * ```
77
+ *
78
+ * Or inside a string literal:
79
+ * ```
80
+ * const tags = HtmlTagArray.from([tag1, tag2]);
81
+ * const html = `<html><body>${tags.filter((tag) => tag.tagName === 'script')}</body></html>`;
82
+ * ```
83
+ *
84
+ */
85
+ class HtmlTagArray extends Array {
86
+ toString () {
87
+ return this.join('');
88
+ }
89
+ }
90
+
68
91
  module.exports = {
92
+ HtmlTagArray: HtmlTagArray,
69
93
  createHtmlTagObject: createHtmlTagObject,
70
94
  htmlTagObjectToString: htmlTagObjectToString
71
95
  };
@@ -0,0 +1,64 @@
1
+ /** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
2
+ /** @typedef {{timestamp: number, fileDependencies: string[]}} Snapshot */
3
+ 'use strict';
4
+
5
+ /**
6
+ *
7
+ * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies
8
+ * @param {WebpackCompilation} compilation
9
+ * @param {number} startTime
10
+ */
11
+ function createSnapshot (fileDependencies, compilation, startTime) {
12
+ const flatDependencies = [];
13
+ Object.keys(fileDependencies).forEach((depencyTypes) => {
14
+ fileDependencies[depencyTypes].forEach(fileDependency => {
15
+ flatDependencies.push(fileDependency);
16
+ });
17
+ });
18
+ return {
19
+ fileDependencies: flatDependencies,
20
+ timestamp: startTime
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Returns true if the files inside this snapshot
26
+ * have not been changed
27
+ *
28
+ * @param {Snapshot} snapshot
29
+ * @param {WebpackCompilation} compilation
30
+ * @returns {Promise<boolean>}
31
+ */
32
+ function isSnapShotValid (snapshot, compilation) {
33
+ // Check if any dependent file was changed after the last compilation
34
+ const fileTimestamps = compilation.fileTimestamps;
35
+ const isCacheOutOfDate = snapshot.fileDependencies.some((fileDependency) => {
36
+ const timestamp = fileTimestamps.get(fileDependency);
37
+ // If the timestamp is not known the file is new
38
+ // If the timestamp is larger then the file has changed
39
+ // Otherwise the file is still the same
40
+ return !timestamp || timestamp > snapshot.timestamp;
41
+ });
42
+ return Promise.resolve(!isCacheOutOfDate);
43
+ }
44
+
45
+ /**
46
+ * Ensure that the files keep watched for changes
47
+ * and will trigger a recompile
48
+ *
49
+ * @param {WebpackCompilation} mainCompilation
50
+ * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies
51
+ */
52
+ function watchFiles (mainCompilation, fileDependencies) {
53
+ Object.keys(fileDependencies).forEach((depencyTypes) => {
54
+ fileDependencies[depencyTypes].forEach(fileDependency => {
55
+ mainCompilation.compilationDependencies.add(fileDependency);
56
+ });
57
+ });
58
+ }
59
+
60
+ module.exports = {
61
+ createSnapshot,
62
+ isSnapShotValid,
63
+ watchFiles
64
+ };