SyncModuleIdsPlugin.js 4.6 KB

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