source: frontend/node_modules/mini-css-extract-plugin/dist/utils.js

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

Fix frontend appearance

  • Property mode set to 100644
File size: 12.3 KB
Line 
1"use strict";
2
3const NativeModule = require("module");
4const path = require("path");
5
6/** @typedef {import("webpack").Compilation} Compilation */
7/** @typedef {import("webpack").Module} Module */
8
9// eslint-disable-next-line jsdoc/reject-any-type
10/** @typedef {import("webpack").LoaderContext<any>} LoaderContext */
11
12/**
13 * @returns {boolean} always returns true
14 */
15function trueFn() {
16 return true;
17}
18
19/**
20 * @param {Compilation} compilation compilation
21 * @param {string | number} id module id
22 * @returns {null | Module} the found module
23 */
24function findModuleById(compilation, id) {
25 const {
26 modules,
27 chunkGraph
28 } = compilation;
29 for (const module of modules) {
30 const moduleId = typeof chunkGraph !== "undefined" ? chunkGraph.getModuleId(module) : module.id;
31 if (moduleId === id) {
32 return module;
33 }
34 }
35 return null;
36}
37
38/* eslint-disable jsdoc/reject-any-type */
39/**
40 * @param {LoaderContext} loaderContext loader context
41 * @param {string | Buffer} code code
42 * @param {string} filename filename
43 * @returns {Record<string, any>} exports of a module
44 */
45function evalModuleCode(loaderContext, code, filename) {
46 // @ts-expect-error
47 const module = new NativeModule(filename, loaderContext);
48 // @ts-expect-error
49 module.paths = NativeModule._nodeModulePaths(loaderContext.context);
50 module.filename = filename;
51 // @ts-expect-error
52 module._compile(code, filename);
53 return module.exports;
54}
55/* eslint-enable jsdoc/reject-any-type */
56
57/**
58 * @param {string} a a
59 * @param {string} b b
60 * @returns {0 | 1 | -1} result of comparing
61 */
62function compareIds(a, b) {
63 if (typeof a !== typeof b) {
64 return typeof a < typeof b ? -1 : 1;
65 }
66 if (a < b) {
67 return -1;
68 }
69 if (a > b) {
70 return 1;
71 }
72 return 0;
73}
74
75/**
76 * @param {Module} a a
77 * @param {Module} b b
78 * @returns {0 | 1 | -1} result of comparing
79 */
80function compareModulesByIdentifier(a, b) {
81 return compareIds(a.identifier(), b.identifier());
82}
83const MODULE_TYPE = "css/mini-extract";
84const AUTO_PUBLIC_PATH = "__mini_css_extract_plugin_public_path_auto__";
85const ABSOLUTE_PUBLIC_PATH = "webpack:///mini-css-extract-plugin/";
86const BASE_URI = "webpack://";
87const SINGLE_DOT_PATH_SEGMENT = "__mini_css_extract_plugin_single_dot_path_segment__";
88
89/**
90 * @param {string} str path
91 * @returns {boolean} true when path is absolute, otherwise false
92 */
93function isAbsolutePath(str) {
94 return path.posix.isAbsolute(str) || path.win32.isAbsolute(str);
95}
96const RELATIVE_PATH_REGEXP = /^\.\.?[/\\]/;
97
98/**
99 * @param {string} str string
100 * @returns {boolean} true when path is relative, otherwise false
101 */
102function isRelativePath(str) {
103 return RELATIVE_PATH_REGEXP.test(str);
104}
105
106// TODO simplify for the next major release
107/**
108 * @param {LoaderContext} loaderContext the loader context
109 * @param {string} request a request
110 * @returns {string} a stringified request
111 */
112function stringifyRequest(loaderContext, request) {
113 if (typeof loaderContext.utils !== "undefined" && typeof loaderContext.utils.contextify === "function") {
114 return JSON.stringify(loaderContext.utils.contextify(loaderContext.context || loaderContext.rootContext, request));
115 }
116 const splitted = request.split("!");
117 const {
118 context
119 } = loaderContext;
120 return JSON.stringify(splitted.map(part => {
121 // First, separate singlePath from query, because the query might contain paths again
122 const splittedPart = part.match(/^(.*?)(\?.*)/);
123 const query = splittedPart ? splittedPart[2] : "";
124 let singlePath = splittedPart ? splittedPart[1] : part;
125 if (isAbsolutePath(singlePath) && context) {
126 singlePath = path.relative(context, singlePath);
127 if (isAbsolutePath(singlePath)) {
128 // If singlePath still matches an absolute path, singlePath was on a different drive than context.
129 // In this case, we leave the path platform-specific without replacing any separators.
130 // @see https://github.com/webpack/loader-utils/pull/14
131 return singlePath + query;
132 }
133 if (isRelativePath(singlePath) === false) {
134 // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules).
135 singlePath = `./${singlePath}`;
136 }
137 }
138 return singlePath.replace(/\\/g, "/") + query;
139 }).join("!"));
140}
141
142/**
143 * @param {string} filename filename
144 * @param {string} outputPath output path
145 * @param {boolean} enforceRelative true when need to enforce relative path, otherwise false
146 * @returns {string} undo path
147 */
148function getUndoPath(filename, outputPath, enforceRelative) {
149 let depth = -1;
150 let append = "";
151 outputPath = outputPath.replace(/[\\/]$/, "");
152 for (const part of filename.split(/[/\\]+/)) {
153 if (part === "..") {
154 if (depth > -1) {
155 depth--;
156 } else {
157 const i = outputPath.lastIndexOf("/");
158 const j = outputPath.lastIndexOf("\\");
159 const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j);
160 if (pos < 0) {
161 return `${outputPath}/`;
162 }
163 append = `${outputPath.slice(pos + 1)}/${append}`;
164 outputPath = outputPath.slice(0, pos);
165 }
166 } else if (part !== ".") {
167 depth++;
168 }
169 }
170 return depth > 0 ? `${"../".repeat(depth)}${append}` : enforceRelative ? `./${append}` : append;
171}
172
173/* eslint-disable jsdoc/reject-function-type */
174/**
175 * @param {string | Function} value local
176 * @returns {string} stringified local
177 */
178function stringifyLocal(value) {
179 return typeof value === "function" ? value.toString() : JSON.stringify(value);
180}
181/* eslint-enable jsdoc/reject-function-type */
182
183/**
184 * @param {string} str string
185 * @returns {string} string
186 */
187const toSimpleString = str => {
188 // eslint-disable-next-line no-implicit-coercion
189 if (`${+str}` === str) {
190 return str;
191 }
192 return JSON.stringify(str);
193};
194
195/**
196 * @param {string} str string
197 * @returns {string} quoted meta
198 */
199const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
200
201/**
202 * @param {string[]} items items
203 * @returns {string} common prefix
204 */
205const getCommonPrefix = items => {
206 let [prefix] = items;
207 for (let i = 1; i < items.length; i++) {
208 const item = items[i];
209 for (let prefixIndex = 0; prefixIndex < prefix.length; prefixIndex++) {
210 if (item[prefixIndex] !== prefix[prefixIndex]) {
211 prefix = prefix.slice(0, prefixIndex);
212 break;
213 }
214 }
215 }
216 return prefix;
217};
218
219/**
220 * @param {string[]} items items
221 * @returns {string} common suffix
222 */
223const getCommonSuffix = items => {
224 let [suffix] = items;
225 for (let i = 1; i < items.length; i++) {
226 const item = items[i];
227 for (let itemIndex = item.length - 1, suffixIndex = suffix.length - 1; suffixIndex >= 0; itemIndex--, suffixIndex--) {
228 if (item[itemIndex] !== suffix[suffixIndex]) {
229 suffix = suffix.slice(suffixIndex + 1);
230 break;
231 }
232 }
233 }
234 return suffix;
235};
236
237/**
238 * @param {Set<string>} itemsSet items set
239 * @param {(str: string) => string | false} getKey get key function
240 * @param {(str: string[]) => boolean} condition condition
241 * @returns {string[][]} list of common items
242 */
243const popCommonItems = (itemsSet, getKey, condition) => {
244 /** @type {Map<string, string[]>} */
245 const map = new Map();
246 for (const item of itemsSet) {
247 const key = getKey(item);
248 if (key) {
249 let list = map.get(key);
250 if (list === undefined) {
251 /** @type {string[]} */
252 list = [];
253 map.set(key, list);
254 }
255 list.push(item);
256 }
257 }
258
259 /** @type {string[][]} */
260 const result = [];
261 for (const list of map.values()) {
262 if (condition(list)) {
263 for (const item of list) {
264 itemsSet.delete(item);
265 }
266 result.push(list);
267 }
268 }
269 return result;
270};
271
272/**
273 * @param {string[]} itemsArr array of items
274 * @returns {string} regexp
275 */
276const itemsToRegexp = itemsArr => {
277 if (itemsArr.length === 1) {
278 return quoteMeta(itemsArr[0]);
279 }
280
281 /** @type {string[]} */
282 const finishedItems = [];
283
284 // merge single char items: (a|b|c|d|ef) => ([abcd]|ef)
285 let countOfSingleCharItems = 0;
286 for (const item of itemsArr) {
287 if (item.length === 1) {
288 countOfSingleCharItems++;
289 }
290 }
291
292 // special case for only single char items
293 if (countOfSingleCharItems === itemsArr.length) {
294 return `[${quoteMeta(itemsArr.sort().join(""))}]`;
295 }
296 const items = new Set(itemsArr.sort());
297 if (countOfSingleCharItems > 2) {
298 let singleCharItems = "";
299 for (const item of items) {
300 if (item.length === 1) {
301 singleCharItems += item;
302 items.delete(item);
303 }
304 }
305 finishedItems.push(`[${quoteMeta(singleCharItems)}]`);
306 }
307
308 // special case for 2 items with common prefix/suffix
309 if (finishedItems.length === 0 && items.size === 2) {
310 const prefix = getCommonPrefix(itemsArr);
311 const suffix = getCommonSuffix(itemsArr.map(item => item.slice(prefix.length)));
312 if (prefix.length > 0 || suffix.length > 0) {
313 return `${quoteMeta(prefix)}${itemsToRegexp(itemsArr.map(i => i.slice(prefix.length, -suffix.length || undefined)))}${quoteMeta(suffix)}`;
314 }
315 }
316
317 // special case for 2 items with common suffix
318 if (finishedItems.length === 0 && items.size === 2) {
319 /** @type {Iterator<string>} */
320 const it = items[Symbol.iterator]();
321 const a = it.next().value;
322 const b = it.next().value;
323 if (a.length > 0 && b.length > 0 && a.slice(-1) === b.slice(-1)) {
324 return `${itemsToRegexp([a.slice(0, -1), b.slice(0, -1)])}${quoteMeta(a.slice(-1))}`;
325 }
326 }
327
328 // find common prefix: (a1|a2|a3|a4|b5) => (a(1|2|3|4)|b5)
329 const prefixed = popCommonItems(items, item => item.length >= 1 ? item[0] : false, list => {
330 if (list.length >= 3) return true;
331 if (list.length <= 1) return false;
332 return list[0][1] === list[1][1];
333 });
334 for (const prefixedItems of prefixed) {
335 const prefix = getCommonPrefix(prefixedItems);
336 finishedItems.push(`${quoteMeta(prefix)}${itemsToRegexp(prefixedItems.map(i => i.slice(prefix.length)))}`);
337 }
338
339 // find common suffix: (a1|b1|c1|d1|e2) => ((a|b|c|d)1|e2)
340 const suffixed = popCommonItems(items, item => item.length >= 1 ? item.slice(-1) : false, list => {
341 if (list.length >= 3) return true;
342 if (list.length <= 1) return false;
343 return list[0].slice(-2) === list[1].slice(-2);
344 });
345 for (const suffixedItems of suffixed) {
346 const suffix = getCommonSuffix(suffixedItems);
347 finishedItems.push(`${itemsToRegexp(suffixedItems.map(i => i.slice(0, -suffix.length)))}${quoteMeta(suffix)}`);
348 }
349
350 // TODO further optimize regexp, i. e.
351 // use ranges: (1|2|3|4|a) => [1-4a]
352 const conditional = [...finishedItems, ...Array.from(items, quoteMeta)];
353 if (conditional.length === 1) return conditional[0];
354 return `(${conditional.join("|")})`;
355};
356
357/**
358 * @param {string[]} positiveItems positive items
359 * @param {string[]} negativeItems negative items
360 * @returns {(val: string) => string} a template function to determine the value at runtime
361 */
362const compileBooleanMatcherFromLists = (positiveItems, negativeItems) => {
363 if (positiveItems.length === 0) {
364 return () => "false";
365 }
366 if (negativeItems.length === 0) {
367 return () => "true";
368 }
369 if (positiveItems.length === 1) {
370 return value => `${toSimpleString(positiveItems[0])} == ${value}`;
371 }
372 if (negativeItems.length === 1) {
373 return value => `${toSimpleString(negativeItems[0])} != ${value}`;
374 }
375 const positiveRegexp = itemsToRegexp(positiveItems);
376 const negativeRegexp = itemsToRegexp(negativeItems);
377 if (positiveRegexp.length <= negativeRegexp.length) {
378 return value => `/^${positiveRegexp}$/.test(${value})`;
379 }
380 return value => `!/^${negativeRegexp}$/.test(${value})`;
381};
382
383// TODO simplify in the next major release and use it from webpack
384/**
385 * @param {Record<string | number, boolean>} map value map
386 * @returns {boolean | ((value: string) => string)} true/false, when unconditionally true/false, or a template function to determine the value at runtime
387 */
388const compileBooleanMatcher = map => {
389 const positiveItems = Object.keys(map).filter(i => map[i]);
390 const negativeItems = Object.keys(map).filter(i => !map[i]);
391 if (positiveItems.length === 0) {
392 return false;
393 }
394 if (negativeItems.length === 0) {
395 return true;
396 }
397 return compileBooleanMatcherFromLists(positiveItems, negativeItems);
398};
399module.exports = {
400 ABSOLUTE_PUBLIC_PATH,
401 AUTO_PUBLIC_PATH,
402 BASE_URI,
403 MODULE_TYPE,
404 SINGLE_DOT_PATH_SEGMENT,
405 compareModulesByIdentifier,
406 compileBooleanMatcher,
407 evalModuleCode,
408 findModuleById,
409 getUndoPath,
410 stringifyLocal,
411 stringifyRequest,
412 trueFn
413};
Note: See TracBrowser for help on using the repository browser.