UNPKG

12.9 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const createHash = require("../util/createHash");
9const { makePathsRelative } = require("../util/identifier");
10const numberHash = require("../util/numberHash");
11
12/** @typedef {import("../Chunk")} Chunk */
13/** @typedef {import("../ChunkGraph")} ChunkGraph */
14/** @typedef {import("../Compilation")} Compilation */
15/** @typedef {import("../Module")} Module */
16/** @typedef {typeof import("../util/Hash")} Hash */
17
18/**
19 * @param {string} str string to hash
20 * @param {number} len max length of the hash
21 * @param {string | Hash} hashFunction hash function to use
22 * @returns {string} hash
23 */
24const getHash = (str, len, hashFunction) => {
25 const hash = createHash(hashFunction);
26 hash.update(str);
27 const digest = /** @type {string} */ (hash.digest("hex"));
28 return digest.slice(0, len);
29};
30
31/**
32 * @param {string} str the string
33 * @returns {string} string prefixed by an underscore if it is a number
34 */
35const avoidNumber = str => {
36 // max length of a number is 21 chars, bigger numbers a written as "...e+xx"
37 if (str.length > 21) return str;
38 const firstChar = str.charCodeAt(0);
39 // skip everything that doesn't look like a number
40 // charCodes: "-": 45, "1": 49, "9": 57
41 if (firstChar < 49) {
42 if (firstChar !== 45) return str;
43 } else if (firstChar > 57) {
44 return str;
45 }
46 if (str === +str + "") {
47 return `_${str}`;
48 }
49 return str;
50};
51
52/**
53 * @param {string} request the request
54 * @returns {string} id representation
55 */
56const requestToId = request => {
57 return request
58 .replace(/^(\.\.?\/)+/, "")
59 .replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_");
60};
61exports.requestToId = requestToId;
62
63/**
64 * @param {string} string the string
65 * @param {string} delimiter separator for string and hash
66 * @param {string | Hash} hashFunction hash function to use
67 * @returns {string} string with limited max length to 100 chars
68 */
69const shortenLongString = (string, delimiter, hashFunction) => {
70 if (string.length < 100) return string;
71 return (
72 string.slice(0, 100 - 6 - delimiter.length) +
73 delimiter +
74 getHash(string, 6, hashFunction)
75 );
76};
77
78/**
79 * @param {Module} module the module
80 * @param {string} context context directory
81 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
82 * @returns {string} short module name
83 */
84const getShortModuleName = (module, context, associatedObjectForCache) => {
85 const libIdent = module.libIdent({ context, associatedObjectForCache });
86 if (libIdent) return avoidNumber(libIdent);
87 const nameForCondition = module.nameForCondition();
88 if (nameForCondition)
89 return avoidNumber(
90 makePathsRelative(context, nameForCondition, associatedObjectForCache)
91 );
92 return "";
93};
94exports.getShortModuleName = getShortModuleName;
95
96/**
97 * @param {string} shortName the short name
98 * @param {Module} module the module
99 * @param {string} context context directory
100 * @param {string | Hash} hashFunction hash function to use
101 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
102 * @returns {string} long module name
103 */
104const getLongModuleName = (
105 shortName,
106 module,
107 context,
108 hashFunction,
109 associatedObjectForCache
110) => {
111 const fullName = getFullModuleName(module, context, associatedObjectForCache);
112 return `${shortName}?${getHash(fullName, 4, hashFunction)}`;
113};
114exports.getLongModuleName = getLongModuleName;
115
116/**
117 * @param {Module} module the module
118 * @param {string} context context directory
119 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
120 * @returns {string} full module name
121 */
122const getFullModuleName = (module, context, associatedObjectForCache) => {
123 return makePathsRelative(
124 context,
125 module.identifier(),
126 associatedObjectForCache
127 );
128};
129exports.getFullModuleName = getFullModuleName;
130
131/**
132 * @param {Chunk} chunk the chunk
133 * @param {ChunkGraph} chunkGraph the chunk graph
134 * @param {string} context context directory
135 * @param {string} delimiter delimiter for names
136 * @param {string | Hash} hashFunction hash function to use
137 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
138 * @returns {string} short chunk name
139 */
140const getShortChunkName = (
141 chunk,
142 chunkGraph,
143 context,
144 delimiter,
145 hashFunction,
146 associatedObjectForCache
147) => {
148 const modules = chunkGraph.getChunkRootModules(chunk);
149 const shortModuleNames = modules.map(m =>
150 requestToId(getShortModuleName(m, context, associatedObjectForCache))
151 );
152 chunk.idNameHints.sort();
153 const chunkName = Array.from(chunk.idNameHints)
154 .concat(shortModuleNames)
155 .filter(Boolean)
156 .join(delimiter);
157 return shortenLongString(chunkName, delimiter, hashFunction);
158};
159exports.getShortChunkName = getShortChunkName;
160
161/**
162 * @param {Chunk} chunk the chunk
163 * @param {ChunkGraph} chunkGraph the chunk graph
164 * @param {string} context context directory
165 * @param {string} delimiter delimiter for names
166 * @param {string | Hash} hashFunction hash function to use
167 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
168 * @returns {string} short chunk name
169 */
170const getLongChunkName = (
171 chunk,
172 chunkGraph,
173 context,
174 delimiter,
175 hashFunction,
176 associatedObjectForCache
177) => {
178 const modules = chunkGraph.getChunkRootModules(chunk);
179 const shortModuleNames = modules.map(m =>
180 requestToId(getShortModuleName(m, context, associatedObjectForCache))
181 );
182 const longModuleNames = modules.map(m =>
183 requestToId(
184 getLongModuleName("", m, context, hashFunction, associatedObjectForCache)
185 )
186 );
187 chunk.idNameHints.sort();
188 const chunkName = Array.from(chunk.idNameHints)
189 .concat(shortModuleNames, longModuleNames)
190 .filter(Boolean)
191 .join(delimiter);
192 return shortenLongString(chunkName, delimiter, hashFunction);
193};
194exports.getLongChunkName = getLongChunkName;
195
196/**
197 * @param {Chunk} chunk the chunk
198 * @param {ChunkGraph} chunkGraph the chunk graph
199 * @param {string} context context directory
200 * @param {Object=} associatedObjectForCache an object to which the cache will be attached
201 * @returns {string} full chunk name
202 */
203const getFullChunkName = (
204 chunk,
205 chunkGraph,
206 context,
207 associatedObjectForCache
208) => {
209 if (chunk.name) return chunk.name;
210 const modules = chunkGraph.getChunkRootModules(chunk);
211 const fullModuleNames = modules.map(m =>
212 makePathsRelative(context, m.identifier(), associatedObjectForCache)
213 );
214 return fullModuleNames.join();
215};
216exports.getFullChunkName = getFullChunkName;
217
218/**
219 * @template K
220 * @template V
221 * @param {Map<K, V[]>} map a map from key to values
222 * @param {K} key key
223 * @param {V} value value
224 * @returns {void}
225 */
226const addToMapOfItems = (map, key, value) => {
227 let array = map.get(key);
228 if (array === undefined) {
229 array = [];
230 map.set(key, array);
231 }
232 array.push(value);
233};
234
235/**
236 * @param {Compilation} compilation the compilation
237 * @param {function(Module): boolean=} filter filter modules
238 * @returns {[Set<string>, Module[]]} used module ids as strings and modules without id matching the filter
239 */
240const getUsedModuleIdsAndModules = (compilation, filter) => {
241 const chunkGraph = compilation.chunkGraph;
242
243 const modules = [];
244
245 /** @type {Set<string>} */
246 const usedIds = new Set();
247 if (compilation.usedModuleIds) {
248 for (const id of compilation.usedModuleIds) {
249 usedIds.add(id + "");
250 }
251 }
252
253 for (const module of compilation.modules) {
254 if (!module.needId) continue;
255 const moduleId = chunkGraph.getModuleId(module);
256 if (moduleId !== null) {
257 usedIds.add(moduleId + "");
258 } else {
259 if (
260 (!filter || filter(module)) &&
261 chunkGraph.getNumberOfModuleChunks(module) !== 0
262 ) {
263 modules.push(module);
264 }
265 }
266 }
267
268 return [usedIds, modules];
269};
270exports.getUsedModuleIdsAndModules = getUsedModuleIdsAndModules;
271
272/**
273 * @param {Compilation} compilation the compilation
274 * @returns {Set<string>} used chunk ids as strings
275 */
276const getUsedChunkIds = compilation => {
277 /** @type {Set<string>} */
278 const usedIds = new Set();
279 if (compilation.usedChunkIds) {
280 for (const id of compilation.usedChunkIds) {
281 usedIds.add(id + "");
282 }
283 }
284
285 for (const chunk of compilation.chunks) {
286 const chunkId = chunk.id;
287 if (chunkId !== null) {
288 usedIds.add(chunkId + "");
289 }
290 }
291
292 return usedIds;
293};
294exports.getUsedChunkIds = getUsedChunkIds;
295
296/**
297 * @template T
298 * @param {Iterable<T>} items list of items to be named
299 * @param {function(T): string} getShortName get a short name for an item
300 * @param {function(T, string): string} getLongName get a long name for an item
301 * @param {function(T, T): -1|0|1} comparator order of items
302 * @param {Set<string>} usedIds already used ids, will not be assigned
303 * @param {function(T, string): void} assignName assign a name to an item
304 * @returns {T[]} list of items without a name
305 */
306const assignNames = (
307 items,
308 getShortName,
309 getLongName,
310 comparator,
311 usedIds,
312 assignName
313) => {
314 /** @type {Map<string, T[]>} */
315 const nameToItems = new Map();
316
317 for (const item of items) {
318 const name = getShortName(item);
319 addToMapOfItems(nameToItems, name, item);
320 }
321
322 /** @type {Map<string, T[]>} */
323 const nameToItems2 = new Map();
324
325 for (const [name, items] of nameToItems) {
326 if (items.length > 1 || !name) {
327 for (const item of items) {
328 const longName = getLongName(item, name);
329 addToMapOfItems(nameToItems2, longName, item);
330 }
331 } else {
332 addToMapOfItems(nameToItems2, name, items[0]);
333 }
334 }
335
336 /** @type {T[]} */
337 const unnamedItems = [];
338
339 for (const [name, items] of nameToItems2) {
340 if (!name) {
341 for (const item of items) {
342 unnamedItems.push(item);
343 }
344 } else if (items.length === 1 && !usedIds.has(name)) {
345 assignName(items[0], name);
346 usedIds.add(name);
347 } else {
348 items.sort(comparator);
349 let i = 0;
350 for (const item of items) {
351 while (nameToItems2.has(name + i) && usedIds.has(name + i)) i++;
352 assignName(item, name + i);
353 usedIds.add(name + i);
354 i++;
355 }
356 }
357 }
358
359 unnamedItems.sort(comparator);
360 return unnamedItems;
361};
362exports.assignNames = assignNames;
363
364/**
365 * @template T
366 * @param {T[]} items list of items to be named
367 * @param {function(T): string} getName get a name for an item
368 * @param {function(T, T): -1|0|1} comparator order of items
369 * @param {function(T, number): boolean} assignId assign an id to an item
370 * @param {number[]} ranges usable ranges for ids
371 * @param {number} expandFactor factor to create more ranges
372 * @param {number} extraSpace extra space to allocate, i. e. when some ids are already used
373 * @param {number} salt salting number to initialize hashing
374 * @returns {void}
375 */
376const assignDeterministicIds = (
377 items,
378 getName,
379 comparator,
380 assignId,
381 ranges = [10],
382 expandFactor = 10,
383 extraSpace = 0,
384 salt = 0
385) => {
386 items.sort(comparator);
387
388 // max 5% fill rate
389 const optimalRange = Math.min(
390 Math.ceil(items.length * 20) + extraSpace,
391 Number.MAX_SAFE_INTEGER
392 );
393
394 let i = 0;
395 let range = ranges[i];
396 while (range < optimalRange) {
397 i++;
398 if (i < ranges.length) {
399 range = Math.min(ranges[i], Number.MAX_SAFE_INTEGER);
400 } else if (expandFactor) {
401 range = Math.min(range * expandFactor, Number.MAX_SAFE_INTEGER);
402 } else {
403 break;
404 }
405 }
406
407 for (const item of items) {
408 const ident = getName(item);
409 let id;
410 let i = salt;
411 do {
412 id = numberHash(ident + i++, range);
413 } while (!assignId(item, id));
414 }
415};
416exports.assignDeterministicIds = assignDeterministicIds;
417
418/**
419 * @param {Set<string>} usedIds used ids
420 * @param {Iterable<Module>} modules the modules
421 * @param {Compilation} compilation the compilation
422 * @returns {void}
423 */
424const assignAscendingModuleIds = (usedIds, modules, compilation) => {
425 const chunkGraph = compilation.chunkGraph;
426
427 let nextId = 0;
428 let assignId;
429 if (usedIds.size > 0) {
430 assignId = module => {
431 if (chunkGraph.getModuleId(module) === null) {
432 while (usedIds.has(nextId + "")) nextId++;
433 chunkGraph.setModuleId(module, nextId++);
434 }
435 };
436 } else {
437 assignId = module => {
438 if (chunkGraph.getModuleId(module) === null) {
439 chunkGraph.setModuleId(module, nextId++);
440 }
441 };
442 }
443 for (const module of modules) {
444 assignId(module);
445 }
446};
447exports.assignAscendingModuleIds = assignAscendingModuleIds;
448
449/**
450 * @param {Iterable<Chunk>} chunks the chunks
451 * @param {Compilation} compilation the compilation
452 * @returns {void}
453 */
454const assignAscendingChunkIds = (chunks, compilation) => {
455 const usedIds = getUsedChunkIds(compilation);
456
457 let nextId = 0;
458 if (usedIds.size > 0) {
459 for (const chunk of chunks) {
460 if (chunk.id === null) {
461 while (usedIds.has(nextId + "")) nextId++;
462 chunk.id = nextId;
463 chunk.ids = [nextId];
464 nextId++;
465 }
466 }
467 } else {
468 for (const chunk of chunks) {
469 if (chunk.id === null) {
470 chunk.id = nextId;
471 chunk.ids = [nextId];
472 nextId++;
473 }
474 }
475 }
476};
477exports.assignAscendingChunkIds = assignAscendingChunkIds;