source: imaps-frontend/node_modules/webpack/lib/Compiler.js@ 79a0317

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

F4 Finalna Verzija

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