2014-08-22 19:51:24 +08:00
|
|
|
/*
|
|
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
|
|
Author Jason Anderson @diurnalist
|
|
|
|
*/
|
2018-07-30 23:08:51 +08:00
|
|
|
|
2017-01-06 23:11:30 +08:00
|
|
|
"use strict";
|
2014-08-22 19:51:24 +08:00
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
const { basename, extname } = require("path");
|
|
|
|
|
2018-08-23 01:23:48 +08:00
|
|
|
const Module = require("./Module");
|
|
|
|
|
|
|
|
/** @typedef {import("./Compilation").PathData} PathData */
|
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
const REGEXP_ID = /\[id\]/gi;
|
|
|
|
const REGEXP_EXT = /\[ext\]/gi;
|
|
|
|
const REGEXP_NAME = /\[name\]/gi;
|
|
|
|
const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi;
|
|
|
|
const REGEXP_QUERY = /\[query\]/gi;
|
|
|
|
const REGEXP_FILE = /\[file\]/gi;
|
|
|
|
const REGEXP_FILEBASE = /\[filebase\]/gi;
|
|
|
|
// Chunks
|
|
|
|
const REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/gi;
|
|
|
|
const REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/gi;
|
|
|
|
// Modules
|
|
|
|
const REGEXP_MODULEID = /\[moduleid\]/gi;
|
|
|
|
const REGEXP_MODULEHASH = /\[modulehash(?::(\d+))?\]/gi;
|
2014-08-22 19:51:24 +08:00
|
|
|
|
2018-09-06 03:20:22 +08:00
|
|
|
const prepareId = id => {
|
|
|
|
if (typeof id !== "string") return id;
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2018-09-06 03:20:22 +08:00
|
|
|
if (/^"\s\+*.*\+\s*"$/.test(id)) {
|
|
|
|
const match = /^"\s\+*\s*(.*)\s*\+\s*"$/.exec(id);
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2018-09-06 03:20:22 +08:00
|
|
|
return `" + (${
|
|
|
|
match[1]
|
|
|
|
} + "").replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_") + "`;
|
|
|
|
}
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2018-09-06 03:20:22 +08:00
|
|
|
return id.replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_");
|
|
|
|
};
|
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
const hashLength = (replacer, handler) => {
|
2017-11-09 03:49:41 +08:00
|
|
|
const fn = (match, hashLength, ...args) => {
|
2017-01-06 23:11:30 +08:00
|
|
|
const length = hashLength && parseInt(hashLength, 10);
|
2018-09-28 19:10:37 +08:00
|
|
|
|
|
|
|
if (length && handler) {
|
|
|
|
return handler(length);
|
2014-09-12 01:25:18 +08:00
|
|
|
}
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2017-11-08 18:32:05 +08:00
|
|
|
const hash = replacer(match, hashLength, ...args);
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2014-09-12 01:25:18 +08:00
|
|
|
return length ? hash.slice(0, length) : hash;
|
2014-08-22 19:51:24 +08:00
|
|
|
};
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2017-11-09 03:49:41 +08:00
|
|
|
return fn;
|
2017-01-11 17:51:58 +08:00
|
|
|
};
|
2014-08-22 19:51:24 +08:00
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
const replacer = (value, allowEmpty) => {
|
2017-11-09 03:49:41 +08:00
|
|
|
const fn = (match, ...args) => {
|
2014-08-22 19:51:24 +08:00
|
|
|
// last argument in replacer is the entire input string
|
2017-11-08 18:32:05 +08:00
|
|
|
const input = args[args.length - 1];
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2018-02-25 09:00:20 +08:00
|
|
|
if (value === null || value === undefined) {
|
2018-05-29 20:50:40 +08:00
|
|
|
if (!allowEmpty) {
|
2018-02-25 09:00:20 +08:00
|
|
|
throw new Error(
|
|
|
|
`Path variable ${match} not implemented in this context: ${input}`
|
|
|
|
);
|
2018-05-29 20:50:40 +08:00
|
|
|
}
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2014-08-22 19:51:24 +08:00
|
|
|
return "";
|
|
|
|
} else {
|
2017-01-06 23:11:30 +08:00
|
|
|
return `${value}`;
|
2014-08-22 19:51:24 +08:00
|
|
|
}
|
|
|
|
};
|
2018-09-28 19:10:37 +08:00
|
|
|
|
2017-11-09 03:49:41 +08:00
|
|
|
return fn;
|
2017-01-11 17:51:58 +08:00
|
|
|
};
|
2014-08-22 19:51:24 +08:00
|
|
|
|
2018-08-23 01:23:48 +08:00
|
|
|
/**
|
|
|
|
* @param {string | function(PathData): string} path the raw path
|
|
|
|
* @param {PathData} data context data
|
|
|
|
* @returns {string} the interpolated path
|
|
|
|
*/
|
2017-01-06 23:11:30 +08:00
|
|
|
const replacePathVariables = (path, data) => {
|
2018-08-23 01:23:48 +08:00
|
|
|
const chunkGraph = data.chunkGraph;
|
2018-09-28 19:10:37 +08:00
|
|
|
|
|
|
|
let file = {};
|
|
|
|
|
2018-10-30 17:49:49 +08:00
|
|
|
const replacements = new Map();
|
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
if (data.filename) {
|
|
|
|
if (typeof data.filename === "string") {
|
|
|
|
const idx = data.filename.indexOf("?");
|
|
|
|
|
|
|
|
if (idx >= 0) {
|
|
|
|
file.path = data.filename.substr(0, idx);
|
|
|
|
file.query = data.filename.substr(idx);
|
|
|
|
} else {
|
|
|
|
file.path = data.filename;
|
|
|
|
file.query = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
file.ext = extname(file.path);
|
|
|
|
file.base = basename(file.path);
|
|
|
|
file.name = file.base.replace(file.ext, "");
|
|
|
|
}
|
2017-12-20 10:03:15 +08:00
|
|
|
}
|
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
// Chunk Context
|
|
|
|
//
|
|
|
|
// Placeholders
|
|
|
|
//
|
|
|
|
// [id] - chunk.id (0.js)
|
|
|
|
// [ext] - file.ext (app.{js, css, ...})
|
|
|
|
// [name] - chunk.name (app.js)
|
|
|
|
// [hash] - data.hash (53276435.js)
|
|
|
|
// [chunkhash] - chunk.hash (7823t4t4.js)
|
|
|
|
// [contenthash] - chunk.contentHash[type] (3256urzg.js)
|
|
|
|
// [query] - data.query (app.js?v=1.0.0)
|
|
|
|
// [file] - file.path (/context/file.map.js)
|
|
|
|
// [filebase] - file.base (file.map.js)
|
|
|
|
if (data.chunk) {
|
|
|
|
const chunk = data.chunk;
|
|
|
|
|
|
|
|
const id = chunk.id;
|
|
|
|
const name = chunk.name || chunk.id;
|
|
|
|
|
|
|
|
const chunkHash = chunk.renderedHash || chunk.hash;
|
|
|
|
// @ts-ignore
|
|
|
|
const chunkHashWithLength = chunk.hashWithLength || undefined;
|
|
|
|
|
|
|
|
const contentHashType = data.contentHashType;
|
|
|
|
const contentHash =
|
|
|
|
data.contentHash ||
|
|
|
|
(contentHashType &&
|
|
|
|
chunk.contentHash &&
|
|
|
|
chunk.contentHash[contentHashType]);
|
|
|
|
const contentHashWithLength =
|
|
|
|
data.contentHashWithLength ||
|
|
|
|
// @ts-ignore
|
|
|
|
(chunk.contentHashWithLength &&
|
|
|
|
// @ts-ignore
|
|
|
|
chunk.contentHashWithLength[contentHashType]) ||
|
|
|
|
undefined;
|
|
|
|
|
2018-10-30 17:49:49 +08:00
|
|
|
replacements.set(REGEXP_ID, replacer(id));
|
|
|
|
replacements.set(REGEXP_EXT, replacer(file.ext));
|
|
|
|
replacements.set(REGEXP_NAME, replacer(name));
|
|
|
|
replacements.set(
|
|
|
|
REGEXP_HASH,
|
|
|
|
hashLength(replacer(data.hash), data.hashWithLength)
|
2018-09-28 19:10:37 +08:00
|
|
|
);
|
2018-10-30 17:49:49 +08:00
|
|
|
replacements.set(
|
|
|
|
REGEXP_CHUNKHASH,
|
|
|
|
hashLength(replacer(chunkHash), chunkHashWithLength)
|
|
|
|
);
|
|
|
|
replacements.set(
|
|
|
|
REGEXP_CONTENTHASH,
|
|
|
|
hashLength(replacer(contentHash), contentHashWithLength)
|
|
|
|
);
|
|
|
|
// query is optional, it's OK if it's in a path
|
|
|
|
// but there's nothing to replace it with
|
|
|
|
replacements.set(REGEXP_QUERY, replacer(file.query, true));
|
|
|
|
replacements.set(REGEXP_FILE, replacer(file.path));
|
|
|
|
replacements.set(REGEXP_FILEBASE, replacer(file.base));
|
2018-09-28 19:10:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Module Context
|
|
|
|
//
|
|
|
|
// Placeholders
|
|
|
|
//
|
|
|
|
// [id] - module.id (2.png)
|
|
|
|
// [ext] - file.ext (file.{png, svg, ...})
|
|
|
|
// [hash] - module.hash (6237543873.png)
|
|
|
|
// [name] - file.name (file)
|
|
|
|
// [file] - file.base (file.png)
|
|
|
|
//
|
|
|
|
// Legacy Placeholders
|
|
|
|
//
|
|
|
|
// [moduleid] - module.id (2.png)
|
|
|
|
// [modulehash] - module.hash (6237543873.png)
|
|
|
|
if (data.module) {
|
|
|
|
const module = data.module;
|
|
|
|
|
|
|
|
const id =
|
|
|
|
module instanceof Module ? chunkGraph.getModuleId(module) : module.id;
|
|
|
|
|
|
|
|
const hash =
|
|
|
|
module instanceof Module
|
|
|
|
? chunkGraph.getRenderedModuleHash(module)
|
|
|
|
: module.renderedHash || module.hash;
|
|
|
|
// @ts-ignore
|
|
|
|
const hashWithLength = module.hashWithLength || undefined;
|
|
|
|
|
2018-10-30 17:49:49 +08:00
|
|
|
replacements.set(REGEXP_ID, replacer(prepareId(id)));
|
|
|
|
replacements.set(REGEXP_EXT, replacer(file.ext));
|
|
|
|
replacements.set(REGEXP_NAME, replacer(file.name));
|
|
|
|
replacements.set(REGEXP_FILE, replacer(file.base));
|
|
|
|
replacements.set(REGEXP_HASH, hashLength(replacer(hash), hashWithLength));
|
|
|
|
// Legacy
|
|
|
|
replacements.set(REGEXP_MODULEID, replacer(prepareId(id)));
|
|
|
|
replacements.set(
|
|
|
|
REGEXP_MODULEHASH,
|
|
|
|
hashLength(replacer(hash), hashWithLength)
|
2018-09-28 19:10:37 +08:00
|
|
|
);
|
|
|
|
}
|
2018-10-30 17:49:49 +08:00
|
|
|
|
|
|
|
if (typeof path === "function") {
|
|
|
|
path = path(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const [regExp, replacer] of replacements) {
|
|
|
|
path = path.replace(regExp, replacer);
|
|
|
|
}
|
|
|
|
|
|
|
|
return path;
|
2017-01-11 17:51:58 +08:00
|
|
|
};
|
2014-08-22 19:51:24 +08:00
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
const plugin = "TemplatedPathPlugin";
|
|
|
|
|
2017-01-06 23:11:30 +08:00
|
|
|
class TemplatedPathPlugin {
|
|
|
|
apply(compiler) {
|
2018-09-28 19:10:37 +08:00
|
|
|
compiler.hooks.compilation.tap(plugin, compilation => {
|
2017-01-06 23:11:30 +08:00
|
|
|
const mainTemplate = compilation.mainTemplate;
|
2014-08-22 19:51:24 +08:00
|
|
|
|
2018-09-28 19:10:37 +08:00
|
|
|
mainTemplate.hooks.assetPath.tap(plugin, replacePathVariables);
|
2015-06-25 05:17:12 +08:00
|
|
|
});
|
2017-01-06 23:11:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = TemplatedPathPlugin;
|