[6a3a178] | 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;
|
---|