SyncModuleIdsPlugin.js 4.3 KB

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