webpack
Version:
Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
189 lines (174 loc) • 5.83 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Jason Anderson @diurnalist
*/
;
const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/gi,
REGEXP_MODULEHASH = /\[modulehash(?::(\d+))?\]/gi,
REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/gi,
REGEXP_NAME = /\[name\]/gi,
REGEXP_ID = /\[id\]/gi,
REGEXP_MODULEID = /\[moduleid\]/gi,
REGEXP_FILE = /\[file\]/gi,
REGEXP_QUERY = /\[query\]/gi,
REGEXP_FILEBASE = /\[filebase\]/gi,
REGEXP_URL = /\[url\]/gi;
// Using global RegExp for .test is dangerous
// We use a normal RegExp instead of .test
const REGEXP_HASH_FOR_TEST = new RegExp(REGEXP_HASH.source, "i"),
REGEXP_CHUNKHASH_FOR_TEST = new RegExp(REGEXP_CHUNKHASH.source, "i"),
REGEXP_CONTENTHASH_FOR_TEST = new RegExp(REGEXP_CONTENTHASH.source, "i"),
REGEXP_NAME_FOR_TEST = new RegExp(REGEXP_NAME.source, "i");
const withHashLength = (replacer, handlerFn, assetInfo) => {
const fn = (match, hashLength, ...args) => {
if (assetInfo) assetInfo.immutable = true;
const length = hashLength && parseInt(hashLength, 10);
if (length && handlerFn) {
return handlerFn(length);
}
const hash = replacer(match, hashLength, ...args);
return length ? hash.slice(0, length) : hash;
};
return fn;
};
const getReplacer = (value, allowEmpty) => {
const fn = (match, ...args) => {
// last argument in replacer is the entire input string
const input = args[args.length - 1];
if (value === null || value === undefined) {
if (!allowEmpty) {
throw new Error(
`Path variable ${match} not implemented in this context: ${input}`
);
}
return "";
} else {
return `${escapePathVariables(value)}`;
}
};
return fn;
};
const escapePathVariables = value => {
return typeof value === "string"
? value.replace(/\[(\\*[\w:]+\\*)\]/gi, "[\\$1\\]")
: value;
};
const replacePathVariables = (path, data, assetInfo) => {
const chunk = data.chunk;
const chunkId = chunk && chunk.id;
const chunkName = chunk && (chunk.name || chunk.id);
const chunkHash = chunk && (chunk.renderedHash || chunk.hash);
const chunkHashWithLength = chunk && chunk.hashWithLength;
const contentHashType = data.contentHashType;
const contentHash =
(chunk && chunk.contentHash && chunk.contentHash[contentHashType]) ||
data.contentHash;
const contentHashWithLength =
(chunk &&
chunk.contentHashWithLength &&
chunk.contentHashWithLength[contentHashType]) ||
data.contentHashWithLength;
const module = data.module;
const moduleId = module && module.id;
const moduleHash = module && (module.renderedHash || module.hash);
const moduleHashWithLength = module && module.hashWithLength;
if (typeof path === "function") {
path = path(data);
}
if (
data.noChunkHash &&
(REGEXP_CHUNKHASH_FOR_TEST.test(path) ||
REGEXP_CONTENTHASH_FOR_TEST.test(path))
) {
throw new Error(
`Cannot use [chunkhash] or [contenthash] for chunk in '${path}' (use [hash] instead)`
);
}
return (
path
.replace(
REGEXP_HASH,
withHashLength(getReplacer(data.hash), data.hashWithLength, assetInfo)
)
.replace(
REGEXP_CHUNKHASH,
withHashLength(getReplacer(chunkHash), chunkHashWithLength, assetInfo)
)
.replace(
REGEXP_CONTENTHASH,
withHashLength(
getReplacer(contentHash),
contentHashWithLength,
assetInfo
)
)
.replace(
REGEXP_MODULEHASH,
withHashLength(getReplacer(moduleHash), moduleHashWithLength, assetInfo)
)
.replace(REGEXP_ID, getReplacer(chunkId))
.replace(REGEXP_MODULEID, getReplacer(moduleId))
.replace(REGEXP_NAME, getReplacer(chunkName))
.replace(REGEXP_FILE, getReplacer(data.filename))
.replace(REGEXP_FILEBASE, getReplacer(data.basename))
// query is optional, it's OK if it's in a path but there's nothing to replace it with
.replace(REGEXP_QUERY, getReplacer(data.query, true))
// only available in sourceMappingURLComment
.replace(REGEXP_URL, getReplacer(data.url))
.replace(/\[\\(\\*[\w:]+\\*)\\\]/gi, "[$1]")
);
};
class TemplatedPathPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("TemplatedPathPlugin", compilation => {
const mainTemplate = compilation.mainTemplate;
mainTemplate.hooks.assetPath.tap(
"TemplatedPathPlugin",
replacePathVariables
);
mainTemplate.hooks.globalHash.tap(
"TemplatedPathPlugin",
(chunk, paths) => {
const outputOptions = mainTemplate.outputOptions;
const publicPath = outputOptions.publicPath || "";
const filename = outputOptions.filename || "";
const chunkFilename =
outputOptions.chunkFilename || outputOptions.filename;
if (
REGEXP_HASH_FOR_TEST.test(publicPath) ||
REGEXP_CHUNKHASH_FOR_TEST.test(publicPath) ||
REGEXP_CONTENTHASH_FOR_TEST.test(publicPath) ||
REGEXP_NAME_FOR_TEST.test(publicPath)
)
return true;
if (REGEXP_HASH_FOR_TEST.test(filename)) return true;
if (REGEXP_HASH_FOR_TEST.test(chunkFilename)) return true;
if (REGEXP_HASH_FOR_TEST.test(paths.join("|"))) return true;
}
);
mainTemplate.hooks.hashForChunk.tap(
"TemplatedPathPlugin",
(hash, chunk) => {
const outputOptions = mainTemplate.outputOptions;
const chunkFilename =
outputOptions.chunkFilename || outputOptions.filename;
if (REGEXP_CHUNKHASH_FOR_TEST.test(chunkFilename)) {
hash.update(JSON.stringify(chunk.getChunkMaps(true).hash));
}
if (REGEXP_CONTENTHASH_FOR_TEST.test(chunkFilename)) {
hash.update(
JSON.stringify(
chunk.getChunkMaps(true).contentHash.javascript || {}
)
);
}
if (REGEXP_NAME_FOR_TEST.test(chunkFilename)) {
hash.update(JSON.stringify(chunk.getChunkMaps(true).name));
}
}
);
});
}
}
module.exports = TemplatedPathPlugin;