RecordIdsPlugin.js 6.5 KB

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