RecordIdsPlugin.js 6.5 KB

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