RecordIdsPlugin.js 6.4 KB

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