SyncModuleIdsPlugin.js 4.0 KB

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