add [contenthash] support

This commit is contained in:
Tobias Koppers 2018-03-22 19:52:11 +01:00
parent 7f11210cff
commit 296542ed79
10 changed files with 194 additions and 22 deletions

View File

@ -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
};
}

View File

@ -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);

View File

@ -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);
});
}
);
}

View File

@ -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

View File

@ -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));
}

View File

@ -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([

View File

@ -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([

View File

@ -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, {

View File

@ -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"
}) +
");"
]),

View File

@ -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
}
}
];