SyncModuleIdsPlugin.js 4.7 KB

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