source: imaps-frontend/node_modules/webpack/lib/MultiCompiler.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: 18.0 KB
RevLine 
[79a0317]1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const asyncLib = require("neo-async");
9const { SyncHook, MultiHook } = require("tapable");
10
11const ConcurrentCompilationError = require("./ConcurrentCompilationError");
12const MultiStats = require("./MultiStats");
13const MultiWatching = require("./MultiWatching");
14const WebpackError = require("./WebpackError");
15const ArrayQueue = require("./util/ArrayQueue");
16
17/** @template T @typedef {import("tapable").AsyncSeriesHook<T>} AsyncSeriesHook<T> */
18/** @template T @template R @typedef {import("tapable").SyncBailHook<T, R>} SyncBailHook<T, R> */
19/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */
20/** @typedef {import("./Compiler")} Compiler */
21/** @typedef {import("./Stats")} Stats */
22/** @typedef {import("./Watching")} Watching */
23/** @typedef {import("./logging/Logger").Logger} Logger */
24/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
25/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
26/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
27/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
28
29/**
30 * @template T
31 * @callback Callback
32 * @param {Error | null} err
33 * @param {T=} result
34 */
35
36/**
37 * @callback RunWithDependenciesHandler
38 * @param {Compiler} compiler
39 * @param {Callback<MultiStats>} callback
40 */
41
42/**
43 * @typedef {object} MultiCompilerOptions
44 * @property {number=} parallelism how many Compilers are allows to run at the same time in parallel
45 */
46
47module.exports = class MultiCompiler {
48 /**
49 * @param {Compiler[] | Record<string, Compiler>} compilers child compilers
50 * @param {MultiCompilerOptions} options options
51 */
52 constructor(compilers, options) {
53 if (!Array.isArray(compilers)) {
54 /** @type {Compiler[]} */
55 compilers = Object.keys(compilers).map(name => {
56 /** @type {Record<string, Compiler>} */
57 (compilers)[name].name = name;
58 return /** @type {Record<string, Compiler>} */ (compilers)[name];
59 });
60 }
61
62 this.hooks = Object.freeze({
63 /** @type {SyncHook<[MultiStats]>} */
64 done: new SyncHook(["stats"]),
65 /** @type {MultiHook<SyncHook<[string | null, number]>>} */
66 invalid: new MultiHook(compilers.map(c => c.hooks.invalid)),
67 /** @type {MultiHook<AsyncSeriesHook<[Compiler]>>} */
68 run: new MultiHook(compilers.map(c => c.hooks.run)),
69 /** @type {SyncHook<[]>} */
70 watchClose: new SyncHook([]),
71 /** @type {MultiHook<AsyncSeriesHook<[Compiler]>>} */
72 watchRun: new MultiHook(compilers.map(c => c.hooks.watchRun)),
73 /** @type {MultiHook<SyncBailHook<[string, string, any[]], true>>} */
74 infrastructureLog: new MultiHook(
75 compilers.map(c => c.hooks.infrastructureLog)
76 )
77 });
78 this.compilers = compilers;
79 /** @type {MultiCompilerOptions} */
80 this._options = {
81 parallelism: options.parallelism || Infinity
82 };
83 /** @type {WeakMap<Compiler, string[]>} */
84 this.dependencies = new WeakMap();
85 this.running = false;
86
87 /** @type {(Stats | null)[]} */
88 const compilerStats = this.compilers.map(() => null);
89 let doneCompilers = 0;
90 for (let index = 0; index < this.compilers.length; index++) {
91 const compiler = this.compilers[index];
92 const compilerIndex = index;
93 let compilerDone = false;
94 // eslint-disable-next-line no-loop-func
95 compiler.hooks.done.tap("MultiCompiler", stats => {
96 if (!compilerDone) {
97 compilerDone = true;
98 doneCompilers++;
99 }
100 compilerStats[compilerIndex] = stats;
101 if (doneCompilers === this.compilers.length) {
102 this.hooks.done.call(
103 new MultiStats(/** @type {Stats[]} */ (compilerStats))
104 );
105 }
106 });
107 // eslint-disable-next-line no-loop-func
108 compiler.hooks.invalid.tap("MultiCompiler", () => {
109 if (compilerDone) {
110 compilerDone = false;
111 doneCompilers--;
112 }
113 });
114 }
115 this._validateCompilersOptions();
116 }
117
118 _validateCompilersOptions() {
119 if (this.compilers.length < 2) return;
120 /**
121 * @param {Compiler} compiler compiler
122 * @param {WebpackError} warning warning
123 */
124 const addWarning = (compiler, warning) => {
125 compiler.hooks.thisCompilation.tap("MultiCompiler", compilation => {
126 compilation.warnings.push(warning);
127 });
128 };
129 const cacheNames = new Set();
130 for (const compiler of this.compilers) {
131 if (compiler.options.cache && "name" in compiler.options.cache) {
132 const name = compiler.options.cache.name;
133 if (cacheNames.has(name)) {
134 addWarning(
135 compiler,
136 new WebpackError(
137 `${
138 compiler.name
139 ? `Compiler with name "${compiler.name}" doesn't use unique cache name. `
140 : ""
141 }Please set unique "cache.name" option. Name "${name}" already used.`
142 )
143 );
144 } else {
145 cacheNames.add(name);
146 }
147 }
148 }
149 }
150
151 get options() {
152 return Object.assign(
153 this.compilers.map(c => c.options),
154 this._options
155 );
156 }
157
158 get outputPath() {
159 let commonPath = this.compilers[0].outputPath;
160 for (const compiler of this.compilers) {
161 while (
162 compiler.outputPath.indexOf(commonPath) !== 0 &&
163 /[/\\]/.test(commonPath)
164 ) {
165 commonPath = commonPath.replace(/[/\\][^/\\]*$/, "");
166 }
167 }
168
169 if (!commonPath && this.compilers[0].outputPath[0] === "/") return "/";
170 return commonPath;
171 }
172
173 get inputFileSystem() {
174 throw new Error("Cannot read inputFileSystem of a MultiCompiler");
175 }
176
177 /**
178 * @param {InputFileSystem} value the new input file system
179 */
180 set inputFileSystem(value) {
181 for (const compiler of this.compilers) {
182 compiler.inputFileSystem = value;
183 }
184 }
185
186 get outputFileSystem() {
187 throw new Error("Cannot read outputFileSystem of a MultiCompiler");
188 }
189
190 /**
191 * @param {OutputFileSystem} value the new output file system
192 */
193 set outputFileSystem(value) {
194 for (const compiler of this.compilers) {
195 compiler.outputFileSystem = value;
196 }
197 }
198
199 get watchFileSystem() {
200 throw new Error("Cannot read watchFileSystem of a MultiCompiler");
201 }
202
203 /**
204 * @param {WatchFileSystem} value the new watch file system
205 */
206 set watchFileSystem(value) {
207 for (const compiler of this.compilers) {
208 compiler.watchFileSystem = value;
209 }
210 }
211
212 /**
213 * @param {IntermediateFileSystem} value the new intermediate file system
214 */
215 set intermediateFileSystem(value) {
216 for (const compiler of this.compilers) {
217 compiler.intermediateFileSystem = value;
218 }
219 }
220
221 get intermediateFileSystem() {
222 throw new Error("Cannot read outputFileSystem of a MultiCompiler");
223 }
224
225 /**
226 * @param {string | (function(): string)} name name of the logger, or function called once to get the logger name
227 * @returns {Logger} a logger with that name
228 */
229 getInfrastructureLogger(name) {
230 return this.compilers[0].getInfrastructureLogger(name);
231 }
232
233 /**
234 * @param {Compiler} compiler the child compiler
235 * @param {string[]} dependencies its dependencies
236 * @returns {void}
237 */
238 setDependencies(compiler, dependencies) {
239 this.dependencies.set(compiler, dependencies);
240 }
241
242 /**
243 * @param {Callback<MultiStats>} callback signals when the validation is complete
244 * @returns {boolean} true if the dependencies are valid
245 */
246 validateDependencies(callback) {
247 /** @type {Set<{source: Compiler, target: Compiler}>} */
248 const edges = new Set();
249 /** @type {string[]} */
250 const missing = [];
251 /**
252 * @param {Compiler} compiler compiler
253 * @returns {boolean} target was found
254 */
255 const targetFound = compiler => {
256 for (const edge of edges) {
257 if (edge.target === compiler) {
258 return true;
259 }
260 }
261 return false;
262 };
263 /**
264 * @param {{source: Compiler, target: Compiler}} e1 edge 1
265 * @param {{source: Compiler, target: Compiler}} e2 edge 2
266 * @returns {number} result
267 */
268 const sortEdges = (e1, e2) =>
269 /** @type {string} */
270 (e1.source.name).localeCompare(/** @type {string} */ (e2.source.name)) ||
271 /** @type {string} */
272 (e1.target.name).localeCompare(/** @type {string} */ (e2.target.name));
273 for (const source of this.compilers) {
274 const dependencies = this.dependencies.get(source);
275 if (dependencies) {
276 for (const dep of dependencies) {
277 const target = this.compilers.find(c => c.name === dep);
278 if (!target) {
279 missing.push(dep);
280 } else {
281 edges.add({
282 source,
283 target
284 });
285 }
286 }
287 }
288 }
289 /** @type {string[]} */
290 const errors = missing.map(m => `Compiler dependency \`${m}\` not found.`);
291 const stack = this.compilers.filter(c => !targetFound(c));
292 while (stack.length > 0) {
293 const current = stack.pop();
294 for (const edge of edges) {
295 if (edge.source === current) {
296 edges.delete(edge);
297 const target = edge.target;
298 if (!targetFound(target)) {
299 stack.push(target);
300 }
301 }
302 }
303 }
304 if (edges.size > 0) {
305 /** @type {string[]} */
306 const lines = Array.from(edges)
307 .sort(sortEdges)
308 .map(edge => `${edge.source.name} -> ${edge.target.name}`);
309 lines.unshift("Circular dependency found in compiler dependencies.");
310 errors.unshift(lines.join("\n"));
311 }
312 if (errors.length > 0) {
313 const message = errors.join("\n");
314 callback(new Error(message));
315 return false;
316 }
317 return true;
318 }
319
320 // TODO webpack 6 remove
321 /**
322 * @deprecated This method should have been private
323 * @param {Compiler[]} compilers the child compilers
324 * @param {RunWithDependenciesHandler} fn a handler to run for each compiler
325 * @param {Callback<MultiStats>} callback the compiler's handler
326 * @returns {void}
327 */
328 runWithDependencies(compilers, fn, callback) {
329 const fulfilledNames = new Set();
330 let remainingCompilers = compilers;
331 /**
332 * @param {string} d dependency
333 * @returns {boolean} when dependency was fulfilled
334 */
335 const isDependencyFulfilled = d => fulfilledNames.has(d);
336 /**
337 * @returns {Compiler[]} compilers
338 */
339 const getReadyCompilers = () => {
340 const readyCompilers = [];
341 const list = remainingCompilers;
342 remainingCompilers = [];
343 for (const c of list) {
344 const dependencies = this.dependencies.get(c);
345 const ready =
346 !dependencies || dependencies.every(isDependencyFulfilled);
347 if (ready) {
348 readyCompilers.push(c);
349 } else {
350 remainingCompilers.push(c);
351 }
352 }
353 return readyCompilers;
354 };
355 /**
356 * @param {Callback<MultiStats>} callback callback
357 * @returns {void}
358 */
359 const runCompilers = callback => {
360 if (remainingCompilers.length === 0) return callback(null);
361 asyncLib.map(
362 getReadyCompilers(),
363 (compiler, callback) => {
364 fn(compiler, err => {
365 if (err) return callback(err);
366 fulfilledNames.add(compiler.name);
367 runCompilers(callback);
368 });
369 },
370 (err, results) => {
371 callback(err, /** @type {TODO} */ (results));
372 }
373 );
374 };
375 runCompilers(callback);
376 }
377
378 /**
379 * @template SetupResult
380 * @param {function(Compiler, number, Callback<Stats>, function(): boolean, function(): void, function(): void): SetupResult} setup setup a single compiler
381 * @param {function(Compiler, SetupResult, Callback<Stats>): void} run run/continue a single compiler
382 * @param {Callback<MultiStats>} callback callback when all compilers are done, result includes Stats of all changed compilers
383 * @returns {SetupResult[]} result of setup
384 */
385 _runGraph(setup, run, callback) {
386 /** @typedef {{ compiler: Compiler, setupResult: undefined | SetupResult, result: undefined | Stats, state: "pending" | "blocked" | "queued" | "starting" | "running" | "running-outdated" | "done", children: Node[], parents: Node[] }} Node */
387
388 // State transitions for nodes:
389 // -> blocked (initial)
390 // blocked -> starting [running++] (when all parents done)
391 // queued -> starting [running++] (when processing the queue)
392 // starting -> running (when run has been called)
393 // running -> done [running--] (when compilation is done)
394 // done -> pending (when invalidated from file change)
395 // pending -> blocked [add to queue] (when invalidated from aggregated changes)
396 // done -> blocked [add to queue] (when invalidated, from parent invalidation)
397 // running -> running-outdated (when invalidated, either from change or parent invalidation)
398 // running-outdated -> blocked [running--] (when compilation is done)
399
400 /** @type {Node[]} */
401 const nodes = this.compilers.map(compiler => ({
402 compiler,
403 setupResult: undefined,
404 result: undefined,
405 state: "blocked",
406 children: [],
407 parents: []
408 }));
409 /** @type {Map<string, Node>} */
410 const compilerToNode = new Map();
411 for (const node of nodes) {
412 compilerToNode.set(/** @type {string} */ (node.compiler.name), node);
413 }
414 for (const node of nodes) {
415 const dependencies = this.dependencies.get(node.compiler);
416 if (!dependencies) continue;
417 for (const dep of dependencies) {
418 const parent = /** @type {Node} */ (compilerToNode.get(dep));
419 node.parents.push(parent);
420 parent.children.push(node);
421 }
422 }
423 /** @type {ArrayQueue<Node>} */
424 const queue = new ArrayQueue();
425 for (const node of nodes) {
426 if (node.parents.length === 0) {
427 node.state = "queued";
428 queue.enqueue(node);
429 }
430 }
431 let errored = false;
432 let running = 0;
433 const parallelism = /** @type {number} */ (this._options.parallelism);
434 /**
435 * @param {Node} node node
436 * @param {(Error | null)=} err error
437 * @param {Stats=} stats result
438 * @returns {void}
439 */
440 const nodeDone = (node, err, stats) => {
441 if (errored) return;
442 if (err) {
443 errored = true;
444 return asyncLib.each(
445 nodes,
446 (node, callback) => {
447 if (node.compiler.watching) {
448 node.compiler.watching.close(callback);
449 } else {
450 callback();
451 }
452 },
453 () => callback(err)
454 );
455 }
456 node.result = stats;
457 running--;
458 if (node.state === "running") {
459 node.state = "done";
460 for (const child of node.children) {
461 if (child.state === "blocked") queue.enqueue(child);
462 }
463 } else if (node.state === "running-outdated") {
464 node.state = "blocked";
465 queue.enqueue(node);
466 }
467 processQueue();
468 };
469 /**
470 * @param {Node} node node
471 * @returns {void}
472 */
473 const nodeInvalidFromParent = node => {
474 if (node.state === "done") {
475 node.state = "blocked";
476 } else if (node.state === "running") {
477 node.state = "running-outdated";
478 }
479 for (const child of node.children) {
480 nodeInvalidFromParent(child);
481 }
482 };
483 /**
484 * @param {Node} node node
485 * @returns {void}
486 */
487 const nodeInvalid = node => {
488 if (node.state === "done") {
489 node.state = "pending";
490 } else if (node.state === "running") {
491 node.state = "running-outdated";
492 }
493 for (const child of node.children) {
494 nodeInvalidFromParent(child);
495 }
496 };
497 /**
498 * @param {Node} node node
499 * @returns {void}
500 */
501 const nodeChange = node => {
502 nodeInvalid(node);
503 if (node.state === "pending") {
504 node.state = "blocked";
505 }
506 if (node.state === "blocked") {
507 queue.enqueue(node);
508 processQueue();
509 }
510 };
511
512 /** @type {SetupResult[]} */
513 const setupResults = [];
514 for (const [i, node] of nodes.entries()) {
515 setupResults.push(
516 (node.setupResult = setup(
517 node.compiler,
518 i,
519 nodeDone.bind(null, node),
520 () => node.state !== "starting" && node.state !== "running",
521 () => nodeChange(node),
522 () => nodeInvalid(node)
523 ))
524 );
525 }
526 let processing = true;
527 const processQueue = () => {
528 if (processing) return;
529 processing = true;
530 process.nextTick(processQueueWorker);
531 };
532 const processQueueWorker = () => {
533 // eslint-disable-next-line no-unmodified-loop-condition
534 while (running < parallelism && queue.length > 0 && !errored) {
535 const node = /** @type {Node} */ (queue.dequeue());
536 if (
537 node.state === "queued" ||
538 (node.state === "blocked" &&
539 node.parents.every(p => p.state === "done"))
540 ) {
541 running++;
542 node.state = "starting";
543 run(
544 node.compiler,
545 /** @type {SetupResult} */ (node.setupResult),
546 nodeDone.bind(null, node)
547 );
548 node.state = "running";
549 }
550 }
551 processing = false;
552 if (
553 !errored &&
554 running === 0 &&
555 nodes.every(node => node.state === "done")
556 ) {
557 const stats = [];
558 for (const node of nodes) {
559 const result = node.result;
560 if (result) {
561 node.result = undefined;
562 stats.push(result);
563 }
564 }
565 if (stats.length > 0) {
566 callback(null, new MultiStats(stats));
567 }
568 }
569 };
570 processQueueWorker();
571 return setupResults;
572 }
573
574 /**
575 * @param {WatchOptions|WatchOptions[]} watchOptions the watcher's options
576 * @param {Callback<MultiStats>} handler signals when the call finishes
577 * @returns {MultiWatching} a compiler watcher
578 */
579 watch(watchOptions, handler) {
580 if (this.running) {
581 return handler(new ConcurrentCompilationError());
582 }
583 this.running = true;
584
585 if (this.validateDependencies(handler)) {
586 const watchings = this._runGraph(
587 (compiler, idx, callback, isBlocked, setChanged, setInvalid) => {
588 const watching = compiler.watch(
589 Array.isArray(watchOptions) ? watchOptions[idx] : watchOptions,
590 callback
591 );
592 if (watching) {
593 watching._onInvalid = setInvalid;
594 watching._onChange = setChanged;
595 watching._isBlocked = isBlocked;
596 }
597 return watching;
598 },
599 (compiler, watching, callback) => {
600 if (compiler.watching !== watching) return;
601 if (!watching.running) watching.invalidate();
602 },
603 handler
604 );
605 return new MultiWatching(watchings, this);
606 }
607
608 return new MultiWatching([], this);
609 }
610
611 /**
612 * @param {Callback<MultiStats>} callback signals when the call finishes
613 * @returns {void}
614 */
615 run(callback) {
616 if (this.running) {
617 return callback(new ConcurrentCompilationError());
618 }
619 this.running = true;
620
621 if (this.validateDependencies(callback)) {
622 this._runGraph(
623 () => {},
624 (compiler, setupResult, callback) => compiler.run(callback),
625 (err, stats) => {
626 this.running = false;
627
628 if (callback !== undefined) {
629 return callback(err, stats);
630 }
631 }
632 );
633 }
634 }
635
636 purgeInputFileSystem() {
637 for (const compiler of this.compilers) {
638 if (compiler.inputFileSystem && compiler.inputFileSystem.purge) {
639 compiler.inputFileSystem.purge();
640 }
641 }
642 }
643
644 /**
645 * @param {Callback<void>} callback signals when the compiler closes
646 * @returns {void}
647 */
648 close(callback) {
649 asyncLib.each(
650 this.compilers,
651 (compiler, callback) => {
652 compiler.close(callback);
653 },
654 error => {
655 callback(error);
656 }
657 );
658 }
659};
Note: See TracBrowser for help on using the repository browser.