Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

1383 строки
40 KiB

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