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