source: trip-planner-front/node_modules/webpack/lib/Compiler.js@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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