1 | /*
|
---|
2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
3 | Author Jason Anderson @diurnalist
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | const { basename, extname } = require("path");
|
---|
9 | const util = require("util");
|
---|
10 | const Chunk = require("./Chunk");
|
---|
11 | const Module = require("./Module");
|
---|
12 | const { parseResource } = require("./util/identifier");
|
---|
13 |
|
---|
14 | /** @typedef {import("./Compilation").AssetInfo} AssetInfo */
|
---|
15 | /** @typedef {import("./Compilation").PathData} PathData */
|
---|
16 | /** @typedef {import("./Compiler")} Compiler */
|
---|
17 |
|
---|
18 | const REGEXP = /\[\\*([\w:]+)\\*\]/gi;
|
---|
19 |
|
---|
20 | const prepareId = id => {
|
---|
21 | if (typeof id !== "string") return id;
|
---|
22 |
|
---|
23 | if (/^"\s\+*.*\+\s*"$/.test(id)) {
|
---|
24 | const match = /^"\s\+*\s*(.*)\s*\+\s*"$/.exec(id);
|
---|
25 |
|
---|
26 | return `" + (${match[1]} + "").replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_") + "`;
|
---|
27 | }
|
---|
28 |
|
---|
29 | return id.replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_");
|
---|
30 | };
|
---|
31 |
|
---|
32 | const hashLength = (replacer, handler, assetInfo, hashName) => {
|
---|
33 | const fn = (match, arg, input) => {
|
---|
34 | let result;
|
---|
35 | const length = arg && parseInt(arg, 10);
|
---|
36 |
|
---|
37 | if (length && handler) {
|
---|
38 | result = handler(length);
|
---|
39 | } else {
|
---|
40 | const hash = replacer(match, arg, input);
|
---|
41 |
|
---|
42 | result = length ? hash.slice(0, length) : hash;
|
---|
43 | }
|
---|
44 | if (assetInfo) {
|
---|
45 | assetInfo.immutable = true;
|
---|
46 | if (Array.isArray(assetInfo[hashName])) {
|
---|
47 | assetInfo[hashName] = [...assetInfo[hashName], result];
|
---|
48 | } else if (assetInfo[hashName]) {
|
---|
49 | assetInfo[hashName] = [assetInfo[hashName], result];
|
---|
50 | } else {
|
---|
51 | assetInfo[hashName] = result;
|
---|
52 | }
|
---|
53 | }
|
---|
54 | return result;
|
---|
55 | };
|
---|
56 |
|
---|
57 | return fn;
|
---|
58 | };
|
---|
59 |
|
---|
60 | const replacer = (value, allowEmpty) => {
|
---|
61 | const fn = (match, arg, input) => {
|
---|
62 | if (typeof value === "function") {
|
---|
63 | value = value();
|
---|
64 | }
|
---|
65 | if (value === null || value === undefined) {
|
---|
66 | if (!allowEmpty) {
|
---|
67 | throw new Error(
|
---|
68 | `Path variable ${match} not implemented in this context: ${input}`
|
---|
69 | );
|
---|
70 | }
|
---|
71 |
|
---|
72 | return "";
|
---|
73 | } else {
|
---|
74 | return `${value}`;
|
---|
75 | }
|
---|
76 | };
|
---|
77 |
|
---|
78 | return fn;
|
---|
79 | };
|
---|
80 |
|
---|
81 | const deprecationCache = new Map();
|
---|
82 | const deprecatedFunction = (() => () => {})();
|
---|
83 | const deprecated = (fn, message, code) => {
|
---|
84 | let d = deprecationCache.get(message);
|
---|
85 | if (d === undefined) {
|
---|
86 | d = util.deprecate(deprecatedFunction, message, code);
|
---|
87 | deprecationCache.set(message, d);
|
---|
88 | }
|
---|
89 | return (...args) => {
|
---|
90 | d();
|
---|
91 | return fn(...args);
|
---|
92 | };
|
---|
93 | };
|
---|
94 |
|
---|
95 | /**
|
---|
96 | * @param {string | function(PathData, AssetInfo=): string} path the raw path
|
---|
97 | * @param {PathData} data context data
|
---|
98 | * @param {AssetInfo} assetInfo extra info about the asset (will be written to)
|
---|
99 | * @returns {string} the interpolated path
|
---|
100 | */
|
---|
101 | const replacePathVariables = (path, data, assetInfo) => {
|
---|
102 | const chunkGraph = data.chunkGraph;
|
---|
103 |
|
---|
104 | /** @type {Map<string, Function>} */
|
---|
105 | const replacements = new Map();
|
---|
106 |
|
---|
107 | // Filename context
|
---|
108 | //
|
---|
109 | // Placeholders
|
---|
110 | //
|
---|
111 | // for /some/path/file.js?query#fragment:
|
---|
112 | // [file] - /some/path/file.js
|
---|
113 | // [query] - ?query
|
---|
114 | // [fragment] - #fragment
|
---|
115 | // [base] - file.js
|
---|
116 | // [path] - /some/path/
|
---|
117 | // [name] - file
|
---|
118 | // [ext] - .js
|
---|
119 | if (typeof data.filename === "string") {
|
---|
120 | const { path: file, query, fragment } = parseResource(data.filename);
|
---|
121 |
|
---|
122 | const ext = extname(file);
|
---|
123 | const base = basename(file);
|
---|
124 | const name = base.slice(0, base.length - ext.length);
|
---|
125 | const path = file.slice(0, file.length - base.length);
|
---|
126 |
|
---|
127 | replacements.set("file", replacer(file));
|
---|
128 | replacements.set("query", replacer(query, true));
|
---|
129 | replacements.set("fragment", replacer(fragment, true));
|
---|
130 | replacements.set("path", replacer(path, true));
|
---|
131 | replacements.set("base", replacer(base));
|
---|
132 | replacements.set("name", replacer(name));
|
---|
133 | replacements.set("ext", replacer(ext, true));
|
---|
134 | // Legacy
|
---|
135 | replacements.set(
|
---|
136 | "filebase",
|
---|
137 | deprecated(
|
---|
138 | replacer(base),
|
---|
139 | "[filebase] is now [base]",
|
---|
140 | "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_FILENAME"
|
---|
141 | )
|
---|
142 | );
|
---|
143 | }
|
---|
144 |
|
---|
145 | // Compilation context
|
---|
146 | //
|
---|
147 | // Placeholders
|
---|
148 | //
|
---|
149 | // [fullhash] - data.hash (3a4b5c6e7f)
|
---|
150 | //
|
---|
151 | // Legacy Placeholders
|
---|
152 | //
|
---|
153 | // [hash] - data.hash (3a4b5c6e7f)
|
---|
154 | if (data.hash) {
|
---|
155 | const hashReplacer = hashLength(
|
---|
156 | replacer(data.hash),
|
---|
157 | data.hashWithLength,
|
---|
158 | assetInfo,
|
---|
159 | "fullhash"
|
---|
160 | );
|
---|
161 |
|
---|
162 | replacements.set("fullhash", hashReplacer);
|
---|
163 |
|
---|
164 | // Legacy
|
---|
165 | replacements.set(
|
---|
166 | "hash",
|
---|
167 | deprecated(
|
---|
168 | hashReplacer,
|
---|
169 | "[hash] is now [fullhash] (also consider using [chunkhash] or [contenthash], see documentation for details)",
|
---|
170 | "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_HASH"
|
---|
171 | )
|
---|
172 | );
|
---|
173 | }
|
---|
174 |
|
---|
175 | // Chunk Context
|
---|
176 | //
|
---|
177 | // Placeholders
|
---|
178 | //
|
---|
179 | // [id] - chunk.id (0.js)
|
---|
180 | // [name] - chunk.name (app.js)
|
---|
181 | // [chunkhash] - chunk.hash (7823t4t4.js)
|
---|
182 | // [contenthash] - chunk.contentHash[type] (3256u3zg.js)
|
---|
183 | if (data.chunk) {
|
---|
184 | const chunk = data.chunk;
|
---|
185 |
|
---|
186 | const contentHashType = data.contentHashType;
|
---|
187 |
|
---|
188 | const idReplacer = replacer(chunk.id);
|
---|
189 | const nameReplacer = replacer(chunk.name || chunk.id);
|
---|
190 | const chunkhashReplacer = hashLength(
|
---|
191 | replacer(chunk instanceof Chunk ? chunk.renderedHash : chunk.hash),
|
---|
192 | "hashWithLength" in chunk ? chunk.hashWithLength : undefined,
|
---|
193 | assetInfo,
|
---|
194 | "chunkhash"
|
---|
195 | );
|
---|
196 | const contenthashReplacer = hashLength(
|
---|
197 | replacer(
|
---|
198 | data.contentHash ||
|
---|
199 | (contentHashType &&
|
---|
200 | chunk.contentHash &&
|
---|
201 | chunk.contentHash[contentHashType])
|
---|
202 | ),
|
---|
203 | data.contentHashWithLength ||
|
---|
204 | ("contentHashWithLength" in chunk && chunk.contentHashWithLength
|
---|
205 | ? chunk.contentHashWithLength[contentHashType]
|
---|
206 | : undefined),
|
---|
207 | assetInfo,
|
---|
208 | "contenthash"
|
---|
209 | );
|
---|
210 |
|
---|
211 | replacements.set("id", idReplacer);
|
---|
212 | replacements.set("name", nameReplacer);
|
---|
213 | replacements.set("chunkhash", chunkhashReplacer);
|
---|
214 | replacements.set("contenthash", contenthashReplacer);
|
---|
215 | }
|
---|
216 |
|
---|
217 | // Module Context
|
---|
218 | //
|
---|
219 | // Placeholders
|
---|
220 | //
|
---|
221 | // [id] - module.id (2.png)
|
---|
222 | // [hash] - module.hash (6237543873.png)
|
---|
223 | //
|
---|
224 | // Legacy Placeholders
|
---|
225 | //
|
---|
226 | // [moduleid] - module.id (2.png)
|
---|
227 | // [modulehash] - module.hash (6237543873.png)
|
---|
228 | if (data.module) {
|
---|
229 | const module = data.module;
|
---|
230 |
|
---|
231 | const idReplacer = replacer(() =>
|
---|
232 | prepareId(
|
---|
233 | module instanceof Module ? chunkGraph.getModuleId(module) : module.id
|
---|
234 | )
|
---|
235 | );
|
---|
236 | const moduleHashReplacer = hashLength(
|
---|
237 | replacer(() =>
|
---|
238 | module instanceof Module
|
---|
239 | ? chunkGraph.getRenderedModuleHash(module, data.runtime)
|
---|
240 | : module.hash
|
---|
241 | ),
|
---|
242 | "hashWithLength" in module ? module.hashWithLength : undefined,
|
---|
243 | assetInfo,
|
---|
244 | "modulehash"
|
---|
245 | );
|
---|
246 | const contentHashReplacer = hashLength(
|
---|
247 | replacer(data.contentHash),
|
---|
248 | undefined,
|
---|
249 | assetInfo,
|
---|
250 | "contenthash"
|
---|
251 | );
|
---|
252 |
|
---|
253 | replacements.set("id", idReplacer);
|
---|
254 | replacements.set("modulehash", moduleHashReplacer);
|
---|
255 | replacements.set("contenthash", contentHashReplacer);
|
---|
256 | replacements.set(
|
---|
257 | "hash",
|
---|
258 | data.contentHash ? contentHashReplacer : moduleHashReplacer
|
---|
259 | );
|
---|
260 | // Legacy
|
---|
261 | replacements.set(
|
---|
262 | "moduleid",
|
---|
263 | deprecated(
|
---|
264 | idReplacer,
|
---|
265 | "[moduleid] is now [id]",
|
---|
266 | "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_MODULE_ID"
|
---|
267 | )
|
---|
268 | );
|
---|
269 | }
|
---|
270 |
|
---|
271 | // Other things
|
---|
272 | if (data.url) {
|
---|
273 | replacements.set("url", replacer(data.url));
|
---|
274 | }
|
---|
275 | if (typeof data.runtime === "string") {
|
---|
276 | replacements.set(
|
---|
277 | "runtime",
|
---|
278 | replacer(() => prepareId(data.runtime))
|
---|
279 | );
|
---|
280 | } else {
|
---|
281 | replacements.set("runtime", replacer("_"));
|
---|
282 | }
|
---|
283 |
|
---|
284 | if (typeof path === "function") {
|
---|
285 | path = path(data, assetInfo);
|
---|
286 | }
|
---|
287 |
|
---|
288 | path = path.replace(REGEXP, (match, content) => {
|
---|
289 | if (content.length + 2 === match.length) {
|
---|
290 | const contentMatch = /^(\w+)(?::(\w+))?$/.exec(content);
|
---|
291 | if (!contentMatch) return match;
|
---|
292 | const [, kind, arg] = contentMatch;
|
---|
293 | const replacer = replacements.get(kind);
|
---|
294 | if (replacer !== undefined) {
|
---|
295 | return replacer(match, arg, path);
|
---|
296 | }
|
---|
297 | } else if (match.startsWith("[\\") && match.endsWith("\\]")) {
|
---|
298 | return `[${match.slice(2, -2)}]`;
|
---|
299 | }
|
---|
300 | return match;
|
---|
301 | });
|
---|
302 |
|
---|
303 | return path;
|
---|
304 | };
|
---|
305 |
|
---|
306 | const plugin = "TemplatedPathPlugin";
|
---|
307 |
|
---|
308 | class TemplatedPathPlugin {
|
---|
309 | /**
|
---|
310 | * Apply the plugin
|
---|
311 | * @param {Compiler} compiler the compiler instance
|
---|
312 | * @returns {void}
|
---|
313 | */
|
---|
314 | apply(compiler) {
|
---|
315 | compiler.hooks.compilation.tap(plugin, compilation => {
|
---|
316 | compilation.hooks.assetPath.tap(plugin, replacePathVariables);
|
---|
317 | });
|
---|
318 | }
|
---|
319 | }
|
---|
320 |
|
---|
321 | module.exports = TemplatedPathPlugin;
|
---|