mirror of https://github.com/webpack/webpack.git
add [contenthash] support
This commit is contained in:
parent
7f11210cff
commit
296542ed79
|
|
@ -55,6 +55,7 @@ class Chunk {
|
|||
this.files = [];
|
||||
this.rendered = false;
|
||||
this.hash = undefined;
|
||||
this.contentHash = Object.create(null);
|
||||
this.renderedHash = undefined;
|
||||
this.chunkReason = undefined;
|
||||
this.extraAsync = false;
|
||||
|
|
@ -340,15 +341,22 @@ class Chunk {
|
|||
|
||||
getChunkMaps(realHash) {
|
||||
const chunkHashMap = Object.create(null);
|
||||
const chunkContentHashMap = Object.create(null);
|
||||
const chunkNameMap = Object.create(null);
|
||||
|
||||
for (const chunk of this.getAllAsyncChunks()) {
|
||||
chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
|
||||
for (const key of Object.keys(chunk.contentHash)) {
|
||||
if (!chunkContentHashMap[key])
|
||||
chunkContentHashMap[key] = Object.create(null);
|
||||
chunkContentHashMap[key][chunk.id] = chunk.contentHash[key];
|
||||
}
|
||||
if (chunk.name) chunkNameMap[chunk.id] = chunk.name;
|
||||
}
|
||||
|
||||
return {
|
||||
hash: chunkHashMap,
|
||||
contentHash: chunkContentHashMap,
|
||||
name: chunkNameMap
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ class Compilation extends Tapable {
|
|||
recordChunks: new SyncHook(["chunks", "records"]),
|
||||
|
||||
beforeHash: new SyncHook([]),
|
||||
contentHash: new SyncHook(["chunk"]),
|
||||
afterHash: new SyncHook([]),
|
||||
|
||||
recordHash: new SyncHook(["records"]),
|
||||
|
|
@ -1680,15 +1681,15 @@ class Compilation extends Tapable {
|
|||
const chunkHash = createHash(hashFunction);
|
||||
if (outputOptions.hashSalt) chunkHash.update(outputOptions.hashSalt);
|
||||
chunk.updateHash(chunkHash);
|
||||
if (chunk.hasRuntime()) {
|
||||
this.mainTemplate.updateHashForChunk(chunkHash, chunk);
|
||||
} else {
|
||||
this.chunkTemplate.updateHashForChunk(chunkHash, chunk);
|
||||
}
|
||||
const template = chunk.hasRuntime()
|
||||
? this.mainTemplate
|
||||
: this.chunkTemplate;
|
||||
template.updateHashForChunk(chunkHash, chunk);
|
||||
this.hooks.chunkHash.call(chunk, chunkHash);
|
||||
chunk.hash = chunkHash.digest(hashDigest);
|
||||
hash.update(chunk.hash);
|
||||
chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
|
||||
this.hooks.contentHash.call(chunk);
|
||||
}
|
||||
this.fullHash = hash.digest(hashDigest);
|
||||
this.hash = this.fullHash.substr(0, hashDigestLength);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const Parser = require("./Parser");
|
|||
const Template = require("./Template");
|
||||
const { ConcatSource } = require("webpack-sources");
|
||||
const JavascriptGenerator = require("./JavascriptGenerator");
|
||||
const createHash = require("./util/createHash");
|
||||
|
||||
class JavascriptModulesPlugin {
|
||||
apply(compiler) {
|
||||
|
|
@ -72,6 +73,7 @@ class JavascriptModulesPlugin {
|
|||
filenameTemplate,
|
||||
pathOptions: {
|
||||
noChunkHash: !useChunkHash,
|
||||
contentHashType: "javascript",
|
||||
chunk
|
||||
},
|
||||
identifier: `chunk${chunk.id}`,
|
||||
|
|
@ -115,7 +117,8 @@ class JavascriptModulesPlugin {
|
|||
),
|
||||
filenameTemplate,
|
||||
pathOptions: {
|
||||
chunk
|
||||
chunk,
|
||||
contentHashType: "javascript"
|
||||
},
|
||||
identifier: `chunk${chunk.id}`,
|
||||
hash: chunk.hash
|
||||
|
|
@ -124,6 +127,27 @@ class JavascriptModulesPlugin {
|
|||
return result;
|
||||
}
|
||||
);
|
||||
compilation.hooks.contentHash.tap("JavascriptModulesPlugin", chunk => {
|
||||
const outputOptions = compilation.outputOptions;
|
||||
const hashFunction = outputOptions.hashFunction;
|
||||
const hashSalt = outputOptions.hashSalt;
|
||||
const hashDigest = outputOptions.hashDigest;
|
||||
const hashDigestLength = outputOptions.hashDigestLength;
|
||||
const hash = createHash(hashFunction);
|
||||
if (hashSalt) hash.update(hashSalt);
|
||||
const template = chunk.hasRuntime()
|
||||
? compilation.mainTemplate
|
||||
: compilation.chunkTemplate;
|
||||
template.updateHashForChunk(hash, chunk);
|
||||
for (const m of chunk.modulesIterable) {
|
||||
if (typeof m.source === "function") {
|
||||
hash.update(m.hash);
|
||||
}
|
||||
}
|
||||
chunk.contentHash.javascript = hash
|
||||
.digest(hashDigest)
|
||||
.substr(0, hashDigestLength);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -249,16 +249,11 @@ class SourceMapDevToolPlugin {
|
|||
? path.relative(options.fileContext, filename)
|
||||
: filename,
|
||||
query,
|
||||
basename: basename(filename)
|
||||
basename: basename(filename),
|
||||
contentHash: createHash("md4")
|
||||
.update(sourceMapString)
|
||||
.digest("hex")
|
||||
});
|
||||
if (sourceMapFile.includes("[contenthash]")) {
|
||||
sourceMapFile = sourceMapFile.replace(
|
||||
/\[contenthash\]/g,
|
||||
createHash("md4")
|
||||
.update(sourceMapString)
|
||||
.digest("hex")
|
||||
);
|
||||
}
|
||||
const sourceMapUrl = options.publicPath
|
||||
? options.publicPath + sourceMapFile.replace(/\\/g, "/")
|
||||
: path
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
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,
|
||||
|
|
@ -18,6 +19,7 @@ const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
|
|||
// 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) => {
|
||||
|
|
@ -55,6 +57,15 @@ const replacePathVariables = (path, data) => {
|
|||
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);
|
||||
|
|
@ -64,9 +75,15 @@ const replacePathVariables = (path, data) => {
|
|||
path = path(data);
|
||||
}
|
||||
|
||||
if (data.noChunkHash && REGEXP_CHUNKHASH_FOR_TEST.test(path)) {
|
||||
if (
|
||||
data.noChunkHash &&
|
||||
(REGEXP_CHUNKHASH_FOR_TEST.test(path) ||
|
||||
REGEXP_CONTENTHASH_FOR_TEST.test(path))
|
||||
) {
|
||||
throw new Error(
|
||||
`Cannot use [chunkhash] for chunk in '${path}' (use [hash] instead)`
|
||||
`Cannot use [chunkhash] or [contenthash] for chunk in '${
|
||||
path
|
||||
}' (use [hash] instead)`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +97,10 @@ const replacePathVariables = (path, data) => {
|
|||
REGEXP_CHUNKHASH,
|
||||
withHashLength(getReplacer(chunkHash), chunkHashWithLength)
|
||||
)
|
||||
.replace(
|
||||
REGEXP_CONTENTHASH,
|
||||
withHashLength(getReplacer(contentHash), contentHashWithLength)
|
||||
)
|
||||
.replace(
|
||||
REGEXP_MODULEHASH,
|
||||
withHashLength(getReplacer(moduleHash), moduleHashWithLength)
|
||||
|
|
@ -115,6 +136,7 @@ class TemplatedPathPlugin {
|
|||
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;
|
||||
|
|
@ -132,6 +154,11 @@ class TemplatedPathPlugin {
|
|||
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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,10 +125,33 @@ module.exports = class NodeMainTemplatePlugin {
|
|||
shortChunkHashMap
|
||||
)}[chunkId] + "`;
|
||||
},
|
||||
contentHash: {
|
||||
javascript: `" + ${JSON.stringify(
|
||||
chunkMaps.contentHash.javascript
|
||||
)}[chunkId] + "`
|
||||
},
|
||||
contentHashWithLength: {
|
||||
javascript: length => {
|
||||
const shortContentHashMap = {};
|
||||
const contentHash =
|
||||
chunkMaps.contentHash.javascript;
|
||||
for (const chunkId of Object.keys(contentHash)) {
|
||||
if (typeof contentHash[chunkId] === "string") {
|
||||
shortContentHashMap[chunkId] = contentHash[
|
||||
chunkId
|
||||
].substr(0, length);
|
||||
}
|
||||
}
|
||||
return `" + ${JSON.stringify(
|
||||
shortContentHashMap
|
||||
)}[chunkId] + "`;
|
||||
}
|
||||
},
|
||||
name: `" + (${JSON.stringify(
|
||||
chunkMaps.name
|
||||
)}[chunkId]||chunkId) + "`
|
||||
}
|
||||
},
|
||||
contentHashType: "javascript"
|
||||
}
|
||||
) +
|
||||
";",
|
||||
|
|
@ -187,10 +210,32 @@ module.exports = class NodeMainTemplatePlugin {
|
|||
shortChunkHashMap
|
||||
)}[chunkId] + "`;
|
||||
},
|
||||
contentHash: {
|
||||
javascript: `" + ${JSON.stringify(
|
||||
chunkMaps.contentHash.javascript
|
||||
)}[chunkId] + "`
|
||||
},
|
||||
contentHashWithLength: {
|
||||
javascript: length => {
|
||||
const shortContentHashMap = {};
|
||||
const contentHash = chunkMaps.contentHash.javascript;
|
||||
for (const chunkId of Object.keys(contentHash)) {
|
||||
if (typeof contentHash[chunkId] === "string") {
|
||||
shortContentHashMap[chunkId] = contentHash[
|
||||
chunkId
|
||||
].substr(0, length);
|
||||
}
|
||||
}
|
||||
return `" + ${JSON.stringify(
|
||||
shortContentHashMap
|
||||
)}[chunkId] + "`;
|
||||
}
|
||||
},
|
||||
name: `" + (${JSON.stringify(
|
||||
chunkMaps.name
|
||||
)}[chunkId]||chunkId) + "`
|
||||
}
|
||||
},
|
||||
contentHashType: "javascript"
|
||||
}
|
||||
);
|
||||
return Template.asString([
|
||||
|
|
|
|||
|
|
@ -88,8 +88,30 @@ class JsonpMainTemplatePlugin {
|
|||
},
|
||||
name: `" + (${JSON.stringify(
|
||||
chunkMaps.name
|
||||
)}[chunkId]||chunkId) + "`
|
||||
}
|
||||
)}[chunkId]||chunkId) + "`,
|
||||
contentHash: {
|
||||
javascript: `" + ${JSON.stringify(
|
||||
chunkMaps.contentHash.javascript
|
||||
)}[chunkId] + "`
|
||||
},
|
||||
contentHashWithLength: {
|
||||
javascript: length => {
|
||||
const shortContentHashMap = {};
|
||||
const contentHash = chunkMaps.contentHash.javascript;
|
||||
for (const chunkId of Object.keys(contentHash)) {
|
||||
if (typeof contentHash[chunkId] === "string") {
|
||||
shortContentHashMap[chunkId] = contentHash[
|
||||
chunkId
|
||||
].substr(0, length);
|
||||
}
|
||||
}
|
||||
return `" + ${JSON.stringify(
|
||||
shortContentHashMap
|
||||
)}[chunkId] + "`;
|
||||
}
|
||||
}
|
||||
},
|
||||
contentHashType: "javascript"
|
||||
}
|
||||
);
|
||||
return Template.asString([
|
||||
|
|
|
|||
|
|
@ -153,6 +153,9 @@ exportPlugins((exports.node = {}), {
|
|||
exportPlugins((exports.debug = {}), {
|
||||
ProfilingPlugin: () => require("./debug/ProfilingPlugin")
|
||||
});
|
||||
exportPlugins((exports.util = {}), {
|
||||
createHash: () => require("./util/createHash")
|
||||
});
|
||||
|
||||
const defineMissingPluginError = (namespace, pluginName, errorMessage) => {
|
||||
Reflect.defineProperty(namespace, pluginName, {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class WebWorkerMainTemplatePlugin {
|
|||
"WebWorkerMainTemplatePlugin",
|
||||
(_, chunk, hash) => {
|
||||
const chunkFilename = mainTemplate.outputOptions.chunkFilename;
|
||||
const chunkMaps = chunk.getChunkMaps();
|
||||
return Template.asString([
|
||||
"promises.push(Promise.resolve().then(function() {",
|
||||
Template.indent([
|
||||
|
|
@ -51,7 +52,29 @@ class WebWorkerMainTemplatePlugin {
|
|||
)} + "`,
|
||||
chunk: {
|
||||
id: '" + chunkId + "'
|
||||
}
|
||||
},
|
||||
contentHash: {
|
||||
javascript: `" + ${JSON.stringify(
|
||||
chunkMaps.contentHash.javascript
|
||||
)}[chunkId] + "`
|
||||
},
|
||||
contentHashWithLength: {
|
||||
javascript: length => {
|
||||
const shortContentHashMap = {};
|
||||
const contentHash = chunkMaps.contentHash.javascript;
|
||||
for (const chunkId of Object.keys(contentHash)) {
|
||||
if (typeof contentHash[chunkId] === "string") {
|
||||
shortContentHashMap[chunkId] = contentHash[
|
||||
chunkId
|
||||
].substr(0, length);
|
||||
}
|
||||
}
|
||||
return `" + ${JSON.stringify(
|
||||
shortContentHashMap
|
||||
)}[chunkId] + "`;
|
||||
}
|
||||
},
|
||||
contentHashType: "javascript"
|
||||
}) +
|
||||
");"
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -115,6 +115,30 @@ module.exports = [
|
|||
expectedFilenameLength: 31,
|
||||
expectedChunkFilenameLength: 20
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "contenthash in node",
|
||||
output: {
|
||||
filename: "bundle10.[contenthash].js",
|
||||
chunkFilename: "[id].bundle10.[contenthash].js"
|
||||
},
|
||||
target: "node",
|
||||
amd: {
|
||||
expectedFilenameLength: 32,
|
||||
expectedChunkFilenameLength: 34
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "contenthash in node with length",
|
||||
output: {
|
||||
filename: "bundle11.[contenthash:7].js",
|
||||
chunkFilename: "[id].bundle11.[contenthash:7].js"
|
||||
},
|
||||
target: "node",
|
||||
amd: {
|
||||
expectedFilenameLength: 9 + 7 + 3,
|
||||
expectedChunkFilenameLength: 2 + 9 + 7 + 3
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue