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