RecordIdsPlugin.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. if (chunkGroup.name) {
  124. sources.push(`${index} ${chunkGroup.name}`);
  125. } else {
  126. for (const origin of chunkGroup.origins) {
  127. if (origin.module) {
  128. if (origin.request) {
  129. sources.push(
  130. `${index} ${getModuleIdentifier(origin.module)} ${
  131. origin.request
  132. }`
  133. );
  134. } else if (typeof origin.loc === "string") {
  135. sources.push(
  136. `${index} ${getModuleIdentifier(origin.module)} ${
  137. origin.loc
  138. }`
  139. );
  140. } else if (
  141. origin.loc &&
  142. typeof origin.loc === "object" &&
  143. origin.loc.start
  144. ) {
  145. sources.push(
  146. `${index} ${getModuleIdentifier(
  147. origin.module
  148. )} ${JSON.stringify(origin.loc.start)}`
  149. );
  150. }
  151. }
  152. }
  153. }
  154. }
  155. return sources;
  156. };
  157. compilation.hooks.recordChunks.tap(
  158. "RecordIdsPlugin",
  159. /**
  160. * @param {Chunk[]} chunks the chunks array
  161. * @param {Records} records the records object
  162. * @returns {void}
  163. */
  164. (chunks, records) => {
  165. if (!records.chunks) records.chunks = {};
  166. if (!records.chunks.byName) records.chunks.byName = {};
  167. if (!records.chunks.bySource) records.chunks.bySource = {};
  168. /** @type {Set<number>} */
  169. const usedIds = new Set();
  170. for (const chunk of chunks) {
  171. if (typeof chunk.id !== "number") continue;
  172. const name = chunk.name;
  173. if (name) records.chunks.byName[name] = chunk.id;
  174. const sources = getChunkSources(chunk);
  175. for (const source of sources) {
  176. records.chunks.bySource[source] = chunk.id;
  177. }
  178. usedIds.add(chunk.id);
  179. }
  180. records.chunks.usedIds = Array.from(usedIds).sort();
  181. }
  182. );
  183. compilation.hooks.reviveChunks.tap(
  184. "RecordIdsPlugin",
  185. /**
  186. * @param {Chunk[]} chunks the chunks array
  187. * @param {Records} records the records object
  188. * @returns {void}
  189. */
  190. (chunks, records) => {
  191. if (!records.chunks) return;
  192. /** @type {Set<number>} */
  193. const usedIds = new Set();
  194. if (records.chunks.byName) {
  195. for (const chunk of chunks) {
  196. if (chunk.id !== null) continue;
  197. if (!chunk.name) continue;
  198. const id = records.chunks.byName[chunk.name];
  199. if (id === undefined) continue;
  200. if (usedIds.has(id)) continue;
  201. usedIds.add(id);
  202. chunk.id = id;
  203. }
  204. }
  205. if (records.chunks.bySource) {
  206. for (const chunk of chunks) {
  207. const sources = getChunkSources(chunk);
  208. for (const source of sources) {
  209. const id = records.chunks.bySource[source];
  210. if (id === undefined) continue;
  211. if (usedIds.has(id)) continue;
  212. usedIds.add(id);
  213. chunk.id = id;
  214. break;
  215. }
  216. }
  217. }
  218. if (Array.isArray(records.chunks.usedIds)) {
  219. compilation.usedChunkIds = new Set(records.chunks.usedIds);
  220. }
  221. }
  222. );
  223. });
  224. }
  225. }
  226. module.exports = RecordIdsPlugin;