UNPKG

29 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const parseJson = require("json-parse-better-errors");
9const asyncLib = require("neo-async");
10const {
11 SyncHook,
12 SyncBailHook,
13 AsyncParallelHook,
14 AsyncSeriesHook
15} = require("tapable");
16const { SizeOnlySource } = require("webpack-sources");
17const webpack = require("./");
18const Cache = require("./Cache");
19const CacheFacade = require("./CacheFacade");
20const Compilation = require("./Compilation");
21const ConcurrentCompilationError = require("./ConcurrentCompilationError");
22const ContextModuleFactory = require("./ContextModuleFactory");
23const NormalModuleFactory = require("./NormalModuleFactory");
24const RequestShortener = require("./RequestShortener");
25const ResolverFactory = require("./ResolverFactory");
26const Stats = require("./Stats");
27const Watching = require("./Watching");
28const WebpackError = require("./WebpackError");
29const { Logger } = require("./logging/Logger");
30const { join, dirname, mkdirp } = require("./util/fs");
31const { makePathsRelative } = require("./util/identifier");
32
33/** @typedef {import("webpack-sources").Source} Source */
34/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} Entry */
35/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */
36/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */
37/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
38/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */
39/** @typedef {import("./Chunk")} Chunk */
40/** @typedef {import("./FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */
41/** @typedef {import("./Module")} Module */
42/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
43/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
44/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
45/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */
46
47/**
48 * @typedef {Object} CompilationParams
49 * @property {NormalModuleFactory} normalModuleFactory
50 * @property {ContextModuleFactory} contextModuleFactory
51 */
52
53/**
54 * @template T
55 * @callback Callback
56 * @param {Error=} err
57 * @param {T=} result
58 */
59
60/**
61 * @callback RunAsChildCallback
62 * @param {Error=} err
63 * @param {Chunk[]=} entries
64 * @param {Compilation=} compilation
65 */
66
67/**
68 * @typedef {Object} AssetEmittedInfo
69 * @property {Buffer} content
70 * @property {Source} source
71 * @property {Compilation} compilation
72 * @property {string} outputPath
73 * @property {string} targetPath
74 */
75
76/**
77 * @param {string[]} array an array
78 * @returns {boolean} true, if the array is sorted
79 */
80const isSorted = array => {
81 for (let i = 1; i < array.length; i++) {
82 if (array[i - 1] > array[i]) return false;
83 }
84 return true;
85};
86
87/**
88 * @param {Object} obj an object
89 * @param {string[]} keys the keys of the object
90 * @returns {Object} the object with properties sorted by property name
91 */
92const sortObject = (obj, keys) => {
93 const o = {};
94 for (const k of keys.sort()) {
95 o[k] = obj[k];
96 }
97 return o;
98};
99
100class Compiler {
101 /**
102 * @param {string} context the compilation path
103 */
104 constructor(context) {
105 this.hooks = Object.freeze({
106 /** @type {SyncHook<[]>} */
107 initialize: new SyncHook([]),
108
109 /** @type {SyncBailHook<[Compilation], boolean>} */
110 shouldEmit: new SyncBailHook(["compilation"]),
111 /** @type {AsyncSeriesHook<[Stats]>} */
112 done: new AsyncSeriesHook(["stats"]),
113 /** @type {SyncHook<[Stats]>} */
114 afterDone: new SyncHook(["stats"]),
115 /** @type {AsyncSeriesHook<[]>} */
116 additionalPass: new AsyncSeriesHook([]),
117 /** @type {AsyncSeriesHook<[Compiler]>} */
118 beforeRun: new AsyncSeriesHook(["compiler"]),
119 /** @type {AsyncSeriesHook<[Compiler]>} */
120 run: new AsyncSeriesHook(["compiler"]),
121 /** @type {AsyncSeriesHook<[Compilation]>} */
122 emit: new AsyncSeriesHook(["compilation"]),
123 /** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
124 assetEmitted: new AsyncSeriesHook(["file", "info"]),
125 /** @type {AsyncSeriesHook<[Compilation]>} */
126 afterEmit: new AsyncSeriesHook(["compilation"]),
127
128 /** @type {SyncHook<[Compilation, CompilationParams]>} */
129 thisCompilation: new SyncHook(["compilation", "params"]),
130 /** @type {SyncHook<[Compilation, CompilationParams]>} */
131 compilation: new SyncHook(["compilation", "params"]),
132 /** @type {SyncHook<[NormalModuleFactory]>} */
133 normalModuleFactory: new SyncHook(["normalModuleFactory"]),
134 /** @type {SyncHook<[ContextModuleFactory]>} */
135 contextModuleFactory: new SyncHook(["contextModuleFactory"]),
136
137 /** @type {AsyncSeriesHook<[CompilationParams]>} */
138 beforeCompile: new AsyncSeriesHook(["params"]),
139 /** @type {SyncHook<[CompilationParams]>} */
140 compile: new SyncHook(["params"]),
141 /** @type {AsyncParallelHook<[Compilation], Module>} */
142 make: new AsyncParallelHook(["compilation"]),
143 /** @type {AsyncParallelHook<[Compilation], Module>} */
144 finishMake: new AsyncSeriesHook(["compilation"]),
145 /** @type {AsyncSeriesHook<[Compilation]>} */
146 afterCompile: new AsyncSeriesHook(["compilation"]),
147
148 /** @type {AsyncSeriesHook<[Compiler]>} */
149 watchRun: new AsyncSeriesHook(["compiler"]),
150 /** @type {SyncHook<[Error]>} */
151 failed: new SyncHook(["error"]),
152 /** @type {SyncHook<[string | null, number]>} */
153 invalid: new SyncHook(["filename", "changeTime"]),
154 /** @type {SyncHook<[]>} */
155 watchClose: new SyncHook([]),
156
157 /** @type {SyncBailHook<[string, string, any[]], true>} */
158 infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
159
160 // TODO the following hooks are weirdly located here
161 // TODO move them for webpack 5
162 /** @type {SyncHook<[]>} */
163 environment: new SyncHook([]),
164 /** @type {SyncHook<[]>} */
165 afterEnvironment: new SyncHook([]),
166 /** @type {SyncHook<[Compiler]>} */
167 afterPlugins: new SyncHook(["compiler"]),
168 /** @type {SyncHook<[Compiler]>} */
169 afterResolvers: new SyncHook(["compiler"]),
170 /** @type {SyncBailHook<[string, Entry], boolean>} */
171 entryOption: new SyncBailHook(["context", "entry"])
172 });
173
174 this.webpack = webpack;
175
176 /** @type {string=} */
177 this.name = undefined;
178 /** @type {Compilation=} */
179 this.parentCompilation = undefined;
180 /** @type {Compiler} */
181 this.root = this;
182 /** @type {string} */
183 this.outputPath = "";
184 /** @type {Watching} */
185 this.watching = undefined;
186
187 /** @type {OutputFileSystem} */
188 this.outputFileSystem = null;
189 /** @type {IntermediateFileSystem} */
190 this.intermediateFileSystem = null;
191 /** @type {InputFileSystem} */
192 this.inputFileSystem = null;
193 /** @type {WatchFileSystem} */
194 this.watchFileSystem = null;
195
196 /** @type {string|null} */
197 this.recordsInputPath = null;
198 /** @type {string|null} */
199 this.recordsOutputPath = null;
200 this.records = {};
201 /** @type {Set<string>} */
202 this.managedPaths = new Set();
203 /** @type {Set<string>} */
204 this.immutablePaths = new Set();
205
206 /** @type {Set<string>} */
207 this.modifiedFiles = undefined;
208 /** @type {Set<string>} */
209 this.removedFiles = undefined;
210 /** @type {Map<string, FileSystemInfoEntry | null>} */
211 this.fileTimestamps = undefined;
212 /** @type {Map<string, FileSystemInfoEntry | null>} */
213 this.contextTimestamps = undefined;
214
215 /** @type {ResolverFactory} */
216 this.resolverFactory = new ResolverFactory();
217
218 this.infrastructureLogger = undefined;
219
220 /** @type {WebpackOptions} */
221 this.options = /** @type {WebpackOptions} */ ({});
222
223 this.context = context;
224
225 this.requestShortener = new RequestShortener(context, this.root);
226
227 this.cache = new Cache();
228
229 this.compilerPath = "";
230
231 /** @type {boolean} */
232 this.running = false;
233
234 /** @type {boolean} */
235 this.idle = false;
236
237 /** @type {boolean} */
238 this.watchMode = false;
239
240 /** @private @type {WeakMap<Source, { sizeOnlySource: SizeOnlySource, writtenTo: Map<string, number> }>} */
241 this._assetEmittingSourceCache = new WeakMap();
242 /** @private @type {Map<string, number>} */
243 this._assetEmittingWrittenFiles = new Map();
244 }
245
246 /**
247 * @param {string} name cache name
248 * @returns {CacheFacade} the cache facade instance
249 */
250 getCache(name) {
251 return new CacheFacade(this.cache, `${this.compilerPath}${name}`);
252 }
253
254 /**
255 * @param {string | (function(): string)} name name of the logger, or function called once to get the logger name
256 * @returns {Logger} a logger with that name
257 */
258 getInfrastructureLogger(name) {
259 if (!name) {
260 throw new TypeError(
261 "Compiler.getInfrastructureLogger(name) called without a name"
262 );
263 }
264 return new Logger(
265 (type, args) => {
266 if (typeof name === "function") {
267 name = name();
268 if (!name) {
269 throw new TypeError(
270 "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
271 );
272 }
273 }
274 if (this.hooks.infrastructureLog.call(name, type, args) === undefined) {
275 if (this.infrastructureLogger !== undefined) {
276 this.infrastructureLogger(name, type, args);
277 }
278 }
279 },
280 childName => {
281 if (typeof name === "function") {
282 if (typeof childName === "function") {
283 return this.getInfrastructureLogger(() => {
284 if (typeof name === "function") {
285 name = name();
286 if (!name) {
287 throw new TypeError(
288 "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
289 );
290 }
291 }
292 if (typeof childName === "function") {
293 childName = childName();
294 if (!childName) {
295 throw new TypeError(
296 "Logger.getChildLogger(name) called with a function not returning a name"
297 );
298 }
299 }
300 return `${name}/${childName}`;
301 });
302 } else {
303 return this.getInfrastructureLogger(() => {
304 if (typeof name === "function") {
305 name = name();
306 if (!name) {
307 throw new TypeError(
308 "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
309 );
310 }
311 }
312 return `${name}/${childName}`;
313 });
314 }
315 } else {
316 if (typeof childName === "function") {
317 return this.getInfrastructureLogger(() => {
318 if (typeof childName === "function") {
319 childName = childName();
320 if (!childName) {
321 throw new TypeError(
322 "Logger.getChildLogger(name) called with a function not returning a name"
323 );
324 }
325 }
326 return `${name}/${childName}`;
327 });
328 } else {
329 return this.getInfrastructureLogger(`${name}/${childName}`);
330 }
331 }
332 }
333 );
334 }
335
336 /**
337 * @param {WatchOptions} watchOptions the watcher's options
338 * @param {Callback<Stats>} handler signals when the call finishes
339 * @returns {Watching} a compiler watcher
340 */
341 watch(watchOptions, handler) {
342 if (this.running) {
343 return handler(new ConcurrentCompilationError());
344 }
345
346 this.running = true;
347 this.watchMode = true;
348 this.watching = new Watching(this, watchOptions, handler);
349 return this.watching;
350 }
351
352 /**
353 * @param {Callback<Stats>} callback signals when the call finishes
354 * @returns {void}
355 */
356 run(callback) {
357 if (this.running) {
358 return callback(new ConcurrentCompilationError());
359 }
360
361 let logger;
362
363 const finalCallback = (err, stats) => {
364 if (logger) logger.time("beginIdle");
365 this.idle = true;
366 this.cache.beginIdle();
367 this.idle = true;
368 if (logger) logger.timeEnd("beginIdle");
369 this.running = false;
370 if (err) {
371 this.hooks.failed.call(err);
372 }
373 if (callback !== undefined) callback(err, stats);
374 this.hooks.afterDone.call(stats);
375 };
376
377 const startTime = Date.now();
378
379 this.running = true;
380
381 const onCompiled = (err, compilation) => {
382 if (err) return finalCallback(err);
383
384 if (this.hooks.shouldEmit.call(compilation) === false) {
385 compilation.startTime = startTime;
386 compilation.endTime = Date.now();
387 const stats = new Stats(compilation);
388 this.hooks.done.callAsync(stats, err => {
389 if (err) return finalCallback(err);
390 return finalCallback(null, stats);
391 });
392 return;
393 }
394
395 process.nextTick(() => {
396 logger = compilation.getLogger("webpack.Compiler");
397 logger.time("emitAssets");
398 this.emitAssets(compilation, err => {
399 logger.timeEnd("emitAssets");
400 if (err) return finalCallback(err);
401
402 if (compilation.hooks.needAdditionalPass.call()) {
403 compilation.needAdditionalPass = true;
404
405 compilation.startTime = startTime;
406 compilation.endTime = Date.now();
407 logger.time("done hook");
408 const stats = new Stats(compilation);
409 this.hooks.done.callAsync(stats, err => {
410 logger.timeEnd("done hook");
411 if (err) return finalCallback(err);
412
413 this.hooks.additionalPass.callAsync(err => {
414 if (err) return finalCallback(err);
415 this.compile(onCompiled);
416 });
417 });
418 return;
419 }
420
421 logger.time("emitRecords");
422 this.emitRecords(err => {
423 logger.timeEnd("emitRecords");
424 if (err) return finalCallback(err);
425
426 compilation.startTime = startTime;
427 compilation.endTime = Date.now();
428 logger.time("done hook");
429 const stats = new Stats(compilation);
430 this.hooks.done.callAsync(stats, err => {
431 logger.timeEnd("done hook");
432 if (err) return finalCallback(err);
433 this.cache.storeBuildDependencies(
434 compilation.buildDependencies,
435 err => {
436 if (err) return finalCallback(err);
437 return finalCallback(null, stats);
438 }
439 );
440 });
441 });
442 });
443 });
444 };
445
446 const run = () => {
447 this.hooks.beforeRun.callAsync(this, err => {
448 if (err) return finalCallback(err);
449
450 this.hooks.run.callAsync(this, err => {
451 if (err) return finalCallback(err);
452
453 this.readRecords(err => {
454 if (err) return finalCallback(err);
455
456 this.compile(onCompiled);
457 });
458 });
459 });
460 };
461
462 if (this.idle) {
463 this.cache.endIdle(err => {
464 if (err) return finalCallback(err);
465
466 this.idle = false;
467 run();
468 });
469 } else {
470 run();
471 }
472 }
473
474 /**
475 * @param {RunAsChildCallback} callback signals when the call finishes
476 * @returns {void}
477 */
478 runAsChild(callback) {
479 const startTime = Date.now();
480 this.compile((err, compilation) => {
481 if (err) return callback(err);
482
483 this.parentCompilation.children.push(compilation);
484 for (const { name, source, info } of compilation.getAssets()) {
485 this.parentCompilation.emitAsset(name, source, info);
486 }
487
488 const entries = [];
489 for (const ep of compilation.entrypoints.values()) {
490 entries.push(...ep.chunks);
491 }
492
493 compilation.startTime = startTime;
494 compilation.endTime = Date.now();
495
496 return callback(null, entries, compilation);
497 });
498 }
499
500 purgeInputFileSystem() {
501 if (this.inputFileSystem && this.inputFileSystem.purge) {
502 this.inputFileSystem.purge();
503 }
504 }
505
506 /**
507 * @param {Compilation} compilation the compilation
508 * @param {Callback<void>} callback signals when the assets are emitted
509 * @returns {void}
510 */
511 emitAssets(compilation, callback) {
512 let outputPath;
513
514 const emitFiles = err => {
515 if (err) return callback(err);
516
517 const assets = compilation.getAssets();
518 compilation.assets = { ...compilation.assets };
519 const caseInsensitiveMap = new Map();
520 asyncLib.forEachLimit(
521 assets,
522 15,
523 ({ name: file, source, info }, callback) => {
524 let targetFile = file;
525 const queryStringIdx = targetFile.indexOf("?");
526 if (queryStringIdx >= 0) {
527 targetFile = targetFile.substr(0, queryStringIdx);
528 }
529
530 const writeOut = err => {
531 if (err) return callback(err);
532 const targetPath = join(
533 this.outputFileSystem,
534 outputPath,
535 targetFile
536 );
537
538 const caseInsensitiveTargetPath = targetPath.toLowerCase();
539 if (caseInsensitiveMap.has(caseInsensitiveTargetPath)) {
540 const other = caseInsensitiveMap.get(caseInsensitiveTargetPath);
541 const err = new WebpackError(`Prevent writing to file that only differs in casing or query string from already written file.
542This will lead to a race-condition and corrupted files on case-insensitive file systems.
543${targetPath}
544${other}`);
545 err.file = file;
546 return callback(err);
547 } else {
548 caseInsensitiveMap.set(caseInsensitiveTargetPath, targetPath);
549 }
550
551 // check if the target file has already been written by this Compiler
552 const targetFileGeneration = this._assetEmittingWrittenFiles.get(
553 targetPath
554 );
555
556 // create an cache entry for this Source if not already existing
557 let cacheEntry = this._assetEmittingSourceCache.get(source);
558 if (cacheEntry === undefined) {
559 cacheEntry = {
560 sizeOnlySource: undefined,
561 writtenTo: new Map()
562 };
563 this._assetEmittingSourceCache.set(source, cacheEntry);
564 }
565
566 /**
567 * get the binary (Buffer) content from the Source
568 * @returns {Buffer} content for the source
569 */
570 const getContent = () => {
571 if (typeof source.buffer === "function") {
572 return source.buffer();
573 } else {
574 const bufferOrString = source.source();
575 if (Buffer.isBuffer(bufferOrString)) {
576 return bufferOrString;
577 } else {
578 return Buffer.from(bufferOrString, "utf8");
579 }
580 }
581 };
582
583 const alreadyWritten = () => {
584 // cache the information that the Source has been already been written to that location
585 if (targetFileGeneration === undefined) {
586 const newGeneration = 1;
587 this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
588 cacheEntry.writtenTo.set(targetPath, newGeneration);
589 } else {
590 cacheEntry.writtenTo.set(targetPath, targetFileGeneration);
591 }
592 callback();
593 };
594
595 /**
596 * Write the file to output file system
597 * @param {Buffer} content content to be written
598 * @returns {void}
599 */
600 const doWrite = content => {
601 this.outputFileSystem.writeFile(targetPath, content, err => {
602 if (err) return callback(err);
603
604 // information marker that the asset has been emitted
605 compilation.emittedAssets.add(file);
606
607 // cache the information that the Source has been written to that location
608 const newGeneration =
609 targetFileGeneration === undefined
610 ? 1
611 : targetFileGeneration + 1;
612 cacheEntry.writtenTo.set(targetPath, newGeneration);
613 this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
614 this.hooks.assetEmitted.callAsync(
615 file,
616 {
617 content,
618 source,
619 outputPath,
620 compilation,
621 targetPath
622 },
623 callback
624 );
625 });
626 };
627
628 const updateWithReplacementSource = size => {
629 // Create a replacement resource which only allows to ask for size
630 // This allows to GC all memory allocated by the Source
631 // (expect when the Source is stored in any other cache)
632 if (!cacheEntry.sizeOnlySource) {
633 cacheEntry.sizeOnlySource = new SizeOnlySource(size);
634 }
635 compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
636 size
637 });
638 };
639
640 const processExistingFile = stats => {
641 // skip emitting if it's already there and an immutable file
642 if (info.immutable) {
643 updateWithReplacementSource(stats.size);
644 return alreadyWritten();
645 }
646
647 const content = getContent();
648
649 updateWithReplacementSource(content.length);
650
651 // if it exists and content on disk matches content
652 // skip writing the same content again
653 // (to keep mtime and don't trigger watchers)
654 // for a fast negative match file size is compared first
655 if (content.length === stats.size) {
656 compilation.comparedForEmitAssets.add(file);
657 return this.outputFileSystem.readFile(
658 targetPath,
659 (err, existingContent) => {
660 if (err || !content.equals(existingContent)) {
661 return doWrite(content);
662 } else {
663 return alreadyWritten();
664 }
665 }
666 );
667 }
668
669 return doWrite(content);
670 };
671
672 const processMissingFile = () => {
673 const content = getContent();
674
675 updateWithReplacementSource(content.length);
676
677 return doWrite(content);
678 };
679
680 // if the target file has already been written
681 if (targetFileGeneration !== undefined) {
682 // check if the Source has been written to this target file
683 const writtenGeneration = cacheEntry.writtenTo.get(targetPath);
684 if (writtenGeneration === targetFileGeneration) {
685 // if yes, we skip writing the file
686 // as it's already there
687 // (we assume one doesn't remove files while the Compiler is running)
688
689 compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
690 size: cacheEntry.sizeOnlySource.size()
691 });
692
693 return callback();
694 }
695
696 if (!info.immutable) {
697 // We wrote to this file before which has very likely a different content
698 // skip comparing and assume content is different for performance
699 // This case happens often during watch mode.
700 return processMissingFile();
701 }
702 }
703
704 if (this.options.output.compareBeforeEmit) {
705 this.outputFileSystem.stat(targetPath, (err, stats) => {
706 const exists = !err && stats.isFile();
707
708 if (exists) {
709 processExistingFile(stats);
710 } else {
711 processMissingFile();
712 }
713 });
714 } else {
715 processMissingFile();
716 }
717 };
718
719 if (targetFile.match(/\/|\\/)) {
720 const fs = this.outputFileSystem;
721 const dir = dirname(fs, join(fs, outputPath, targetFile));
722 mkdirp(fs, dir, writeOut);
723 } else {
724 writeOut();
725 }
726 },
727 err => {
728 if (err) return callback(err);
729
730 this.hooks.afterEmit.callAsync(compilation, err => {
731 if (err) return callback(err);
732
733 return callback();
734 });
735 }
736 );
737 };
738
739 this.hooks.emit.callAsync(compilation, err => {
740 if (err) return callback(err);
741 outputPath = compilation.getPath(this.outputPath, {});
742 mkdirp(this.outputFileSystem, outputPath, emitFiles);
743 });
744 }
745
746 /**
747 * @param {Callback<void>} callback signals when the call finishes
748 * @returns {void}
749 */
750 emitRecords(callback) {
751 if (!this.recordsOutputPath) return callback();
752
753 const writeFile = () => {
754 this.outputFileSystem.writeFile(
755 this.recordsOutputPath,
756 JSON.stringify(
757 this.records,
758 (n, value) => {
759 if (
760 typeof value === "object" &&
761 value !== null &&
762 !Array.isArray(value)
763 ) {
764 const keys = Object.keys(value);
765 if (!isSorted(keys)) {
766 return sortObject(value, keys);
767 }
768 }
769 return value;
770 },
771 2
772 ),
773 callback
774 );
775 };
776
777 const recordsOutputPathDirectory = dirname(
778 this.outputFileSystem,
779 this.recordsOutputPath
780 );
781 if (!recordsOutputPathDirectory) {
782 return writeFile();
783 }
784 mkdirp(this.outputFileSystem, recordsOutputPathDirectory, err => {
785 if (err) return callback(err);
786 writeFile();
787 });
788 }
789
790 /**
791 * @param {Callback<void>} callback signals when the call finishes
792 * @returns {void}
793 */
794 readRecords(callback) {
795 if (!this.recordsInputPath) {
796 this.records = {};
797 return callback();
798 }
799 this.inputFileSystem.stat(this.recordsInputPath, err => {
800 // It doesn't exist
801 // We can ignore this.
802 if (err) return callback();
803
804 this.inputFileSystem.readFile(this.recordsInputPath, (err, content) => {
805 if (err) return callback(err);
806
807 try {
808 this.records = parseJson(content.toString("utf-8"));
809 } catch (e) {
810 e.message = "Cannot parse records: " + e.message;
811 return callback(e);
812 }
813
814 return callback();
815 });
816 });
817 }
818
819 /**
820 * @param {Compilation} compilation the compilation
821 * @param {string} compilerName the compiler's name
822 * @param {number} compilerIndex the compiler's index
823 * @param {OutputOptions} outputOptions the output options
824 * @param {WebpackPluginInstance[]} plugins the plugins to apply
825 * @returns {Compiler} a child compiler
826 */
827 createChildCompiler(
828 compilation,
829 compilerName,
830 compilerIndex,
831 outputOptions,
832 plugins
833 ) {
834 const childCompiler = new Compiler(this.context);
835 childCompiler.name = compilerName;
836 childCompiler.outputPath = this.outputPath;
837 childCompiler.inputFileSystem = this.inputFileSystem;
838 childCompiler.outputFileSystem = null;
839 childCompiler.resolverFactory = this.resolverFactory;
840 childCompiler.modifiedFiles = this.modifiedFiles;
841 childCompiler.removedFiles = this.removedFiles;
842 childCompiler.fileTimestamps = this.fileTimestamps;
843 childCompiler.contextTimestamps = this.contextTimestamps;
844 childCompiler.cache = this.cache;
845 childCompiler.compilerPath = `${this.compilerPath}${compilerName}|${compilerIndex}|`;
846
847 const relativeCompilerName = makePathsRelative(
848 this.context,
849 compilerName,
850 this.root
851 );
852 if (!this.records[relativeCompilerName]) {
853 this.records[relativeCompilerName] = [];
854 }
855 if (this.records[relativeCompilerName][compilerIndex]) {
856 childCompiler.records = this.records[relativeCompilerName][compilerIndex];
857 } else {
858 this.records[relativeCompilerName].push((childCompiler.records = {}));
859 }
860
861 childCompiler.options = {
862 ...this.options,
863 output: {
864 ...this.options.output,
865 ...outputOptions
866 }
867 };
868 childCompiler.parentCompilation = compilation;
869 childCompiler.root = this.root;
870 if (Array.isArray(plugins)) {
871 for (const plugin of plugins) {
872 plugin.apply(childCompiler);
873 }
874 }
875 for (const name in this.hooks) {
876 if (
877 ![
878 "make",
879 "compile",
880 "emit",
881 "afterEmit",
882 "invalid",
883 "done",
884 "thisCompilation"
885 ].includes(name)
886 ) {
887 if (childCompiler.hooks[name]) {
888 childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
889 }
890 }
891 }
892
893 compilation.hooks.childCompiler.call(
894 childCompiler,
895 compilerName,
896 compilerIndex
897 );
898
899 return childCompiler;
900 }
901
902 isChild() {
903 return !!this.parentCompilation;
904 }
905
906 createCompilation() {
907 return new Compilation(this);
908 }
909
910 /**
911 * @param {CompilationParams} params the compilation parameters
912 * @returns {Compilation} the created compilation
913 */
914 newCompilation(params) {
915 const compilation = this.createCompilation();
916 compilation.name = this.name;
917 compilation.records = this.records;
918 this.hooks.thisCompilation.call(compilation, params);
919 this.hooks.compilation.call(compilation, params);
920 return compilation;
921 }
922
923 createNormalModuleFactory() {
924 const normalModuleFactory = new NormalModuleFactory({
925 context: this.options.context,
926 fs: this.inputFileSystem,
927 resolverFactory: this.resolverFactory,
928 options: this.options.module || {},
929 associatedObjectForCache: this.root
930 });
931 this.hooks.normalModuleFactory.call(normalModuleFactory);
932 return normalModuleFactory;
933 }
934
935 createContextModuleFactory() {
936 const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
937 this.hooks.contextModuleFactory.call(contextModuleFactory);
938 return contextModuleFactory;
939 }
940
941 newCompilationParams() {
942 const params = {
943 normalModuleFactory: this.createNormalModuleFactory(),
944 contextModuleFactory: this.createContextModuleFactory()
945 };
946 return params;
947 }
948
949 /**
950 * @param {Callback<Compilation>} callback signals when the compilation finishes
951 * @returns {void}
952 */
953 compile(callback) {
954 const params = this.newCompilationParams();
955 this.hooks.beforeCompile.callAsync(params, err => {
956 if (err) return callback(err);
957
958 this.hooks.compile.call(params);
959
960 const compilation = this.newCompilation(params);
961
962 const logger = compilation.getLogger("webpack.Compiler");
963
964 logger.time("make hook");
965 this.hooks.make.callAsync(compilation, err => {
966 logger.timeEnd("make hook");
967 if (err) return callback(err);
968
969 logger.time("finish make hook");
970 this.hooks.finishMake.callAsync(compilation, err => {
971 logger.timeEnd("finish make hook");
972 if (err) return callback(err);
973
974 process.nextTick(() => {
975 logger.time("finish compilation");
976 compilation.finish(err => {
977 logger.timeEnd("finish compilation");
978 if (err) return callback(err);
979
980 logger.time("seal compilation");
981 compilation.seal(err => {
982 logger.timeEnd("seal compilation");
983 if (err) return callback(err);
984
985 logger.time("afterCompile hook");
986 this.hooks.afterCompile.callAsync(compilation, err => {
987 logger.timeEnd("afterCompile hook");
988 if (err) return callback(err);
989
990 return callback(null, compilation);
991 });
992 });
993 });
994 });
995 });
996 });
997 });
998 }
999
1000 /**
1001 * @param {Callback<void>} callback signals when the compiler closes
1002 * @returns {void}
1003 */
1004 close(callback) {
1005 this.cache.shutdown(callback);
1006 }
1007}
1008
1009module.exports = Compiler;