SyncModuleIdsPlugin.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { WebpackError } = require("..");
  7. const { getUsedModuleIdsAndModules } = require("./IdHelpers");
  8. /** @typedef {import("../Compiler")} Compiler */
  9. /** @typedef {import("../Module")} Module */
  10. /** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */
  11. const plugin = "SyncModuleIdsPlugin";
  12. class SyncModuleIdsPlugin {
  13. /**
  14. * @param {object} options options
  15. * @param {string} options.path path to file
  16. * @param {string=} options.context context for module names
  17. * @param {function(Module): boolean} options.test selector for modules
  18. * @param {"read" | "create" | "merge" | "update"=} options.mode operation mode (defaults to merge)
  19. */
  20. constructor({ path, context, test, mode }) {
  21. this._path = path;
  22. this._context = context;
  23. this._test = test || (() => true);
  24. const readAndWrite = !mode || mode === "merge" || mode === "update";
  25. this._read = readAndWrite || mode === "read";
  26. this._write = readAndWrite || mode === "create";
  27. this._prune = mode === "update";
  28. }
  29. /**
  30. * Apply the plugin
  31. * @param {Compiler} compiler the compiler instance
  32. * @returns {void}
  33. */
  34. apply(compiler) {
  35. /** @type {Map<string, string | number>} */
  36. let data;
  37. let dataChanged = false;
  38. if (this._read) {
  39. compiler.hooks.readRecords.tapAsync(plugin, callback => {
  40. const fs =
  41. /** @type {IntermediateFileSystem} */
  42. (compiler.intermediateFileSystem);
  43. fs.readFile(this._path, (err, buffer) => {
  44. if (err) {
  45. if (err.code !== "ENOENT") {
  46. return callback(err);
  47. }
  48. return callback();
  49. }
  50. const json = JSON.parse(/** @type {Buffer} */ (buffer).toString());
  51. data = new Map();
  52. for (const key of Object.keys(json)) {
  53. data.set(key, json[key]);
  54. }
  55. dataChanged = false;
  56. return callback();
  57. });
  58. });
  59. }
  60. if (this._write) {
  61. compiler.hooks.emitRecords.tapAsync(plugin, callback => {
  62. if (!data || !dataChanged) return callback();
  63. /** @type {{[key: string]: string | number}} */
  64. const json = {};
  65. const sorted = Array.from(data).sort(([a], [b]) => (a < b ? -1 : 1));
  66. for (const [key, value] of sorted) {
  67. json[key] = value;
  68. }
  69. const fs =
  70. /** @type {IntermediateFileSystem} */
  71. (compiler.intermediateFileSystem);
  72. fs.writeFile(this._path, JSON.stringify(json), callback);
  73. });
  74. }
  75. compiler.hooks.thisCompilation.tap(plugin, compilation => {
  76. const associatedObjectForCache = compiler.root;
  77. const context = this._context || compiler.context;
  78. if (this._read) {
  79. compilation.hooks.reviveModules.tap(plugin, (_1, _2) => {
  80. if (!data) return;
  81. const { chunkGraph } = compilation;
  82. const [usedIds, modules] = getUsedModuleIdsAndModules(
  83. compilation,
  84. this._test
  85. );
  86. for (const module of modules) {
  87. const name = module.libIdent({
  88. context,
  89. associatedObjectForCache
  90. });
  91. if (!name) continue;
  92. const id = data.get(name);
  93. const idAsString = `${id}`;
  94. if (usedIds.has(idAsString)) {
  95. const err = new WebpackError(
  96. `SyncModuleIdsPlugin: Unable to restore id '${id}' from '${this._path}' as it's already used.`
  97. );
  98. err.module = module;
  99. compilation.errors.push(err);
  100. }
  101. chunkGraph.setModuleId(module, /** @type {string | number} */ (id));
  102. usedIds.add(idAsString);
  103. }
  104. });
  105. }
  106. if (this._write) {
  107. compilation.hooks.recordModules.tap(plugin, modules => {
  108. const { chunkGraph } = compilation;
  109. let oldData = data;
  110. if (!oldData) {
  111. oldData = data = new Map();
  112. } else if (this._prune) {
  113. data = new Map();
  114. }
  115. for (const module of modules) {
  116. if (this._test(module)) {
  117. const name = module.libIdent({
  118. context,
  119. associatedObjectForCache
  120. });
  121. if (!name) continue;
  122. const id = chunkGraph.getModuleId(module);
  123. if (id === null) continue;
  124. const oldId = oldData.get(name);
  125. if (oldId !== id) {
  126. dataChanged = true;
  127. } else if (data === oldData) {
  128. continue;
  129. }
  130. data.set(name, id);
  131. }
  132. }
  133. if (data.size !== oldData.size) dataChanged = true;
  134. });
  135. }
  136. });
  137. }
  138. }
  139. module.exports = SyncModuleIdsPlugin;