webpack/lib/TemplatedPathPlugin.js

276 lines
6.6 KiB
JavaScript
Raw Normal View History

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
"use strict";
2014-08-22 19:51:24 +08:00
const { basename, extname } = require("path");
const util = require("util");
const Module = require("./Module");
/** @typedef {import("./Compilation").PathData} PathData */
// Compilation
const REGEXP_FULLHASH = /\[fullhash(?::(\d+))?\]/gi;
const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi;
// Chunks and Modules
const REGEXP_ID = /\[id\]/gi;
// Chunks and Filenames
const REGEXP_NAME = /\[name\]/gi;
// Filename
const REGEXP_FILE = /\[file\]/gi;
const REGEXP_QUERY = /\[query\]/gi;
const REGEXP_PATH = /\[path\]/gi;
const REGEXP_BASE = /\[base\]/gi;
const REGEXP_FILEBASE = /\[filebase\]/gi;
const REGEXP_EXT = /\[ext\]/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
const prepareId = id => {
if (typeof id !== "string") return id;
if (/^"\s\+*.*\+\s*"$/.test(id)) {
const match = /^"\s\+*\s*(.*)\s*\+\s*"$/.exec(id);
return `" + (${
match[1]
} + "").replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_") + "`;
}
return id.replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_");
};
const hashLength = (replacer, handler) => {
const fn = (match, hashLength, ...args) => {
const length = hashLength && parseInt(hashLength, 10);
if (length && handler) {
return handler(length);
}
2017-11-08 18:32:05 +08:00
const hash = replacer(match, hashLength, ...args);
return length ? hash.slice(0, length) : hash;
2014-08-22 19:51:24 +08:00
};
return fn;
2017-01-11 17:51:58 +08:00
};
2014-08-22 19:51:24 +08:00
const replacer = (value, allowEmpty) => {
const fn = (match, ...args) => {
2018-02-25 09:00:20 +08:00
if (value === null || value === undefined) {
if (!allowEmpty) {
// last argument in replacer is the entire input string
const input = args[args.length - 1];
2018-02-25 09:00:20 +08:00
throw new Error(
`Path variable ${match} not implemented in this context: ${input}`
);
}
2014-08-22 19:51:24 +08:00
return "";
} else {
return `${value}`;
2014-08-22 19:51:24 +08:00
}
};
return fn;
2017-01-11 17:51:58 +08:00
};
2014-08-22 19:51:24 +08:00
const deprecationCache = new Map();
const deprecated = (fn, message) => {
let d = deprecationCache.get(message);
if (d === undefined) {
d = util.deprecate(() => {}, message);
deprecationCache.set(message, d);
}
return (...args) => {
d();
return fn(...args);
};
};
/**
* @param {string | function(PathData): string} path the raw path
* @param {PathData} data context data
* @returns {string} the interpolated path
*/
const replacePathVariables = (path, data) => {
const chunkGraph = data.chunkGraph;
const replacements = new Map();
// Filename context
//
// Placeholders
//
// for /some/path/file.js?query:
// [file] - /some/path/file.js
// [query] - ?query
// [base] - file.js
// [path] - /some/path/
// [name] - file
// [ext] - .js
if (data.filename) {
if (typeof data.filename === "string") {
const idx = data.filename.indexOf("?");
let file, query;
if (idx >= 0) {
file = data.filename.substr(0, idx);
query = data.filename.substr(idx);
} else {
file = data.filename;
query = "";
}
const ext = extname(file);
const base = basename(file);
const name = base.slice(0, base.length - ext.length);
const path = file.slice(0, file.length - base.length);
replacements.set(REGEXP_FILE, replacer(file));
replacements.set(REGEXP_QUERY, replacer(query, true));
replacements.set(REGEXP_PATH, replacer(path));
replacements.set(REGEXP_BASE, replacer(base));
replacements.set(REGEXP_NAME, replacer(name));
replacements.set(REGEXP_EXT, replacer(ext, true));
// Legacy
replacements.set(
REGEXP_FILEBASE,
deprecated(replacer(base), "[filebase] is now [base]")
);
}
}
// Compilation context
//
// Placeholders
//
// [hash] - data.hash (3a4b5c6e7f)
if (data.hash) {
const hashReplacer = hashLength(replacer(data.hash), data.hashWithLength);
replacements.set(REGEXP_FULLHASH, hashReplacer);
// Legacy
replacements.set(
REGEXP_HASH,
deprecated(
hashReplacer,
"[hash] is now [fullhash] (also consider using [chunkhash] or [contenthash], see documentation for details)"
)
);
}
// Chunk Context
//
// Placeholders
//
// [id] - chunk.id (0.js)
// [name] - chunk.name (app.js)
// [hash] - data.hash (53276435.js)
// [chunkhash] - chunk.hash (7823t4t4.js)
// [contenthash] - chunk.contentHash[type] (3256urzg.js)
if (data.chunk) {
const chunk = data.chunk;
const contentHashType = data.contentHashType;
const idReplacer = replacer(chunk.id);
const nameReplacer = replacer(chunk.name || chunk.id);
const chunkhashReplacer = hashLength(
replacer(chunk.renderedHash || chunk.hash),
"hashWithLength" in chunk ? chunk.hashWithLength : undefined
);
const contenthashReplacer = hashLength(
replacer(
data.contentHash ||
(contentHashType &&
chunk.contentHash &&
chunk.contentHash[contentHashType])
),
data.contentHashWithLength ||
("contentHashWithLength" in chunk && chunk.contentHashWithLength
? chunk.contentHashWithLength[contentHashType]
: undefined)
);
replacements.set(REGEXP_ID, idReplacer);
replacements.set(REGEXP_NAME, nameReplacer);
replacements.set(REGEXP_CHUNKHASH, chunkhashReplacer);
replacements.set(REGEXP_CONTENTHASH, contenthashReplacer);
}
// Module Context
//
// Placeholders
//
// [id] - module.id (2.png)
// [hash] - module.hash (6237543873.png)
//
// Legacy Placeholders
//
// [moduleid] - module.id (2.png)
// [modulehash] - module.hash (6237543873.png)
if (data.module) {
const module = data.module;
const idReplacer = replacer(
prepareId(
module instanceof Module ? chunkGraph.getModuleId(module) : module.id
)
);
const hashReplacer = hashLength(
replacer(
module instanceof Module
? chunkGraph.getRenderedModuleHash(module)
: module.renderedHash || module.hash
),
"hashWithLength" in module ? module.hashWithLength : undefined
);
replacements.set(REGEXP_ID, idReplacer);
replacements.set(REGEXP_HASH, hashReplacer);
// Legacy
replacements.set(
REGEXP_MODULEID,
deprecated(idReplacer, "[moduleid] is now [id]")
);
replacements.set(
REGEXP_MODULEHASH,
deprecated(hashReplacer, "[modulehash] is now [hash]")
);
}
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
const plugin = "TemplatedPathPlugin";
class TemplatedPathPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(plugin, compilation => {
const mainTemplate = compilation.mainTemplate;
2014-08-22 19:51:24 +08:00
mainTemplate.hooks.assetPath.tap(plugin, replacePathVariables);
});
}
}
module.exports = TemplatedPathPlugin;