DeterministicModuleIdsPlugin.js 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Florent Cailhol @ooflorent
  4. */
  5. "use strict";
  6. const {
  7. compareModulesByPreOrderIndexOrIdentifier
  8. } = require("../util/comparators");
  9. const {
  10. getUsedModuleIdsAndModules,
  11. getFullModuleName,
  12. assignDeterministicIds
  13. } = require("./IdHelpers");
  14. /** @typedef {import("../Compiler")} Compiler */
  15. /** @typedef {import("../Module")} Module */
  16. /**
  17. * @typedef {object} DeterministicModuleIdsPluginOptions
  18. * @property {string=} context context relative to which module identifiers are computed
  19. * @property {function(Module): boolean=} test selector function for modules
  20. * @property {number=} maxLength maximum id length in digits (used as starting point)
  21. * @property {number=} salt hash salt for ids
  22. * @property {boolean=} fixedLength do not increase the maxLength to find an optimal id space size
  23. * @property {boolean=} failOnConflict throw an error when id conflicts occur (instead of rehashing)
  24. */
  25. class DeterministicModuleIdsPlugin {
  26. /**
  27. * @param {DeterministicModuleIdsPluginOptions} [options] options
  28. */
  29. constructor(options = {}) {
  30. this.options = options;
  31. }
  32. /**
  33. * Apply the plugin
  34. * @param {Compiler} compiler the compiler instance
  35. * @returns {void}
  36. */
  37. apply(compiler) {
  38. compiler.hooks.compilation.tap(
  39. "DeterministicModuleIdsPlugin",
  40. compilation => {
  41. compilation.hooks.moduleIds.tap("DeterministicModuleIdsPlugin", () => {
  42. const chunkGraph = compilation.chunkGraph;
  43. const context = this.options.context
  44. ? this.options.context
  45. : compiler.context;
  46. const maxLength = this.options.maxLength || 3;
  47. const failOnConflict = this.options.failOnConflict || false;
  48. const fixedLength = this.options.fixedLength || false;
  49. const salt = this.options.salt || 0;
  50. let conflicts = 0;
  51. const [usedIds, modules] = getUsedModuleIdsAndModules(
  52. compilation,
  53. this.options.test
  54. );
  55. assignDeterministicIds(
  56. modules,
  57. module => getFullModuleName(module, context, compiler.root),
  58. failOnConflict
  59. ? () => 0
  60. : compareModulesByPreOrderIndexOrIdentifier(
  61. compilation.moduleGraph
  62. ),
  63. (module, id) => {
  64. const size = usedIds.size;
  65. usedIds.add(`${id}`);
  66. if (size === usedIds.size) {
  67. conflicts++;
  68. return false;
  69. }
  70. chunkGraph.setModuleId(module, id);
  71. return true;
  72. },
  73. [Math.pow(10, maxLength)],
  74. fixedLength ? 0 : 10,
  75. usedIds.size,
  76. salt
  77. );
  78. if (failOnConflict && conflicts)
  79. throw new Error(
  80. `Assigning deterministic module ids has lead to ${conflicts} conflict${
  81. conflicts > 1 ? "s" : ""
  82. }.\nIncrease the 'maxLength' to increase the id space and make conflicts less likely (recommended when there are many conflicts or application is expected to grow), or add an 'salt' number to try another hash starting value in the same id space (recommended when there is only a single conflict).`
  83. );
  84. });
  85. }
  86. );
  87. }
  88. }
  89. module.exports = DeterministicModuleIdsPlugin;