1 | /*
|
---|
2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
3 | Author Tobias Koppers @sokra
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | const parseJson = require("json-parse-even-better-errors");
|
---|
9 | const asyncLib = require("neo-async");
|
---|
10 | const {
|
---|
11 | SyncHook,
|
---|
12 | SyncBailHook,
|
---|
13 | AsyncParallelHook,
|
---|
14 | AsyncSeriesHook
|
---|
15 | } = require("tapable");
|
---|
16 | const { SizeOnlySource } = require("webpack-sources");
|
---|
17 | const webpack = require(".");
|
---|
18 | const Cache = require("./Cache");
|
---|
19 | const CacheFacade = require("./CacheFacade");
|
---|
20 | const ChunkGraph = require("./ChunkGraph");
|
---|
21 | const Compilation = require("./Compilation");
|
---|
22 | const ConcurrentCompilationError = require("./ConcurrentCompilationError");
|
---|
23 | const ContextModuleFactory = require("./ContextModuleFactory");
|
---|
24 | const ModuleGraph = require("./ModuleGraph");
|
---|
25 | const NormalModuleFactory = require("./NormalModuleFactory");
|
---|
26 | const RequestShortener = require("./RequestShortener");
|
---|
27 | const ResolverFactory = require("./ResolverFactory");
|
---|
28 | const Stats = require("./Stats");
|
---|
29 | const Watching = require("./Watching");
|
---|
30 | const WebpackError = require("./WebpackError");
|
---|
31 | const { Logger } = require("./logging/Logger");
|
---|
32 | const { join, dirname, mkdirp } = require("./util/fs");
|
---|
33 | const { makePathsRelative } = require("./util/identifier");
|
---|
34 | const { 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 | */
|
---|
107 | const 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 | */
|
---|
119 | const 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 | */
|
---|
133 | const 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 |
|
---|
141 | class 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.
|
---|
765 | This 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 |
|
---|
1384 | module.exports = Compiler;
|
---|