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