1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const asyncLib = require("neo-async");
|
9 | const { SyncHook, MultiHook } = require("tapable");
|
10 |
|
11 | const ConcurrentCompilationError = require("./ConcurrentCompilationError");
|
12 | const MultiStats = require("./MultiStats");
|
13 | const MultiWatching = require("./MultiWatching");
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | const STATUS_PENDING = 0;
|
29 | const STATUS_DONE = 1;
|
30 | const STATUS_NEW = 2;
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | module.exports = class MultiCompiler {
|
46 | |
47 |
|
48 |
|
49 | constructor(compilers) {
|
50 | if (!Array.isArray(compilers)) {
|
51 | compilers = Object.keys(compilers).map(name => {
|
52 | compilers[name].name = name;
|
53 | return compilers[name];
|
54 | });
|
55 | }
|
56 |
|
57 | this.hooks = Object.freeze({
|
58 |
|
59 | done: new SyncHook(["stats"]),
|
60 |
|
61 | invalid: new MultiHook(compilers.map(c => c.hooks.invalid)),
|
62 |
|
63 | run: new MultiHook(compilers.map(c => c.hooks.run)),
|
64 |
|
65 | watchClose: new SyncHook([]),
|
66 |
|
67 | watchRun: new MultiHook(compilers.map(c => c.hooks.watchRun)),
|
68 |
|
69 | infrastructureLog: new MultiHook(
|
70 | compilers.map(c => c.hooks.infrastructureLog)
|
71 | )
|
72 | });
|
73 | this.compilers = compilers;
|
74 |
|
75 | this.dependencies = new WeakMap();
|
76 | this.running = false;
|
77 |
|
78 |
|
79 | const compilerStats = this.compilers.map(() => null);
|
80 | let doneCompilers = 0;
|
81 | for (let index = 0; index < this.compilers.length; index++) {
|
82 | const compiler = this.compilers[index];
|
83 | const compilerIndex = index;
|
84 | let compilerDone = false;
|
85 |
|
86 | compiler.hooks.done.tap("MultiCompiler", stats => {
|
87 | if (!compilerDone) {
|
88 | compilerDone = true;
|
89 | doneCompilers++;
|
90 | }
|
91 | compilerStats[compilerIndex] = stats;
|
92 | if (doneCompilers === this.compilers.length) {
|
93 | this.hooks.done.call(new MultiStats(compilerStats));
|
94 | }
|
95 | });
|
96 |
|
97 | compiler.hooks.invalid.tap("MultiCompiler", () => {
|
98 | if (compilerDone) {
|
99 | compilerDone = false;
|
100 | doneCompilers--;
|
101 | }
|
102 | });
|
103 | }
|
104 | }
|
105 |
|
106 | get options() {
|
107 | return this.compilers.map(c => c.options);
|
108 | }
|
109 |
|
110 | get outputPath() {
|
111 | let commonPath = this.compilers[0].outputPath;
|
112 | for (const compiler of this.compilers) {
|
113 | while (
|
114 | compiler.outputPath.indexOf(commonPath) !== 0 &&
|
115 | /[/\\]/.test(commonPath)
|
116 | ) {
|
117 | commonPath = commonPath.replace(/[/\\][^/\\]*$/, "");
|
118 | }
|
119 | }
|
120 |
|
121 | if (!commonPath && this.compilers[0].outputPath[0] === "/") return "/";
|
122 | return commonPath;
|
123 | }
|
124 |
|
125 | get inputFileSystem() {
|
126 | throw new Error("Cannot read inputFileSystem of a MultiCompiler");
|
127 | }
|
128 |
|
129 | get outputFileSystem() {
|
130 | throw new Error("Cannot read outputFileSystem of a MultiCompiler");
|
131 | }
|
132 |
|
133 | get watchFileSystem() {
|
134 | throw new Error("Cannot read watchFileSystem of a MultiCompiler");
|
135 | }
|
136 |
|
137 | get intermediateFileSystem() {
|
138 | throw new Error("Cannot read outputFileSystem of a MultiCompiler");
|
139 | }
|
140 |
|
141 | |
142 |
|
143 |
|
144 | set inputFileSystem(value) {
|
145 | for (const compiler of this.compilers) {
|
146 | compiler.inputFileSystem = value;
|
147 | }
|
148 | }
|
149 |
|
150 | |
151 |
|
152 |
|
153 | set outputFileSystem(value) {
|
154 | for (const compiler of this.compilers) {
|
155 | compiler.outputFileSystem = value;
|
156 | }
|
157 | }
|
158 |
|
159 | |
160 |
|
161 |
|
162 | set watchFileSystem(value) {
|
163 | for (const compiler of this.compilers) {
|
164 | compiler.watchFileSystem = value;
|
165 | }
|
166 | }
|
167 |
|
168 | |
169 |
|
170 |
|
171 | set intermediateFileSystem(value) {
|
172 | for (const compiler of this.compilers) {
|
173 | compiler.intermediateFileSystem = value;
|
174 | }
|
175 | }
|
176 |
|
177 | getInfrastructureLogger(name) {
|
178 | return this.compilers[0].getInfrastructureLogger(name);
|
179 | }
|
180 |
|
181 | |
182 |
|
183 |
|
184 |
|
185 |
|
186 | setDependencies(compiler, dependencies) {
|
187 | this.dependencies.set(compiler, dependencies);
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 |
|
194 | validateDependencies(callback) {
|
195 |
|
196 | const edges = new Set();
|
197 |
|
198 | const missing = [];
|
199 | const targetFound = compiler => {
|
200 | for (const edge of edges) {
|
201 | if (edge.target === compiler) {
|
202 | return true;
|
203 | }
|
204 | }
|
205 | return false;
|
206 | };
|
207 | const sortEdges = (e1, e2) => {
|
208 | return (
|
209 | e1.source.name.localeCompare(e2.source.name) ||
|
210 | e1.target.name.localeCompare(e2.target.name)
|
211 | );
|
212 | };
|
213 | for (const source of this.compilers) {
|
214 | const dependencies = this.dependencies.get(source);
|
215 | if (dependencies) {
|
216 | for (const dep of dependencies) {
|
217 | const target = this.compilers.find(c => c.name === dep);
|
218 | if (!target) {
|
219 | missing.push(dep);
|
220 | } else {
|
221 | edges.add({
|
222 | source,
|
223 | target
|
224 | });
|
225 | }
|
226 | }
|
227 | }
|
228 | }
|
229 | const errors = missing.map(m => `Compiler dependency \`${m}\` not found.`);
|
230 | const stack = this.compilers.filter(c => !targetFound(c));
|
231 | while (stack.length > 0) {
|
232 | const current = stack.pop();
|
233 | for (const edge of edges) {
|
234 | if (edge.source === current) {
|
235 | edges.delete(edge);
|
236 | const target = edge.target;
|
237 | if (!targetFound(target)) {
|
238 | stack.push(target);
|
239 | }
|
240 | }
|
241 | }
|
242 | }
|
243 | if (edges.size > 0) {
|
244 | const lines = Array.from(edges)
|
245 | .sort(sortEdges)
|
246 | .map(edge => `${edge.source.name} -> ${edge.target.name}`);
|
247 | lines.unshift("Circular dependency found in compiler dependencies.");
|
248 | errors.unshift(lines.join("\n"));
|
249 | }
|
250 | if (errors.length > 0) {
|
251 | const message = errors.join("\n");
|
252 | callback(new Error(message));
|
253 | return false;
|
254 | }
|
255 | return true;
|
256 | }
|
257 |
|
258 | |
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 | runWithDependencies(compilers, fn, callback) {
|
265 | const fulfilledNames = new Set();
|
266 | let remainingCompilers = compilers;
|
267 | const isDependencyFulfilled = d => fulfilledNames.has(d);
|
268 | const getReadyCompilers = () => {
|
269 | let readyCompilers = [];
|
270 | let list = remainingCompilers;
|
271 | remainingCompilers = [];
|
272 | for (const c of list) {
|
273 | const dependencies = this.dependencies.get(c);
|
274 | const ready =
|
275 | !dependencies || dependencies.every(isDependencyFulfilled);
|
276 | if (ready) {
|
277 | readyCompilers.push(c);
|
278 | } else {
|
279 | remainingCompilers.push(c);
|
280 | }
|
281 | }
|
282 | return readyCompilers;
|
283 | };
|
284 | const runCompilers = callback => {
|
285 | if (remainingCompilers.length === 0) return callback();
|
286 | asyncLib.map(
|
287 | getReadyCompilers(),
|
288 | (compiler, callback) => {
|
289 | fn(compiler, err => {
|
290 | if (err) return callback(err);
|
291 | fulfilledNames.add(compiler.name);
|
292 | runCompilers(callback);
|
293 | });
|
294 | },
|
295 | callback
|
296 | );
|
297 | };
|
298 | runCompilers(callback);
|
299 | }
|
300 |
|
301 | |
302 |
|
303 |
|
304 |
|
305 |
|
306 | watch(watchOptions, handler) {
|
307 | if (this.running) {
|
308 | return handler(new ConcurrentCompilationError());
|
309 | }
|
310 |
|
311 |
|
312 | const watchings = [];
|
313 |
|
314 |
|
315 | const allStats = this.compilers.map(() => null);
|
316 |
|
317 |
|
318 | const compilerStatus = this.compilers.map(() => STATUS_PENDING);
|
319 |
|
320 | if (this.validateDependencies(handler)) {
|
321 | this.running = true;
|
322 | this.runWithDependencies(
|
323 | this.compilers,
|
324 | (compiler, callback) => {
|
325 | const compilerIdx = this.compilers.indexOf(compiler);
|
326 | let firstRun = true;
|
327 | let watching = compiler.watch(
|
328 | Array.isArray(watchOptions)
|
329 | ? watchOptions[compilerIdx]
|
330 | : watchOptions,
|
331 | (err, stats) => {
|
332 | if (err) handler(err);
|
333 | if (stats) {
|
334 | allStats[compilerIdx] = stats;
|
335 | compilerStatus[compilerIdx] = STATUS_NEW;
|
336 | if (compilerStatus.every(status => status !== STATUS_PENDING)) {
|
337 | const freshStats = allStats.filter((s, idx) => {
|
338 | return compilerStatus[idx] === STATUS_NEW;
|
339 | });
|
340 | compilerStatus.fill(STATUS_DONE);
|
341 | const multiStats = new MultiStats(freshStats);
|
342 | handler(null, multiStats);
|
343 | }
|
344 | }
|
345 | if (firstRun && !err) {
|
346 | firstRun = false;
|
347 | callback();
|
348 | }
|
349 | }
|
350 | );
|
351 | watchings.push(watching);
|
352 | },
|
353 | () => {
|
354 |
|
355 | }
|
356 | );
|
357 | }
|
358 |
|
359 | return new MultiWatching(watchings, this);
|
360 | }
|
361 |
|
362 | |
363 |
|
364 |
|
365 |
|
366 | run(callback) {
|
367 | if (this.running) {
|
368 | return callback(new ConcurrentCompilationError());
|
369 | }
|
370 |
|
371 | const finalCallback = (err, stats) => {
|
372 | this.running = false;
|
373 |
|
374 | if (callback !== undefined) {
|
375 | return callback(err, stats);
|
376 | }
|
377 | };
|
378 |
|
379 | const allStats = this.compilers.map(() => null);
|
380 | if (this.validateDependencies(callback)) {
|
381 | this.running = true;
|
382 | this.runWithDependencies(
|
383 | this.compilers,
|
384 | (compiler, callback) => {
|
385 | const compilerIdx = this.compilers.indexOf(compiler);
|
386 | compiler.run((err, stats) => {
|
387 | if (err) {
|
388 | return callback(err);
|
389 | }
|
390 | allStats[compilerIdx] = stats;
|
391 | callback();
|
392 | });
|
393 | },
|
394 | err => {
|
395 | if (err) {
|
396 | return finalCallback(err);
|
397 | }
|
398 | finalCallback(null, new MultiStats(allStats));
|
399 | }
|
400 | );
|
401 | }
|
402 | }
|
403 |
|
404 | purgeInputFileSystem() {
|
405 | for (const compiler of this.compilers) {
|
406 | if (compiler.inputFileSystem && compiler.inputFileSystem.purge) {
|
407 | compiler.inputFileSystem.purge();
|
408 | }
|
409 | }
|
410 | }
|
411 |
|
412 | |
413 |
|
414 |
|
415 |
|
416 | close(callback) {
|
417 | asyncLib.each(
|
418 | this.compilers,
|
419 | (compiler, callback) => {
|
420 | compiler.close(callback);
|
421 | },
|
422 | callback
|
423 | );
|
424 | }
|
425 | };
|