RecordIdsPlugin.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { compareNumbers } = require("./util/comparators");
  7. const identifierUtils = require("./util/identifier");
  8. /** @typedef {import("./Chunk")} Chunk */
  9. /** @typedef {import("./Compiler")} Compiler */
  10. /** @typedef {import("./Module")} Module */
  11. /**
  12. * @typedef {object} RecordsChunks
  13. * @property {Record<string, number>=} byName
  14. * @property {Record<string, number>=} bySource
  15. * @property {number[]=} usedIds
  16. */
  17. /**
  18. * @typedef {object} RecordsModules
  19. * @property {Record<string, number>=} byIdentifier
  20. * @property {Record<string, number>=} bySource
  21. * @property {number[]=} usedIds
  22. */
  23. /**
  24. * @typedef {object} Records
  25. * @property {RecordsChunks=} chunks
  26. * @property {RecordsModules=} modules
  27. */
  28. /**
  29. * @typedef {object} RecordIdsPluginOptions
  30. * @property {boolean=} portableIds true, when ids need to be portable
  31. */
  32. const PLUGIN_NAME = "RecordIdsPlugin";
  33. class RecordIdsPlugin {
  34. /**
  35. * @param {RecordIdsPluginOptions=} options object
  36. */
  37. constructor(options) {
  38. this.options = options || {};
  39. }
  40. /**
  41. * @param {Compiler} compiler the Compiler
  42. * @returns {void}
  43. */
  44. apply(compiler) {
  45. const portableIds = this.options.portableIds;
  46. const makePathsRelative =
  47. identifierUtils.makePathsRelative.bindContextCache(
  48. compiler.context,
  49. compiler.root
  50. );
  51. /**
  52. * @param {Module} module the module
  53. * @returns {string} the (portable) identifier
  54. */
  55. const getModuleIdentifier = module => {
  56. if (portableIds) {
  57. return makePathsRelative(module.identifier());
  58. }
  59. return module.identifier();
  60. };
  61. compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
  62. compilation.hooks.recordModules.tap(PLUGIN_NAME, (modules, records) => {
  63. const chunkGraph = compilation.chunkGraph;
  64. if (!records.modules) records.modules = {};
  65. if (!records.modules.byIdentifier) records.modules.byIdentifier = {};
  66. /** @type {Set<number>} */
  67. const usedIds = new Set();
  68. for (const module of modules) {
  69. const moduleId = chunkGraph.getModuleId(module);
  70. if (typeof moduleId !== "number") continue;
  71. const identifier = getModuleIdentifier(module);
  72. records.modules.byIdentifier[identifier] = moduleId;
  73. usedIds.add(moduleId);
  74. }
  75. records.modules.usedIds = Array.from(usedIds).sort(compareNumbers);
  76. });
  77. compilation.hooks.reviveModules.tap(PLUGIN_NAME, (modules, records) => {
  78. if (!records.modules) return;
  79. if (records.modules.byIdentifier) {
  80. const chunkGraph = compilation.chunkGraph;
  81. /** @type {Set<number>} */
  82. const usedIds = new Set();
  83. for (const module of modules) {
  84. const moduleId = chunkGraph.getModuleId(module);
  85. if (moduleId !== null) continue;
  86. const identifier = getModuleIdentifier(module);
  87. const id = records.modules.byIdentifier[identifier];
  88. if (id === undefined) continue;
  89. if (usedIds.has(id)) continue;
  90. usedIds.add(id);
  91. chunkGraph.setModuleId(module, id);
  92. }
  93. }
  94. if (Array.isArray(records.modules.usedIds)) {
  95. compilation.usedModuleIds = new Set(records.modules.usedIds);
  96. }
  97. });
  98. /**
  99. * @param {Chunk} chunk the chunk
  100. * @returns {string[]} sources of the chunk
  101. */
  102. const getChunkSources = chunk => {
  103. /** @type {string[]} */
  104. const sources = [];
  105. for (const chunkGroup of chunk.groupsIterable) {
  106. const index = chunkGroup.chunks.indexOf(chunk);
  107. if (chunkGroup.name) {
  108. sources.push(`${index} ${chunkGroup.name}`);
  109. } else {
  110. for (const origin of chunkGroup.origins) {
  111. if (origin.module) {
  112. if (origin.request) {
  113. sources.push(
  114. `${index} ${getModuleIdentifier(origin.module)} ${
  115. origin.request
  116. }`
  117. );
  118. } else if (typeof origin.loc === "string") {
  119. sources.push(
  120. `${index} ${getModuleIdentifier(origin.module)} ${
  121. origin.loc
  122. }`
  123. );
  124. } else if (
  125. origin.loc &&
  126. typeof origin.loc === "object" &&
  127. "start" in origin.loc
  128. ) {
  129. sources.push(
  130. `${index} ${getModuleIdentifier(
  131. origin.module
  132. )} ${JSON.stringify(origin.loc.start)}`
  133. );
  134. }
  135. }
  136. }
  137. }
  138. }
  139. return sources;
  140. };
  141. compilation.hooks.recordChunks.tap(PLUGIN_NAME, (chunks, records) => {
  142. if (!records.chunks) records.chunks = {};
  143. if (!records.chunks.byName) records.chunks.byName = {};
  144. if (!records.chunks.bySource) records.chunks.bySource = {};
  145. /** @type {Set<number>} */
  146. const usedIds = new Set();
  147. for (const chunk of chunks) {
  148. if (typeof chunk.id !== "number") continue;
  149. const name = chunk.name;
  150. if (name) records.chunks.byName[name] = chunk.id;
  151. const sources = getChunkSources(chunk);
  152. for (const source of sources) {
  153. records.chunks.bySource[source] = chunk.id;
  154. }
  155. usedIds.add(chunk.id);
  156. }
  157. records.chunks.usedIds = Array.from(usedIds).sort(compareNumbers);
  158. });
  159. compilation.hooks.reviveChunks.tap(PLUGIN_NAME, (chunks, records) => {
  160. if (!records.chunks) return;
  161. /** @type {Set<number>} */
  162. const usedIds = new Set();
  163. if (records.chunks.byName) {
  164. for (const chunk of chunks) {
  165. if (chunk.id !== null) continue;
  166. if (!chunk.name) continue;
  167. const id = records.chunks.byName[chunk.name];
  168. if (id === undefined) continue;
  169. if (usedIds.has(id)) continue;
  170. usedIds.add(id);
  171. chunk.id = id;
  172. chunk.ids = [id];
  173. }
  174. }
  175. if (records.chunks.bySource) {
  176. for (const chunk of chunks) {
  177. if (chunk.id !== null) continue;
  178. const sources = getChunkSources(chunk);
  179. for (const source of sources) {
  180. const id = records.chunks.bySource[source];
  181. if (id === undefined) continue;
  182. if (usedIds.has(id)) continue;
  183. usedIds.add(id);
  184. chunk.id = id;
  185. chunk.ids = [id];
  186. break;
  187. }
  188. }
  189. }
  190. if (Array.isArray(records.chunks.usedIds)) {
  191. compilation.usedChunkIds = new Set(records.chunks.usedIds);
  192. }
  193. });
  194. });
  195. }
  196. }
  197. module.exports = RecordIdsPlugin;