DeterministicModuleIdsPlugin.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  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. assignDeterministicIds,
  11. getFullModuleName,
  12. getUsedModuleIdsAndModules
  13. } = require("./IdHelpers");
  14. /** @typedef {import("../Compiler")} Compiler */
  15. /** @typedef {import("../Module")} Module */
  16. /**
  17. * Defines the deterministic module ids plugin options type used by this module.
  18. * @typedef {object} DeterministicModuleIdsPluginOptions
  19. * @property {string=} context context relative to which module identifiers are computed
  20. * @property {((module: Module) => boolean)=} test selector function for modules
  21. * @property {number=} maxLength maximum id length in digits (used as starting point)
  22. * @property {number=} salt hash salt for ids
  23. * @property {boolean=} fixedLength do not increase the maxLength to find an optimal id space size
  24. * @property {boolean=} failOnConflict throw an error when id conflicts occur (instead of rehashing)
  25. */
  26. const PLUGIN_NAME = "DeterministicModuleIdsPlugin";
  27. class DeterministicModuleIdsPlugin {
  28. /**
  29. * Creates an instance of DeterministicModuleIdsPlugin.
  30. * @param {DeterministicModuleIdsPluginOptions=} options options
  31. */
  32. constructor(options = {}) {
  33. /** @type {DeterministicModuleIdsPluginOptions} */
  34. this.options = options;
  35. }
  36. /**
  37. * Applies the plugin by registering its hooks on the compiler.
  38. * @param {Compiler} compiler the compiler instance
  39. * @returns {void}
  40. */
  41. apply(compiler) {
  42. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  43. compilation.hooks.moduleIds.tap(PLUGIN_NAME, () => {
  44. const chunkGraph = compilation.chunkGraph;
  45. const context = this.options.context
  46. ? this.options.context
  47. : compiler.context;
  48. const maxLength = this.options.maxLength || 3;
  49. const failOnConflict = this.options.failOnConflict || false;
  50. const fixedLength = this.options.fixedLength || false;
  51. const salt = this.options.salt || 0;
  52. let conflicts = 0;
  53. const [usedIds, modules] = getUsedModuleIdsAndModules(
  54. compilation,
  55. this.options.test
  56. );
  57. assignDeterministicIds(
  58. modules,
  59. (module) => getFullModuleName(module, context, compiler.root),
  60. failOnConflict
  61. ? () => 0
  62. : compareModulesByPreOrderIndexOrIdentifier(
  63. compilation.moduleGraph
  64. ),
  65. (module, id) => {
  66. const size = usedIds.size;
  67. usedIds.add(`${id}`);
  68. if (size === usedIds.size) {
  69. conflicts++;
  70. return false;
  71. }
  72. chunkGraph.setModuleId(module, id);
  73. return true;
  74. },
  75. [10 ** maxLength],
  76. fixedLength ? 0 : 10,
  77. usedIds.size,
  78. salt
  79. );
  80. if (failOnConflict && conflicts) {
  81. throw new Error(
  82. `Assigning deterministic module ids has lead to ${conflicts} conflict${
  83. conflicts > 1 ? "s" : ""
  84. }.\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).`
  85. );
  86. }
  87. });
  88. });
  89. }
  90. }
  91. module.exports = DeterministicModuleIdsPlugin;