UNPKG

8.17 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 NormalModule = require("./NormalModule");
9const createHash = require("./util/createHash");
10const memoize = require("./util/memoize");
11
12/** @typedef {import("./ChunkGraph")} ChunkGraph */
13/** @typedef {import("./Module")} Module */
14/** @typedef {import("./RequestShortener")} RequestShortener */
15/** @typedef {typeof import("./util/Hash")} Hash */
16
17const ModuleFilenameHelpers = exports;
18
19// TODO webpack 6: consider removing these
20ModuleFilenameHelpers.ALL_LOADERS_RESOURCE = "[all-loaders][resource]";
21ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE =
22 /\[all-?loaders\]\[resource\]/gi;
23ModuleFilenameHelpers.LOADERS_RESOURCE = "[loaders][resource]";
24ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE = /\[loaders\]\[resource\]/gi;
25ModuleFilenameHelpers.RESOURCE = "[resource]";
26ModuleFilenameHelpers.REGEXP_RESOURCE = /\[resource\]/gi;
27ModuleFilenameHelpers.ABSOLUTE_RESOURCE_PATH = "[absolute-resource-path]";
28// cSpell:words olute
29ModuleFilenameHelpers.REGEXP_ABSOLUTE_RESOURCE_PATH =
30 /\[abs(olute)?-?resource-?path\]/gi;
31ModuleFilenameHelpers.RESOURCE_PATH = "[resource-path]";
32ModuleFilenameHelpers.REGEXP_RESOURCE_PATH = /\[resource-?path\]/gi;
33ModuleFilenameHelpers.ALL_LOADERS = "[all-loaders]";
34ModuleFilenameHelpers.REGEXP_ALL_LOADERS = /\[all-?loaders\]/gi;
35ModuleFilenameHelpers.LOADERS = "[loaders]";
36ModuleFilenameHelpers.REGEXP_LOADERS = /\[loaders\]/gi;
37ModuleFilenameHelpers.QUERY = "[query]";
38ModuleFilenameHelpers.REGEXP_QUERY = /\[query\]/gi;
39ModuleFilenameHelpers.ID = "[id]";
40ModuleFilenameHelpers.REGEXP_ID = /\[id\]/gi;
41ModuleFilenameHelpers.HASH = "[hash]";
42ModuleFilenameHelpers.REGEXP_HASH = /\[hash\]/gi;
43ModuleFilenameHelpers.NAMESPACE = "[namespace]";
44ModuleFilenameHelpers.REGEXP_NAMESPACE = /\[namespace\]/gi;
45
46const getAfter = (strFn, token) => {
47 return () => {
48 const str = strFn();
49 const idx = str.indexOf(token);
50 return idx < 0 ? "" : str.substr(idx);
51 };
52};
53
54const getBefore = (strFn, token) => {
55 return () => {
56 const str = strFn();
57 const idx = str.lastIndexOf(token);
58 return idx < 0 ? "" : str.substr(0, idx);
59 };
60};
61
62const getHash = (strFn, hashFunction) => {
63 return () => {
64 const hash = createHash(hashFunction);
65 hash.update(strFn());
66 const digest = /** @type {string} */ (hash.digest("hex"));
67 return digest.substr(0, 4);
68 };
69};
70
71const asRegExp = test => {
72 if (typeof test === "string") {
73 test = new RegExp("^" + test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"));
74 }
75 return test;
76};
77
78const lazyObject = obj => {
79 const newObj = {};
80 for (const key of Object.keys(obj)) {
81 const fn = obj[key];
82 Object.defineProperty(newObj, key, {
83 get: () => fn(),
84 set: v => {
85 Object.defineProperty(newObj, key, {
86 value: v,
87 enumerable: true,
88 writable: true
89 });
90 },
91 enumerable: true,
92 configurable: true
93 });
94 }
95 return newObj;
96};
97
98const REGEXP = /\[\\*([\w-]+)\\*\]/gi;
99
100/**
101 *
102 * @param {Module | string} module the module
103 * @param {TODO} options options
104 * @param {Object} contextInfo context info
105 * @param {RequestShortener} contextInfo.requestShortener requestShortener
106 * @param {ChunkGraph} contextInfo.chunkGraph chunk graph
107 * @param {string | Hash} contextInfo.hashFunction the hash function to use
108 * @returns {string} the filename
109 */
110ModuleFilenameHelpers.createFilename = (
111 module = "",
112 options,
113 { requestShortener, chunkGraph, hashFunction = "md4" }
114) => {
115 const opts = {
116 namespace: "",
117 moduleFilenameTemplate: "",
118 ...(typeof options === "object"
119 ? options
120 : {
121 moduleFilenameTemplate: options
122 })
123 };
124
125 let absoluteResourcePath;
126 let hash;
127 let identifier;
128 let moduleId;
129 let shortIdentifier;
130 if (typeof module === "string") {
131 shortIdentifier = memoize(() => requestShortener.shorten(module));
132 identifier = shortIdentifier;
133 moduleId = () => "";
134 absoluteResourcePath = () => module.split("!").pop();
135 hash = getHash(identifier, hashFunction);
136 } else {
137 shortIdentifier = memoize(() =>
138 module.readableIdentifier(requestShortener)
139 );
140 identifier = memoize(() => requestShortener.shorten(module.identifier()));
141 moduleId = () => chunkGraph.getModuleId(module);
142 absoluteResourcePath = () =>
143 module instanceof NormalModule
144 ? module.resource
145 : module.identifier().split("!").pop();
146 hash = getHash(identifier, hashFunction);
147 }
148 const resource = memoize(() => shortIdentifier().split("!").pop());
149
150 const loaders = getBefore(shortIdentifier, "!");
151 const allLoaders = getBefore(identifier, "!");
152 const query = getAfter(resource, "?");
153 const resourcePath = () => {
154 const q = query().length;
155 return q === 0 ? resource() : resource().slice(0, -q);
156 };
157 if (typeof opts.moduleFilenameTemplate === "function") {
158 return opts.moduleFilenameTemplate(
159 lazyObject({
160 identifier: identifier,
161 shortIdentifier: shortIdentifier,
162 resource: resource,
163 resourcePath: memoize(resourcePath),
164 absoluteResourcePath: memoize(absoluteResourcePath),
165 allLoaders: memoize(allLoaders),
166 query: memoize(query),
167 moduleId: memoize(moduleId),
168 hash: memoize(hash),
169 namespace: () => opts.namespace
170 })
171 );
172 }
173
174 // TODO webpack 6: consider removing alternatives without dashes
175 /** @type {Map<string, function(): string>} */
176 const replacements = new Map([
177 ["identifier", identifier],
178 ["short-identifier", shortIdentifier],
179 ["resource", resource],
180 ["resource-path", resourcePath],
181 // cSpell:words resourcepath
182 ["resourcepath", resourcePath],
183 ["absolute-resource-path", absoluteResourcePath],
184 ["abs-resource-path", absoluteResourcePath],
185 // cSpell:words absoluteresource
186 ["absoluteresource-path", absoluteResourcePath],
187 // cSpell:words absresource
188 ["absresource-path", absoluteResourcePath],
189 // cSpell:words resourcepath
190 ["absolute-resourcepath", absoluteResourcePath],
191 // cSpell:words resourcepath
192 ["abs-resourcepath", absoluteResourcePath],
193 // cSpell:words absoluteresourcepath
194 ["absoluteresourcepath", absoluteResourcePath],
195 // cSpell:words absresourcepath
196 ["absresourcepath", absoluteResourcePath],
197 ["all-loaders", allLoaders],
198 // cSpell:words allloaders
199 ["allloaders", allLoaders],
200 ["loaders", loaders],
201 ["query", query],
202 ["id", moduleId],
203 ["hash", hash],
204 ["namespace", () => opts.namespace]
205 ]);
206
207 // TODO webpack 6: consider removing weird double placeholders
208 return opts.moduleFilenameTemplate
209 .replace(ModuleFilenameHelpers.REGEXP_ALL_LOADERS_RESOURCE, "[identifier]")
210 .replace(
211 ModuleFilenameHelpers.REGEXP_LOADERS_RESOURCE,
212 "[short-identifier]"
213 )
214 .replace(REGEXP, (match, content) => {
215 if (content.length + 2 === match.length) {
216 const replacement = replacements.get(content.toLowerCase());
217 if (replacement !== undefined) {
218 return replacement();
219 }
220 } else if (match.startsWith("[\\") && match.endsWith("\\]")) {
221 return `[${match.slice(2, -2)}]`;
222 }
223 return match;
224 });
225};
226
227ModuleFilenameHelpers.replaceDuplicates = (array, fn, comparator) => {
228 const countMap = Object.create(null);
229 const posMap = Object.create(null);
230 array.forEach((item, idx) => {
231 countMap[item] = countMap[item] || [];
232 countMap[item].push(idx);
233 posMap[item] = 0;
234 });
235 if (comparator) {
236 Object.keys(countMap).forEach(item => {
237 countMap[item].sort(comparator);
238 });
239 }
240 return array.map((item, i) => {
241 if (countMap[item].length > 1) {
242 if (comparator && countMap[item][0] === i) return item;
243 return fn(item, i, posMap[item]++);
244 } else {
245 return item;
246 }
247 });
248};
249
250ModuleFilenameHelpers.matchPart = (str, test) => {
251 if (!test) return true;
252 test = asRegExp(test);
253 if (Array.isArray(test)) {
254 return test.map(asRegExp).some(regExp => regExp.test(str));
255 } else {
256 return test.test(str);
257 }
258};
259
260ModuleFilenameHelpers.matchObject = (obj, str) => {
261 if (obj.test) {
262 if (!ModuleFilenameHelpers.matchPart(str, obj.test)) {
263 return false;
264 }
265 }
266 if (obj.include) {
267 if (!ModuleFilenameHelpers.matchPart(str, obj.include)) {
268 return false;
269 }
270 }
271 if (obj.exclude) {
272 if (ModuleFilenameHelpers.matchPart(str, obj.exclude)) {
273 return false;
274 }
275 }
276 return true;
277};