source: frontend/node_modules/webpack/lib/ModuleFilenameHelpers.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 14.4 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const NormalModule = require("./NormalModule");
9const { DEFAULTS } = require("./config/defaults");
10const createHash = require("./util/createHash");
11const memoize = require("./util/memoize");
12
13/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
14/** @typedef {import("./ChunkGraph")} ChunkGraph */
15/** @typedef {import("./Module")} Module */
16/** @typedef {import("./RequestShortener")} RequestShortener */
17
18/** @typedef {(str: string) => boolean} MatcherFn */
19/** @typedef {string | RegExp | MatcherFn | (string | RegExp | MatcherFn)[]} Matcher */
20/** @typedef {{ test?: Matcher, include?: Matcher, exclude?: Matcher }} MatchObject */
21
22const ModuleFilenameHelpers = module.exports;
23
24// TODO webpack 6: consider removing these
25ModuleFilenameHelpers.ALL_LOADERS_RESOURCE = "[all-loaders][resource]";
26ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE =
27 /\[all-?loaders\]\[resource\]/gi;
28ModuleFilenameHelpers.LOADERS_RESOURCE = "[loaders][resource]";
29ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE = /\[loaders\]\[resource\]/gi;
30ModuleFilenameHelpers.RESOURCE = "[resource]";
31ModuleFilenameHelpers.REGEXP_RESOURCE = /\[resource\]/gi;
32ModuleFilenameHelpers.ABSOLUTE_RESOURCE_PATH = "[absolute-resource-path]";
33// cSpell:words olute
34ModuleFilenameHelpers.REGEXP_ABSOLUTE_RESOURCE_PATH =
35 /\[abs(olute)?-?resource-?path\]/gi;
36ModuleFilenameHelpers.RESOURCE_PATH = "[resource-path]";
37ModuleFilenameHelpers.REGEXP_RESOURCE_PATH = /\[resource-?path\]/gi;
38ModuleFilenameHelpers.ALL_LOADERS = "[all-loaders]";
39ModuleFilenameHelpers.REGEXP_ALL_LOADERS = /\[all-?loaders\]/gi;
40ModuleFilenameHelpers.LOADERS = "[loaders]";
41ModuleFilenameHelpers.REGEXP_LOADERS = /\[loaders\]/gi;
42ModuleFilenameHelpers.QUERY = "[query]";
43ModuleFilenameHelpers.REGEXP_QUERY = /\[query\]/gi;
44ModuleFilenameHelpers.ID = "[id]";
45ModuleFilenameHelpers.REGEXP_ID = /\[id\]/gi;
46ModuleFilenameHelpers.HASH = "[hash]";
47ModuleFilenameHelpers.REGEXP_HASH = /\[hash\]/gi;
48ModuleFilenameHelpers.NAMESPACE = "[namespace]";
49ModuleFilenameHelpers.REGEXP_NAMESPACE = /\[namespace\]/gi;
50
51/** @typedef {() => string} ReturnStringCallback */
52
53/**
54 * Returns a function that returns the part of the string after the token
55 * @param {ReturnStringCallback} strFn the function to get the string
56 * @param {string} token the token to search for
57 * @returns {ReturnStringCallback} a function that returns the part of the string after the token
58 */
59const getAfter = (strFn, token) => () => {
60 const str = strFn();
61 const idx = str.indexOf(token);
62 return idx < 0 ? "" : str.slice(idx);
63};
64
65/**
66 * Returns a function that returns the part of the string before the token
67 * @param {ReturnStringCallback} strFn the function to get the string
68 * @param {string} token the token to search for
69 * @returns {ReturnStringCallback} a function that returns the part of the string before the token
70 */
71const getBefore = (strFn, token) => () => {
72 const str = strFn();
73 const idx = str.lastIndexOf(token);
74 return idx < 0 ? "" : str.slice(0, idx);
75};
76
77/**
78 * Returns a function that returns a hash of the string
79 * @param {ReturnStringCallback} strFn the function to get the string
80 * @param {HashFunction=} hashFunction the hash function to use
81 * @returns {ReturnStringCallback} a function that returns the hash of the string
82 */
83const getHash =
84 (strFn, hashFunction = DEFAULTS.HASH_FUNCTION) =>
85 () => {
86 const hash = createHash(hashFunction);
87 hash.update(strFn());
88 const digest = hash.digest("hex");
89 return digest.slice(0, 4);
90 };
91
92/**
93 * Returns the lazy access object.
94 * @template T
95 * Returns a lazy object. The object is lazy in the sense that the properties are
96 * only evaluated when they are accessed. This is only obtained by setting a function as the value for each key.
97 * @param {Record<string, () => T>} obj the object to convert to a lazy access object
98 * @returns {Record<string, T>} the lazy access object
99 */
100const lazyObject = (obj) => {
101 const newObj = /** @type {Record<string, T>} */ ({});
102 for (const key of Object.keys(obj)) {
103 const fn = obj[key];
104 Object.defineProperty(newObj, key, {
105 get: () => fn(),
106 set: (v) => {
107 Object.defineProperty(newObj, key, {
108 value: v,
109 enumerable: true,
110 writable: true
111 });
112 },
113 enumerable: true,
114 configurable: true
115 });
116 }
117 return newObj;
118};
119
120const SQUARE_BRACKET_TAG_REGEXP = /\[\\*([\w-]+)\\*\]/g;
121/**
122 * Defines the module filename template context type used by this module.
123 * @typedef {object} ModuleFilenameTemplateContext
124 * @property {string} identifier the identifier of the module
125 * @property {string} shortIdentifier the shortened identifier of the module
126 * @property {string} resource the resource of the module request
127 * @property {string} resourcePath the resource path of the module request
128 * @property {string} absoluteResourcePath the absolute resource path of the module request
129 * @property {string} loaders the loaders of the module request
130 * @property {string} allLoaders the all loaders of the module request
131 * @property {string} query the query of the module identifier
132 * @property {string} moduleId the module id of the module
133 * @property {string} hash the hash of the module identifier
134 * @property {string} namespace the module namespace
135 */
136/** @typedef {((context: ModuleFilenameTemplateContext) => string)} ModuleFilenameTemplateFunction */
137/** @typedef {string | ModuleFilenameTemplateFunction} ModuleFilenameTemplate */
138
139/**
140 * Returns the filename.
141 * @param {Module | string} module the module
142 * @param {{ namespace?: string, moduleFilenameTemplate?: ModuleFilenameTemplate }} options options
143 * @param {{ requestShortener: RequestShortener, chunkGraph: ChunkGraph, hashFunction?: HashFunction }} contextInfo context info
144 * @returns {string} the filename
145 */
146ModuleFilenameHelpers.createFilename = (
147 // eslint-disable-next-line default-param-last
148 module = "",
149 options,
150 { requestShortener, chunkGraph, hashFunction = DEFAULTS.HASH_FUNCTION }
151) => {
152 const opts = {
153 namespace: "",
154 moduleFilenameTemplate: "",
155 ...(typeof options === "object"
156 ? options
157 : {
158 moduleFilenameTemplate: options
159 })
160 };
161
162 /** @type {ReturnStringCallback} */
163 let absoluteResourcePath;
164 /** @type {ReturnStringCallback} */
165 let hash;
166 /** @type {ReturnStringCallback} */
167 let identifier;
168 /** @type {ReturnStringCallback} */
169 let moduleId;
170 /** @type {ReturnStringCallback} */
171 let shortIdentifier;
172 if (typeof module === "string") {
173 shortIdentifier =
174 /** @type {ReturnStringCallback} */
175 (memoize(() => requestShortener.shorten(module)));
176 identifier = shortIdentifier;
177 moduleId = () => "";
178 absoluteResourcePath = () =>
179 /** @type {string} */ (module.split("!").pop());
180 hash = getHash(identifier, hashFunction);
181 } else {
182 shortIdentifier = memoize(() =>
183 module.readableIdentifier(requestShortener)
184 );
185 identifier =
186 /** @type {ReturnStringCallback} */
187 (memoize(() => requestShortener.shorten(module.identifier())));
188 moduleId =
189 /** @type {ReturnStringCallback} */
190 (() => chunkGraph.getModuleId(module));
191 absoluteResourcePath = () =>
192 module instanceof NormalModule
193 ? module.resource
194 : /** @type {string} */ (module.identifier().split("!").pop());
195 hash = getHash(identifier, hashFunction);
196 }
197 const resource =
198 /** @type {ReturnStringCallback} */
199 (memoize(() => shortIdentifier().split("!").pop()));
200
201 const loaders = getBefore(shortIdentifier, "!");
202 const allLoaders = getBefore(identifier, "!");
203 const query = getAfter(resource, "?");
204 const resourcePath = () => {
205 const q = query().length;
206 return q === 0 ? resource() : resource().slice(0, -q);
207 };
208 if (typeof opts.moduleFilenameTemplate === "function") {
209 return opts.moduleFilenameTemplate(
210 /** @type {ModuleFilenameTemplateContext} */
211 (
212 lazyObject({
213 identifier,
214 shortIdentifier,
215 resource,
216 resourcePath: memoize(resourcePath),
217 absoluteResourcePath: memoize(absoluteResourcePath),
218 loaders: memoize(loaders),
219 allLoaders: memoize(allLoaders),
220 query: memoize(query),
221 moduleId: memoize(moduleId),
222 hash: memoize(hash),
223 namespace: () => opts.namespace
224 })
225 )
226 );
227 }
228
229 // TODO webpack 6: consider removing alternatives without dashes
230 /** @type {Map<string, () => string>} */
231 const replacements = new Map([
232 ["identifier", identifier],
233 ["short-identifier", shortIdentifier],
234 ["resource", resource],
235 ["resource-path", resourcePath],
236 // cSpell:words resourcepath
237 ["resourcepath", resourcePath],
238 ["absolute-resource-path", absoluteResourcePath],
239 ["abs-resource-path", absoluteResourcePath],
240 // cSpell:words absoluteresource
241 ["absoluteresource-path", absoluteResourcePath],
242 // cSpell:words absresource
243 ["absresource-path", absoluteResourcePath],
244 // cSpell:words resourcepath
245 ["absolute-resourcepath", absoluteResourcePath],
246 // cSpell:words resourcepath
247 ["abs-resourcepath", absoluteResourcePath],
248 // cSpell:words absoluteresourcepath
249 ["absoluteresourcepath", absoluteResourcePath],
250 // cSpell:words absresourcepath
251 ["absresourcepath", absoluteResourcePath],
252 ["all-loaders", allLoaders],
253 // cSpell:words allloaders
254 ["allloaders", allLoaders],
255 ["loaders", loaders],
256 ["query", query],
257 ["id", moduleId],
258 ["hash", hash],
259 ["namespace", () => opts.namespace]
260 ]);
261
262 // TODO webpack 6: consider removing weird double placeholders
263 return /** @type {string} */ (opts.moduleFilenameTemplate)
264 .replace(ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE, "[identifier]")
265 .replace(
266 ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE,
267 "[short-identifier]"
268 )
269 .replace(SQUARE_BRACKET_TAG_REGEXP, (match, content) => {
270 if (content.length + 2 === match.length) {
271 const replacement = replacements.get(content.toLowerCase());
272 if (replacement !== undefined) {
273 return replacement();
274 }
275 } else if (match.startsWith("[\\") && match.endsWith("\\]")) {
276 return `[${match.slice(2, -2)}]`;
277 }
278 return match;
279 });
280};
281
282/**
283 * Replaces duplicate items in an array with new values generated by a callback function.
284 * The callback function is called with the duplicate item, the index of the duplicate item, and the number of times the item has been replaced.
285 * The callback function should return the new value for the duplicate item.
286 * @template T
287 * @param {T[]} array the array with duplicates to be replaced
288 * @param {(duplicateItem: T, duplicateItemIndex: number, numberOfTimesReplaced: number) => T} fn callback function to generate new values for the duplicate items
289 * @param {(firstElement: T, nextElement: T) => -1 | 0 | 1=} comparator optional comparator function to sort the duplicate items
290 * @returns {T[]} the array with duplicates replaced
291 * @example
292 * ```js
293 * const array = ["a", "b", "c", "a", "b", "a"];
294 * const result = ModuleFilenameHelpers.replaceDuplicates(array, (item, index, count) => `${item}-${count}`);
295 * // result: ["a-1", "b-1", "c", "a-2", "b-2", "a-3"]
296 * ```
297 */
298ModuleFilenameHelpers.replaceDuplicates = (array, fn, comparator) => {
299 const countMap = Object.create(null);
300 const posMap = Object.create(null);
301
302 for (const [idx, item] of array.entries()) {
303 countMap[item] = countMap[item] || [];
304 countMap[item].push(idx);
305 posMap[item] = 0;
306 }
307 if (comparator) {
308 for (const item of Object.keys(countMap)) {
309 countMap[item].sort(comparator);
310 }
311 }
312 return array.map((item, i) => {
313 if (countMap[item].length > 1) {
314 if (comparator && countMap[item][0] === i) return item;
315 return fn(item, i, posMap[item]++);
316 }
317 return item;
318 });
319};
320
321/**
322 * Tests if a string matches a RegExp or an array of RegExp.
323 * @param {string} str string to test
324 * @param {Matcher} test value which will be used to match against the string
325 * @returns {boolean} true, when the RegExp matches
326 * @example
327 * ```js
328 * ModuleFilenameHelpers.matchPart("foo.js", "foo"); // true
329 * ModuleFilenameHelpers.matchPart("foo.js", "foo.js"); // true
330 * ModuleFilenameHelpers.matchPart("foo.js", "foo."); // false
331 * ModuleFilenameHelpers.matchPart("foo.js", "foo*"); // false
332 * ModuleFilenameHelpers.matchPart("foo.js", "foo.*"); // true
333 * ModuleFilenameHelpers.matchPart("foo.js", /^foo/); // true
334 * ModuleFilenameHelpers.matchPart("foo.js", [/^foo/, "bar"]); // true
335 * ModuleFilenameHelpers.matchPart("foo.js", [/^foo/, "bar"]); // true
336 * ModuleFilenameHelpers.matchPart("foo.js", [/^foo/, /^bar/]); // true
337 * ModuleFilenameHelpers.matchPart("foo.js", [/^baz/, /^bar/]); // false
338 * ```
339 */
340const matchPart = (str, test) => {
341 if (!test) return true;
342 if (test instanceof RegExp) {
343 return test.test(str);
344 } else if (typeof test === "string") {
345 return str.startsWith(test);
346 } else if (typeof test === "function") {
347 return test(str);
348 }
349
350 return test.some((test) => matchPart(str, test));
351};
352
353ModuleFilenameHelpers.matchPart = matchPart;
354
355/**
356 * Tests if a string matches a match object. The match object can have the following properties:
357 * - `test`: a RegExp or an array of RegExp
358 * - `include`: a RegExp or an array of RegExp
359 * - `exclude`: a RegExp or an array of RegExp
360 *
361 * The `test` property is tested first, then `include` and then `exclude`.
362 * @param {MatchObject} obj a match object to test against the string
363 * @param {string} str string to test against the matching object
364 * @returns {boolean} true, when the object matches
365 * @example
366 * ```js
367 * ModuleFilenameHelpers.matchObject({ test: "foo.js" }, "foo.js"); // true
368 * ModuleFilenameHelpers.matchObject({ test: /^foo/ }, "foo.js"); // true
369 * ModuleFilenameHelpers.matchObject({ test: [/^foo/, "bar"] }, "foo.js"); // true
370 * ModuleFilenameHelpers.matchObject({ test: [/^foo/, "bar"] }, "baz.js"); // false
371 * ModuleFilenameHelpers.matchObject({ include: "foo.js" }, "foo.js"); // true
372 * ModuleFilenameHelpers.matchObject({ include: "foo.js" }, "bar.js"); // false
373 * ModuleFilenameHelpers.matchObject({ include: /^foo/ }, "foo.js"); // true
374 * ModuleFilenameHelpers.matchObject({ include: [/^foo/, "bar"] }, "foo.js"); // true
375 * ModuleFilenameHelpers.matchObject({ include: [/^foo/, "bar"] }, "baz.js"); // false
376 * ModuleFilenameHelpers.matchObject({ exclude: "foo.js" }, "foo.js"); // false
377 * ModuleFilenameHelpers.matchObject({ exclude: [/^foo/, "bar"] }, "foo.js"); // false
378 * ```
379 */
380ModuleFilenameHelpers.matchObject = (obj, str) => {
381 if (obj.test && !ModuleFilenameHelpers.matchPart(str, obj.test)) {
382 return false;
383 }
384 if (obj.include && !ModuleFilenameHelpers.matchPart(str, obj.include)) {
385 return false;
386 }
387 if (obj.exclude && ModuleFilenameHelpers.matchPart(str, obj.exclude)) {
388 return false;
389 }
390 return true;
391};
Note: See TracBrowser for help on using the repository browser.