source: imaps-frontend/node_modules/html-webpack-plugin/lib/cached-child-compiler.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 14.9 KB
Line 
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 asynchronously.
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"use strict";
25
26// Import types
27/** @typedef {import("webpack").Compiler} Compiler */
28/** @typedef {import("webpack").Compilation} Compilation */
29/** @typedef {import("webpack/lib/FileSystemInfo").Snapshot} Snapshot */
30/** @typedef {import("./child-compiler").ChildCompilationTemplateResult} ChildCompilationTemplateResult */
31/** @typedef {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} FileDependencies */
32/** @typedef {{
33 dependencies: FileDependencies,
34 compiledEntries: {[entryName: string]: ChildCompilationTemplateResult}
35} | {
36 dependencies: FileDependencies,
37 error: Error
38}} ChildCompilationResult */
39
40const { HtmlWebpackChildCompiler } = require("./child-compiler");
41
42/**
43 * This plugin is a singleton for performance reasons.
44 * To keep track if a plugin does already exist for the compiler they are cached
45 * in this map
46 * @type {WeakMap<Compiler, PersistentChildCompilerSingletonPlugin>}}
47 */
48const compilerMap = new WeakMap();
49
50class CachedChildCompilation {
51 /**
52 * @param {Compiler} compiler
53 */
54 constructor(compiler) {
55 /**
56 * @private
57 * @type {Compiler}
58 */
59 this.compiler = compiler;
60 // Create a singleton instance for the compiler
61 // if there is none
62 if (compilerMap.has(compiler)) {
63 return;
64 }
65 const persistentChildCompilerSingletonPlugin =
66 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(
77 this.compiler,
78 );
79 if (!persistentChildCompilerSingletonPlugin) {
80 throw new Error(
81 "PersistentChildCompilerSingletonPlugin instance not found.",
82 );
83 }
84 persistentChildCompilerSingletonPlugin.addEntry(entry);
85 }
86
87 getCompilationResult() {
88 const persistentChildCompilerSingletonPlugin = compilerMap.get(
89 this.compiler,
90 );
91 if (!persistentChildCompilerSingletonPlugin) {
92 throw new Error(
93 "PersistentChildCompilerSingletonPlugin instance not found.",
94 );
95 }
96 return persistentChildCompilerSingletonPlugin.getLatestResult();
97 }
98
99 /**
100 * Returns the result for the given entry
101 * @param {string} entry
102 * @returns {
103 | { mainCompilationHash: string, error: Error }
104 | { mainCompilationHash: string, compiledEntry: ChildCompilationTemplateResult }
105 }
106 */
107 getCompilationEntryResult(entry) {
108 const latestResult = this.getCompilationResult();
109 const compilationResult = latestResult.compilationResult;
110 return "error" in compilationResult
111 ? {
112 mainCompilationHash: latestResult.mainCompilationHash,
113 error: compilationResult.error,
114 }
115 : {
116 mainCompilationHash: latestResult.mainCompilationHash,
117 compiledEntry: compilationResult.compiledEntries[entry],
118 };
119 }
120}
121
122class PersistentChildCompilerSingletonPlugin {
123 /**
124 *
125 * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies
126 * @param {Compilation} mainCompilation
127 * @param {number} startTime
128 */
129 static createSnapshot(fileDependencies, mainCompilation, startTime) {
130 return new Promise((resolve, reject) => {
131 mainCompilation.fileSystemInfo.createSnapshot(
132 startTime,
133 fileDependencies.fileDependencies,
134 fileDependencies.contextDependencies,
135 fileDependencies.missingDependencies,
136 // @ts-ignore
137 null,
138 (err, snapshot) => {
139 if (err) {
140 return reject(err);
141 }
142 resolve(snapshot);
143 },
144 );
145 });
146 }
147
148 /**
149 * Returns true if the files inside this snapshot
150 * have not been changed
151 *
152 * @param {Snapshot} snapshot
153 * @param {Compilation} mainCompilation
154 * @returns {Promise<boolean | undefined>}
155 */
156 static isSnapshotValid(snapshot, mainCompilation) {
157 return new Promise((resolve, reject) => {
158 mainCompilation.fileSystemInfo.checkSnapshotValid(
159 snapshot,
160 (err, isValid) => {
161 if (err) {
162 reject(err);
163 }
164 resolve(isValid);
165 },
166 );
167 });
168 }
169
170 static watchFiles(mainCompilation, fileDependencies) {
171 Object.keys(fileDependencies).forEach((dependencyType) => {
172 fileDependencies[dependencyType].forEach((fileDependency) => {
173 mainCompilation[dependencyType].add(fileDependency);
174 });
175 });
176 }
177
178 constructor() {
179 /**
180 * @private
181 * @type {
182 | {
183 isCompiling: false,
184 isVerifyingCache: false,
185 entries: string[],
186 compiledEntries: string[],
187 mainCompilationHash: string,
188 compilationResult: ChildCompilationResult
189 }
190 | Readonly<{
191 isCompiling: false,
192 isVerifyingCache: true,
193 entries: string[],
194 previousEntries: string[],
195 previousResult: ChildCompilationResult
196 }>
197 | Readonly <{
198 isVerifyingCache: false,
199 isCompiling: true,
200 entries: string[],
201 }>
202 } the internal compilation state */
203 this.compilationState = {
204 isCompiling: false,
205 isVerifyingCache: false,
206 entries: [],
207 compiledEntries: [],
208 mainCompilationHash: "initial",
209 compilationResult: {
210 dependencies: {
211 fileDependencies: [],
212 contextDependencies: [],
213 missingDependencies: [],
214 },
215 compiledEntries: {},
216 },
217 };
218 }
219
220 /**
221 * apply is called by the webpack main compiler during the start phase
222 * @param {Compiler} compiler
223 */
224 apply(compiler) {
225 /** @type Promise<ChildCompilationResult> */
226 let childCompilationResultPromise = Promise.resolve({
227 dependencies: {
228 fileDependencies: [],
229 contextDependencies: [],
230 missingDependencies: [],
231 },
232 compiledEntries: {},
233 });
234 /**
235 * The main compilation hash which will only be updated
236 * if the childCompiler changes
237 */
238 /** @type {string} */
239 let mainCompilationHashOfLastChildRecompile = "";
240 /** @type {Snapshot | undefined} */
241 let previousFileSystemSnapshot;
242 let compilationStartTime = new Date().getTime();
243
244 compiler.hooks.make.tapAsync(
245 "PersistentChildCompilerSingletonPlugin",
246 (mainCompilation, callback) => {
247 if (
248 this.compilationState.isCompiling ||
249 this.compilationState.isVerifyingCache
250 ) {
251 return callback(new Error("Child compilation has already started"));
252 }
253
254 // Update the time to the current compile start time
255 compilationStartTime = new Date().getTime();
256
257 // The compilation starts - adding new templates is now not possible anymore
258 this.compilationState = {
259 isCompiling: false,
260 isVerifyingCache: true,
261 previousEntries: this.compilationState.compiledEntries,
262 previousResult: this.compilationState.compilationResult,
263 entries: this.compilationState.entries,
264 };
265
266 // Validate cache:
267 const isCacheValidPromise = this.isCacheValid(
268 previousFileSystemSnapshot,
269 mainCompilation,
270 );
271
272 let cachedResult = childCompilationResultPromise;
273 childCompilationResultPromise = isCacheValidPromise.then(
274 (isCacheValid) => {
275 // Reuse cache
276 if (isCacheValid) {
277 return cachedResult;
278 }
279 // Start the compilation
280 const compiledEntriesPromise = this.compileEntries(
281 mainCompilation,
282 this.compilationState.entries,
283 );
284 // Update snapshot as soon as we know the fileDependencies
285 // this might possibly cause bugs if files were changed between
286 // compilation start and snapshot creation
287 compiledEntriesPromise
288 .then((childCompilationResult) => {
289 return PersistentChildCompilerSingletonPlugin.createSnapshot(
290 childCompilationResult.dependencies,
291 mainCompilation,
292 compilationStartTime,
293 );
294 })
295 .then((snapshot) => {
296 previousFileSystemSnapshot = snapshot;
297 });
298 return compiledEntriesPromise;
299 },
300 );
301
302 // Add files to compilation which needs to be watched:
303 mainCompilation.hooks.optimizeTree.tapAsync(
304 "PersistentChildCompilerSingletonPlugin",
305 (chunks, modules, callback) => {
306 const handleCompilationDonePromise =
307 childCompilationResultPromise.then((childCompilationResult) => {
308 this.watchFiles(
309 mainCompilation,
310 childCompilationResult.dependencies,
311 );
312 });
313 handleCompilationDonePromise.then(
314 // @ts-ignore
315 () => callback(null, chunks, modules),
316 callback,
317 );
318 },
319 );
320
321 // Store the final compilation once the main compilation hash is known
322 mainCompilation.hooks.additionalAssets.tapAsync(
323 "PersistentChildCompilerSingletonPlugin",
324 (callback) => {
325 const didRecompilePromise = Promise.all([
326 childCompilationResultPromise,
327 cachedResult,
328 ]).then(([childCompilationResult, cachedResult]) => {
329 // Update if childCompilation changed
330 return cachedResult !== childCompilationResult;
331 });
332
333 const handleCompilationDonePromise = Promise.all([
334 childCompilationResultPromise,
335 didRecompilePromise,
336 ]).then(([childCompilationResult, didRecompile]) => {
337 // Update hash and snapshot if childCompilation changed
338 if (didRecompile) {
339 mainCompilationHashOfLastChildRecompile =
340 /** @type {string} */ (mainCompilation.hash);
341 }
342 this.compilationState = {
343 isCompiling: false,
344 isVerifyingCache: false,
345 entries: this.compilationState.entries,
346 compiledEntries: this.compilationState.entries,
347 compilationResult: childCompilationResult,
348 mainCompilationHash: mainCompilationHashOfLastChildRecompile,
349 };
350 });
351 handleCompilationDonePromise.then(() => callback(null), callback);
352 },
353 );
354
355 // Continue compilation:
356 callback(null);
357 },
358 );
359 }
360
361 /**
362 * Add a new entry to the next compile run
363 * @param {string} entry
364 */
365 addEntry(entry) {
366 if (
367 this.compilationState.isCompiling ||
368 this.compilationState.isVerifyingCache
369 ) {
370 throw new Error(
371 "The child compiler has already started to compile. " +
372 "Please add entries before the main compiler 'make' phase has started or " +
373 "after the compilation is done.",
374 );
375 }
376 if (this.compilationState.entries.indexOf(entry) === -1) {
377 this.compilationState.entries = [...this.compilationState.entries, entry];
378 }
379 }
380
381 getLatestResult() {
382 if (
383 this.compilationState.isCompiling ||
384 this.compilationState.isVerifyingCache
385 ) {
386 throw new Error(
387 "The child compiler is not done compiling. " +
388 "Please access the result after the compiler 'make' phase has started or " +
389 "after the compilation is done.",
390 );
391 }
392 return {
393 mainCompilationHash: this.compilationState.mainCompilationHash,
394 compilationResult: this.compilationState.compilationResult,
395 };
396 }
397
398 /**
399 * Verify that the cache is still valid
400 * @private
401 * @param {Snapshot | undefined} snapshot
402 * @param {Compilation} mainCompilation
403 * @returns {Promise<boolean | undefined>}
404 */
405 isCacheValid(snapshot, mainCompilation) {
406 if (!this.compilationState.isVerifyingCache) {
407 return Promise.reject(
408 new Error(
409 "Cache validation can only be done right before the compilation starts",
410 ),
411 );
412 }
413 // If there are no entries we don't need a new child compilation
414 if (this.compilationState.entries.length === 0) {
415 return Promise.resolve(true);
416 }
417 // If there are new entries the cache is invalid
418 if (
419 this.compilationState.entries !== this.compilationState.previousEntries
420 ) {
421 return Promise.resolve(false);
422 }
423 // Mark the cache as invalid if there is no snapshot
424 if (!snapshot) {
425 return Promise.resolve(false);
426 }
427
428 return PersistentChildCompilerSingletonPlugin.isSnapshotValid(
429 snapshot,
430 mainCompilation,
431 );
432 }
433
434 /**
435 * Start to compile all templates
436 *
437 * @private
438 * @param {Compilation} mainCompilation
439 * @param {string[]} entries
440 * @returns {Promise<ChildCompilationResult>}
441 */
442 compileEntries(mainCompilation, entries) {
443 const compiler = new HtmlWebpackChildCompiler(entries);
444 return compiler.compileTemplates(mainCompilation).then(
445 (result) => {
446 return {
447 // The compiled sources to render the content
448 compiledEntries: result,
449 // The file dependencies to find out if a
450 // recompilation is required
451 dependencies: compiler.fileDependencies,
452 // The main compilation hash can be used to find out
453 // if this compilation was done during the current compilation
454 mainCompilationHash: mainCompilation.hash,
455 };
456 },
457 (error) => ({
458 // The compiled sources to render the content
459 error,
460 // The file dependencies to find out if a
461 // recompilation is required
462 dependencies: compiler.fileDependencies,
463 // The main compilation hash can be used to find out
464 // if this compilation was done during the current compilation
465 mainCompilationHash: mainCompilation.hash,
466 }),
467 );
468 }
469
470 /**
471 * @private
472 * @param {Compilation} mainCompilation
473 * @param {FileDependencies} files
474 */
475 watchFiles(mainCompilation, files) {
476 PersistentChildCompilerSingletonPlugin.watchFiles(mainCompilation, files);
477 }
478}
479
480module.exports = {
481 CachedChildCompilation,
482};
Note: See TracBrowser for help on using the repository browser.