UNPKG

12.4 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.substr(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 * @returns {Set<string>} used module ids as strings
238 */
239const getUsedModuleIds = compilation => {
240 const chunkGraph = compilation.chunkGraph;
241
242 /** @type {Set<string>} */
243 const usedIds = new Set();
244 if (compilation.usedModuleIds) {
245 for (const id of compilation.usedModuleIds) {
246 usedIds.add(id + "");
247 }
248 }
249
250 for (const module of compilation.modules) {
251 const moduleId = chunkGraph.getModuleId(module);
252 if (moduleId !== null) {
253 usedIds.add(moduleId + "");
254 }
255 }
256
257 return usedIds;
258};
259exports.getUsedModuleIds = getUsedModuleIds;
260
261/**
262 * @param {Compilation} compilation the compilation
263 * @returns {Set<string>} used chunk ids as strings
264 */
265const getUsedChunkIds = compilation => {
266 /** @type {Set<string>} */
267 const usedIds = new Set();
268 if (compilation.usedChunkIds) {
269 for (const id of compilation.usedChunkIds) {
270 usedIds.add(id + "");
271 }
272 }
273
274 for (const chunk of compilation.chunks) {
275 const chunkId = chunk.id;
276 if (chunkId !== null) {
277 usedIds.add(chunkId + "");
278 }
279 }
280
281 return usedIds;
282};
283exports.getUsedChunkIds = getUsedChunkIds;
284
285/**
286 * @template T
287 * @param {Iterable<T>} items list of items to be named
288 * @param {function(T): string} getShortName get a short name for an item
289 * @param {function(T, string): string} getLongName get a long name for an item
290 * @param {function(T, T): -1|0|1} comparator order of items
291 * @param {Set<string>} usedIds already used ids, will not be assigned
292 * @param {function(T, string): void} assignName assign a name to an item
293 * @returns {T[]} list of items without a name
294 */
295const assignNames = (
296 items,
297 getShortName,
298 getLongName,
299 comparator,
300 usedIds,
301 assignName
302) => {
303 /** @type {Map<string, T[]>} */
304 const nameToItems = new Map();
305
306 for (const item of items) {
307 const name = getShortName(item);
308 addToMapOfItems(nameToItems, name, item);
309 }
310
311 /** @type {Map<string, T[]>} */
312 const nameToItems2 = new Map();
313
314 for (const [name, items] of nameToItems) {
315 if (items.length > 1 || !name) {
316 for (const item of items) {
317 const longName = getLongName(item, name);
318 addToMapOfItems(nameToItems2, longName, item);
319 }
320 } else {
321 addToMapOfItems(nameToItems2, name, items[0]);
322 }
323 }
324
325 /** @type {T[]} */
326 const unnamedItems = [];
327
328 for (const [name, items] of nameToItems2) {
329 if (!name) {
330 for (const item of items) {
331 unnamedItems.push(item);
332 }
333 } else if (items.length === 1 && !usedIds.has(name)) {
334 assignName(items[0], name);
335 usedIds.add(name);
336 } else {
337 items.sort(comparator);
338 let i = 0;
339 for (const item of items) {
340 while (nameToItems2.has(name + i) && usedIds.has(name + i)) i++;
341 assignName(item, name + i);
342 usedIds.add(name + i);
343 i++;
344 }
345 }
346 }
347
348 unnamedItems.sort(comparator);
349 return unnamedItems;
350};
351exports.assignNames = assignNames;
352
353/**
354 * @template T
355 * @param {T[]} items list of items to be named
356 * @param {function(T): string} getName get a name for an item
357 * @param {function(T, T): -1|0|1} comparator order of items
358 * @param {function(T, number): boolean} assignId assign an id to an item
359 * @param {number[]} ranges usable ranges for ids
360 * @param {number} expandFactor factor to create more ranges
361 * @param {number} extraSpace extra space to allocate, i. e. when some ids are already used
362 * @returns {void}
363 */
364const assignDeterministicIds = (
365 items,
366 getName,
367 comparator,
368 assignId,
369 ranges = [10],
370 expandFactor = 10,
371 extraSpace = 0
372) => {
373 items.sort(comparator);
374
375 // max 5% fill rate
376 const optimalRange = Math.min(
377 Math.ceil(items.length * 20) + extraSpace,
378 Number.MAX_SAFE_INTEGER
379 );
380
381 let i = 0;
382 let range = ranges[i];
383 while (range < optimalRange) {
384 i++;
385 if (i < ranges.length) {
386 range = Math.min(ranges[i], Number.MAX_SAFE_INTEGER);
387 } else {
388 range = Math.min(range * expandFactor, Number.MAX_SAFE_INTEGER);
389 }
390 }
391
392 for (const item of items) {
393 const ident = getName(item);
394 let id;
395 let i = 0;
396 do {
397 id = numberHash(ident + i++, range);
398 } while (!assignId(item, id));
399 }
400};
401exports.assignDeterministicIds = assignDeterministicIds;
402
403/**
404 * @param {Iterable<Module>} modules the modules
405 * @param {Compilation} compilation the compilation
406 * @returns {void}
407 */
408const assignAscendingModuleIds = (modules, compilation) => {
409 const chunkGraph = compilation.chunkGraph;
410
411 const usedIds = getUsedModuleIds(compilation);
412
413 let nextId = 0;
414 let assignId;
415 if (usedIds.size > 0) {
416 assignId = module => {
417 if (chunkGraph.getModuleId(module) === null) {
418 while (usedIds.has(nextId + "")) nextId++;
419 chunkGraph.setModuleId(module, nextId++);
420 }
421 };
422 } else {
423 assignId = module => {
424 if (chunkGraph.getModuleId(module) === null) {
425 chunkGraph.setModuleId(module, nextId++);
426 }
427 };
428 }
429 for (const module of modules) {
430 assignId(module);
431 }
432};
433exports.assignAscendingModuleIds = assignAscendingModuleIds;
434
435/**
436 * @param {Iterable<Chunk>} chunks the chunks
437 * @param {Compilation} compilation the compilation
438 * @returns {void}
439 */
440const assignAscendingChunkIds = (chunks, compilation) => {
441 const usedIds = getUsedChunkIds(compilation);
442
443 let nextId = 0;
444 if (usedIds.size > 0) {
445 for (const chunk of chunks) {
446 if (chunk.id === null) {
447 while (usedIds.has(nextId + "")) nextId++;
448 chunk.id = nextId;
449 chunk.ids = [nextId];
450 nextId++;
451 }
452 }
453 } else {
454 for (const chunk of chunks) {
455 if (chunk.id === null) {
456 chunk.id = nextId;
457 chunk.ids = [nextId];
458 nextId++;
459 }
460 }
461 }
462};
463exports.assignAscendingChunkIds = assignAscendingChunkIds;