Skip to content

Commit 41a0482

Browse files
authored
Merge pull request webpack#6839 from webpack/feature/contenthash
add [contenthash] support
2 parents 4861d2c + b018bc7 commit 41a0482

File tree

13 files changed

+309
-36
lines changed

13 files changed

+309
-36
lines changed

lib/Chunk.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class Chunk {
5555
this.files = [];
5656
this.rendered = false;
5757
this.hash = undefined;
58+
this.contentHash = Object.create(null);
5859
this.renderedHash = undefined;
5960
this.chunkReason = undefined;
6061
this.extraAsync = false;
@@ -340,15 +341,22 @@ class Chunk {
340341

341342
getChunkMaps(realHash) {
342343
const chunkHashMap = Object.create(null);
344+
const chunkContentHashMap = Object.create(null);
343345
const chunkNameMap = Object.create(null);
344346

345347
for (const chunk of this.getAllAsyncChunks()) {
346348
chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
349+
for (const key of Object.keys(chunk.contentHash)) {
350+
if (!chunkContentHashMap[key])
351+
chunkContentHashMap[key] = Object.create(null);
352+
chunkContentHashMap[key][chunk.id] = chunk.contentHash[key];
353+
}
347354
if (chunk.name) chunkNameMap[chunk.id] = chunk.name;
348355
}
349356

350357
return {
351358
hash: chunkHashMap,
359+
contentHash: chunkContentHashMap,
352360
name: chunkNameMap
353361
};
354362
}

lib/Compilation.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ class Compilation extends Tapable {
146146
recordChunks: new SyncHook(["chunks", "records"]),
147147

148148
beforeHash: new SyncHook([]),
149+
contentHash: new SyncHook(["chunk"]),
149150
afterHash: new SyncHook([]),
150151

151152
recordHash: new SyncHook(["records"]),
@@ -1680,15 +1681,15 @@ class Compilation extends Tapable {
16801681
const chunkHash = createHash(hashFunction);
16811682
if (outputOptions.hashSalt) chunkHash.update(outputOptions.hashSalt);
16821683
chunk.updateHash(chunkHash);
1683-
if (chunk.hasRuntime()) {
1684-
this.mainTemplate.updateHashForChunk(chunkHash, chunk);
1685-
} else {
1686-
this.chunkTemplate.updateHashForChunk(chunkHash, chunk);
1687-
}
1684+
const template = chunk.hasRuntime()
1685+
? this.mainTemplate
1686+
: this.chunkTemplate;
1687+
template.updateHashForChunk(chunkHash, chunk);
16881688
this.hooks.chunkHash.call(chunk, chunkHash);
16891689
chunk.hash = chunkHash.digest(hashDigest);
16901690
hash.update(chunk.hash);
16911691
chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
1692+
this.hooks.contentHash.call(chunk);
16921693
}
16931694
this.fullHash = hash.digest(hashDigest);
16941695
this.hash = this.fullHash.substr(0, hashDigestLength);

lib/JavascriptModulesPlugin.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Parser = require("./Parser");
88
const Template = require("./Template");
99
const { ConcatSource } = require("webpack-sources");
1010
const JavascriptGenerator = require("./JavascriptGenerator");
11+
const createHash = require("./util/createHash");
1112

1213
class JavascriptModulesPlugin {
1314
apply(compiler) {
@@ -72,6 +73,7 @@ class JavascriptModulesPlugin {
7273
filenameTemplate,
7374
pathOptions: {
7475
noChunkHash: !useChunkHash,
76+
contentHashType: "javascript",
7577
chunk
7678
},
7779
identifier: `chunk${chunk.id}`,
@@ -115,7 +117,8 @@ class JavascriptModulesPlugin {
115117
),
116118
filenameTemplate,
117119
pathOptions: {
118-
chunk
120+
chunk,
121+
contentHashType: "javascript"
119122
},
120123
identifier: `chunk${chunk.id}`,
121124
hash: chunk.hash
@@ -124,6 +127,29 @@ class JavascriptModulesPlugin {
124127
return result;
125128
}
126129
);
130+
compilation.hooks.contentHash.tap("JavascriptModulesPlugin", chunk => {
131+
const outputOptions = compilation.outputOptions;
132+
const {
133+
hashSalt,
134+
hashDigest,
135+
hashDigestLength,
136+
hashFunction
137+
} = outputOptions;
138+
const hash = createHash(hashFunction);
139+
if (hashSalt) hash.update(hashSalt);
140+
const template = chunk.hasRuntime()
141+
? compilation.mainTemplate
142+
: compilation.chunkTemplate;
143+
template.updateHashForChunk(hash, chunk);
144+
for (const m of chunk.modulesIterable) {
145+
if (typeof m.source === "function") {
146+
hash.update(m.hash);
147+
}
148+
}
149+
chunk.contentHash.javascript = hash
150+
.digest(hashDigest)
151+
.substr(0, hashDigestLength);
152+
});
127153
}
128154
);
129155
}

lib/SourceMapDevToolPlugin.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,16 +249,11 @@ class SourceMapDevToolPlugin {
249249
? path.relative(options.fileContext, filename)
250250
: filename,
251251
query,
252-
basename: basename(filename)
252+
basename: basename(filename),
253+
contentHash: createHash("md4")
254+
.update(sourceMapString)
255+
.digest("hex")
253256
});
254-
if (sourceMapFile.includes("[contenthash]")) {
255-
sourceMapFile = sourceMapFile.replace(
256-
/\[contenthash\]/g,
257-
createHash("md4")
258-
.update(sourceMapString)
259-
.digest("hex")
260-
);
261-
}
262257
const sourceMapUrl = options.publicPath
263258
? options.publicPath + sourceMapFile.replace(/\\/g, "/")
264259
: path

lib/TemplatedPathPlugin.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
88
REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/gi,
99
REGEXP_MODULEHASH = /\[modulehash(?::(\d+))?\]/gi,
10+
REGEXP_CONTENTHASH = /\[contenthash(?::(\d+))?\]/gi,
1011
REGEXP_NAME = /\[name\]/gi,
1112
REGEXP_ID = /\[id\]/gi,
1213
REGEXP_MODULEID = /\[moduleid\]/gi,
@@ -18,6 +19,7 @@ const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
1819
// We use a normal RegExp instead of .test
1920
const REGEXP_HASH_FOR_TEST = new RegExp(REGEXP_HASH.source, "i"),
2021
REGEXP_CHUNKHASH_FOR_TEST = new RegExp(REGEXP_CHUNKHASH.source, "i"),
22+
REGEXP_CONTENTHASH_FOR_TEST = new RegExp(REGEXP_CONTENTHASH.source, "i"),
2123
REGEXP_NAME_FOR_TEST = new RegExp(REGEXP_NAME.source, "i");
2224

2325
const withHashLength = (replacer, handlerFn) => {
@@ -55,6 +57,15 @@ const replacePathVariables = (path, data) => {
5557
const chunkName = chunk && (chunk.name || chunk.id);
5658
const chunkHash = chunk && (chunk.renderedHash || chunk.hash);
5759
const chunkHashWithLength = chunk && chunk.hashWithLength;
60+
const contentHashType = data.contentHashType;
61+
const contentHash =
62+
(chunk && chunk.contentHash && chunk.contentHash[contentHashType]) ||
63+
data.contentHash;
64+
const contentHashWithLength =
65+
(chunk &&
66+
chunk.contentHashWithLength &&
67+
chunk.contentHashWithLength[contentHashType]) ||
68+
data.contentHashWithLength;
5869
const module = data.module;
5970
const moduleId = module && module.id;
6071
const moduleHash = module && (module.renderedHash || module.hash);
@@ -64,9 +75,15 @@ const replacePathVariables = (path, data) => {
6475
path = path(data);
6576
}
6677

67-
if (data.noChunkHash && REGEXP_CHUNKHASH_FOR_TEST.test(path)) {
78+
if (
79+
data.noChunkHash &&
80+
(REGEXP_CHUNKHASH_FOR_TEST.test(path) ||
81+
REGEXP_CONTENTHASH_FOR_TEST.test(path))
82+
) {
6883
throw new Error(
69-
`Cannot use [chunkhash] for chunk in '${path}' (use [hash] instead)`
84+
`Cannot use [chunkhash] or [contenthash] for chunk in '${
85+
path
86+
}' (use [hash] instead)`
7087
);
7188
}
7289

@@ -80,6 +97,10 @@ const replacePathVariables = (path, data) => {
8097
REGEXP_CHUNKHASH,
8198
withHashLength(getReplacer(chunkHash), chunkHashWithLength)
8299
)
100+
.replace(
101+
REGEXP_CONTENTHASH,
102+
withHashLength(getReplacer(contentHash), contentHashWithLength)
103+
)
83104
.replace(
84105
REGEXP_MODULEHASH,
85106
withHashLength(getReplacer(moduleHash), moduleHashWithLength)
@@ -115,6 +136,7 @@ class TemplatedPathPlugin {
115136
if (
116137
REGEXP_HASH_FOR_TEST.test(publicPath) ||
117138
REGEXP_CHUNKHASH_FOR_TEST.test(publicPath) ||
139+
REGEXP_CONTENTHASH_FOR_TEST.test(publicPath) ||
118140
REGEXP_NAME_FOR_TEST.test(publicPath)
119141
)
120142
return true;
@@ -132,6 +154,13 @@ class TemplatedPathPlugin {
132154
outputOptions.chunkFilename || outputOptions.filename;
133155
if (REGEXP_CHUNKHASH_FOR_TEST.test(chunkFilename))
134156
hash.update(JSON.stringify(chunk.getChunkMaps(true).hash));
157+
if (REGEXP_CONTENTHASH_FOR_TEST.test(chunkFilename)) {
158+
hash.update(
159+
JSON.stringify(
160+
chunk.getChunkMaps(true).contentHash.javascript || {}
161+
)
162+
);
163+
}
135164
if (REGEXP_NAME_FOR_TEST.test(chunkFilename))
136165
hash.update(JSON.stringify(chunk.getChunkMaps(true).name));
137166
}

lib/node/NodeMainTemplatePlugin.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,33 @@ module.exports = class NodeMainTemplatePlugin {
125125
shortChunkHashMap
126126
)}[chunkId] + "`;
127127
},
128+
contentHash: {
129+
javascript: `" + ${JSON.stringify(
130+
chunkMaps.contentHash.javascript
131+
)}[chunkId] + "`
132+
},
133+
contentHashWithLength: {
134+
javascript: length => {
135+
const shortContentHashMap = {};
136+
const contentHash =
137+
chunkMaps.contentHash.javascript;
138+
for (const chunkId of Object.keys(contentHash)) {
139+
if (typeof contentHash[chunkId] === "string") {
140+
shortContentHashMap[chunkId] = contentHash[
141+
chunkId
142+
].substr(0, length);
143+
}
144+
}
145+
return `" + ${JSON.stringify(
146+
shortContentHashMap
147+
)}[chunkId] + "`;
148+
}
149+
},
128150
name: `" + (${JSON.stringify(
129151
chunkMaps.name
130152
)}[chunkId]||chunkId) + "`
131-
}
153+
},
154+
contentHashType: "javascript"
132155
}
133156
) +
134157
";",
@@ -187,10 +210,32 @@ module.exports = class NodeMainTemplatePlugin {
187210
shortChunkHashMap
188211
)}[chunkId] + "`;
189212
},
213+
contentHash: {
214+
javascript: `" + ${JSON.stringify(
215+
chunkMaps.contentHash.javascript
216+
)}[chunkId] + "`
217+
},
218+
contentHashWithLength: {
219+
javascript: length => {
220+
const shortContentHashMap = {};
221+
const contentHash = chunkMaps.contentHash.javascript;
222+
for (const chunkId of Object.keys(contentHash)) {
223+
if (typeof contentHash[chunkId] === "string") {
224+
shortContentHashMap[chunkId] = contentHash[
225+
chunkId
226+
].substr(0, length);
227+
}
228+
}
229+
return `" + ${JSON.stringify(
230+
shortContentHashMap
231+
)}[chunkId] + "`;
232+
}
233+
},
190234
name: `" + (${JSON.stringify(
191235
chunkMaps.name
192236
)}[chunkId]||chunkId) + "`
193-
}
237+
},
238+
contentHashType: "javascript"
194239
}
195240
);
196241
return Template.asString([

lib/web/JsonpMainTemplatePlugin.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,30 @@ class JsonpMainTemplatePlugin {
8888
},
8989
name: `" + (${JSON.stringify(
9090
chunkMaps.name
91-
)}[chunkId]||chunkId) + "`
92-
}
91+
)}[chunkId]||chunkId) + "`,
92+
contentHash: {
93+
javascript: `" + ${JSON.stringify(
94+
chunkMaps.contentHash.javascript
95+
)}[chunkId] + "`
96+
},
97+
contentHashWithLength: {
98+
javascript: length => {
99+
const shortContentHashMap = {};
100+
const contentHash = chunkMaps.contentHash.javascript;
101+
for (const chunkId of Object.keys(contentHash)) {
102+
if (typeof contentHash[chunkId] === "string") {
103+
shortContentHashMap[chunkId] = contentHash[
104+
chunkId
105+
].substr(0, length);
106+
}
107+
}
108+
return `" + ${JSON.stringify(
109+
shortContentHashMap
110+
)}[chunkId] + "`;
111+
}
112+
}
113+
},
114+
contentHashType: "javascript"
93115
}
94116
);
95117
return Template.asString([

lib/webpack.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ exportPlugins((exports.node = {}), {
153153
exportPlugins((exports.debug = {}), {
154154
ProfilingPlugin: () => require("./debug/ProfilingPlugin")
155155
});
156+
exportPlugins((exports.util = {}), {
157+
createHash: () => require("./util/createHash")
158+
});
156159

157160
const defineMissingPluginError = (namespace, pluginName, errorMessage) => {
158161
Object.defineProperty(namespace, pluginName, {

lib/webworker/WebWorkerMainTemplatePlugin.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class WebWorkerMainTemplatePlugin {
3535
"WebWorkerMainTemplatePlugin",
3636
(_, chunk, hash) => {
3737
const chunkFilename = mainTemplate.outputOptions.chunkFilename;
38+
const chunkMaps = chunk.getChunkMaps();
3839
return Template.asString([
3940
"promises.push(Promise.resolve().then(function() {",
4041
Template.indent([
@@ -50,8 +51,30 @@ class WebWorkerMainTemplatePlugin {
5051
length
5152
)} + "`,
5253
chunk: {
53-
id: '" + chunkId + "'
54-
}
54+
id: '" + chunkId + "',
55+
contentHash: {
56+
javascript: `" + ${JSON.stringify(
57+
chunkMaps.contentHash.javascript
58+
)}[chunkId] + "`
59+
},
60+
contentHashWithLength: {
61+
javascript: length => {
62+
const shortContentHashMap = {};
63+
const contentHash = chunkMaps.contentHash.javascript;
64+
for (const chunkId of Object.keys(contentHash)) {
65+
if (typeof contentHash[chunkId] === "string") {
66+
shortContentHashMap[chunkId] = contentHash[
67+
chunkId
68+
].substr(0, length);
69+
}
70+
}
71+
return `" + ${JSON.stringify(
72+
shortContentHashMap
73+
)}[chunkId] + "`;
74+
}
75+
}
76+
},
77+
contentHashType: "javascript"
5578
}) +
5679
");"
5780
]),

test/ConfigTestCases.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ describe("ConfigTestCases", () => {
226226
);
227227
if (exportedTests < filesCount)
228228
return done(new Error("No tests exported by test case"));
229+
if (testConfig.afterExecute) testConfig.afterExecute();
229230
process.nextTick(done);
230231
});
231232
});

0 commit comments

Comments
 (0)