Skip to content

Commit 0993ca9

Browse files
authored
Merge pull request #59 from waysact/multiple-common-chunks
Support multiple common chunks
2 parents 10e85fd + d0bb00a commit 0993ca9

File tree

3 files changed

+181
-99
lines changed

3 files changed

+181
-99
lines changed

.codeclimate.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ engines:
33
enabled: true
44
ratings:
55
paths: ["**.js"]
6+
exclude_paths:
7+
- .git/
8+
- node_modules/
9+
- coverage/
10+
- test/
11+
- karma.conf.js

index.js

Lines changed: 122 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,37 @@ function WebIntegrityJsonpMainTemplatePlugin(sriPlugin, compilation) {
1414
this.compilation = compilation;
1515
}
1616

17-
WebIntegrityJsonpMainTemplatePlugin.prototype.apply = function apply(mainTemplate) {
18-
var self = this;
17+
function addSriHashes(plugin, chunk, source) {
1918
var allDepChunkIds = {};
20-
21-
function findDepChunks(chunk) {
22-
chunk.chunks.forEach(function forEachChunk(depChunk) {
19+
function findDepChunks(childChunk) {
20+
childChunk.chunks.forEach(function forEachChunk(depChunk) {
2321
if (!allDepChunkIds[depChunk.id]) {
2422
allDepChunkIds[depChunk.id] = true;
2523
findDepChunks(depChunk);
2624
}
2725
});
2826
}
2927

28+
if (chunk.chunks.length > 0) {
29+
findDepChunks(chunk);
30+
31+
return plugin.asString([
32+
source,
33+
'var sriHashes = ' + JSON.stringify(
34+
Object.keys(allDepChunkIds).reduce(function chunkIdReducer(sriHashes, chunkId) {
35+
sriHashes[chunkId] = makePlaceholder(chunkId); // eslint-disable-line no-param-reassign
36+
return sriHashes;
37+
}, {})
38+
) + ';'
39+
]);
40+
}
41+
42+
return source;
43+
}
44+
45+
WebIntegrityJsonpMainTemplatePlugin.prototype.apply = function apply(mainTemplate) {
46+
var self = this;
47+
3048
/*
3149
* Patch jsonp-script code to add the integrity attribute.
3250
*/
@@ -50,20 +68,7 @@ WebIntegrityJsonpMainTemplatePlugin.prototype.apply = function apply(mainTemplat
5068
* later.
5169
*/
5270
mainTemplate.plugin('local-vars', function localVarsPlugin(source, chunk) {
53-
if (chunk.chunks.length > 0) {
54-
findDepChunks(chunk);
55-
56-
return this.asString([
57-
source,
58-
'var sriHashes = ' + JSON.stringify(
59-
Object.keys(allDepChunkIds).reduce(function chunkIdReducer(sriHashes, chunkId) {
60-
sriHashes[chunkId] = makePlaceholder(chunkId); // eslint-disable-line no-param-reassign
61-
return sriHashes;
62-
}, {})
63-
) + ';'
64-
]);
65-
}
66-
return source;
71+
return addSriHashes(this, chunk, source);
6772
});
6873
};
6974

@@ -167,15 +172,105 @@ SubresourceIntegrityPlugin.prototype.hwpAssetPath = function hwpAssetPath(src) {
167172
return path.relative(this.hwpPublicPath, src);
168173
};
169174

170-
SubresourceIntegrityPlugin.prototype.apply = function apply(compiler) {
175+
function computeIntegrity(hashFuncNames, source) {
176+
return hashFuncNames.map(function mapHashFuncName(hashFuncName) {
177+
var hash = crypto.createHash(hashFuncName).update(source, 'utf8').digest('base64');
178+
return hashFuncName + '-' + hash;
179+
}).join(' ');
180+
}
181+
182+
SubresourceIntegrityPlugin.prototype.warnIfHotUpdate = function warnIfHotUpdate(
183+
compilation, source
184+
) {
185+
if (source.indexOf('webpackHotUpdate') >= 0) {
186+
this.warnOnce(
187+
compilation,
188+
'webpack-subresource-integrity may interfere with hot reloading. ' +
189+
'Consider disabling this plugin in development mode.'
190+
);
191+
}
192+
};
193+
194+
SubresourceIntegrityPlugin.prototype.replaceAsset = function replaceAsset(
195+
assets,
196+
depChunkIds,
197+
hashByChunkId,
198+
chunkFile
199+
) {
200+
var oldSource = assets[chunkFile].source();
201+
var newAsset;
202+
var magicMarker;
203+
var magicMarkerPos;
204+
205+
newAsset = new ReplaceSource(assets[chunkFile]);
206+
207+
depChunkIds.forEach(function replaceMagicMarkers(depChunkId) {
208+
magicMarker = makePlaceholder(depChunkId);
209+
magicMarkerPos = oldSource.indexOf(magicMarker);
210+
if (magicMarkerPos >= 0) {
211+
newAsset.replace(
212+
magicMarkerPos,
213+
(magicMarkerPos + magicMarker.length) - 1,
214+
hashByChunkId[depChunkId]);
215+
}
216+
});
217+
218+
// eslint-disable-next-line no-param-reassign
219+
assets[chunkFile] = newAsset;
220+
221+
newAsset.integrity = computeIntegrity(this.options.hashFuncNames, newAsset.source());
222+
return newAsset;
223+
};
224+
225+
SubresourceIntegrityPlugin.prototype.processChunk = function processChunk(
226+
chunk, compilation, assets
227+
) {
171228
var self = this;
229+
var newAsset;
230+
var hashByChunkId = {};
231+
232+
function recurse(childChunk) {
233+
var depChunkIds = [];
172234

173-
function computeIntegrity(source) {
174-
return self.options.hashFuncNames.map(function mapHashFuncName(hashFuncName) {
175-
var hash = crypto.createHash(hashFuncName).update(source, 'utf8').digest('base64');
176-
return hashFuncName + '-' + hash;
177-
}).join(' ');
235+
if (hashByChunkId[childChunk.id]) {
236+
return [];
237+
}
238+
hashByChunkId[childChunk.id] = true;
239+
240+
childChunk.chunks.forEach(function mapChunk(depChunk) {
241+
depChunkIds = depChunkIds.concat(recurse(depChunk));
242+
});
243+
244+
if (childChunk.files.length > 0) {
245+
self.warnIfHotUpdate(compilation, assets[childChunk.files[0]].source());
246+
newAsset = self.replaceAsset(
247+
assets,
248+
depChunkIds,
249+
hashByChunkId,
250+
childChunk.files[0]);
251+
hashByChunkId[childChunk.id] = newAsset.integrity;
252+
}
253+
return [childChunk.id].concat(depChunkIds);
178254
}
255+
return recurse(chunk);
256+
};
257+
258+
function getTagSrc(tag) {
259+
// Get asset path - src from scripts and href from links
260+
return tag.attributes.href || tag.attributes.src;
261+
}
262+
263+
function filterTag(tag) {
264+
// Process only script and link tags with a url
265+
return (tag.tagName === 'script' || tag.tagName === 'link') && getTagSrc(tag);
266+
}
267+
268+
function normalizePath(p) {
269+
return p.replace(/\?.*$/, '').split(path.sep).join('/');
270+
}
271+
272+
SubresourceIntegrityPlugin.prototype.apply = function apply(compiler) {
273+
var self = this;
179274

180275
compiler.plugin('after-plugins', function afterPlugins() {
181276
compiler.plugin('this-compilation', function thisCompilation(compilation) {
@@ -192,94 +287,22 @@ SubresourceIntegrityPlugin.prototype.apply = function apply(compiler) {
192287
* placeholders by the actual values.
193288
*/
194289
compilation.plugin('after-optimize-assets', function optimizeAssetsPlugin(assets) {
195-
var hashByChunkId = {};
196-
var visitedByChunkId = {};
197-
var chunkFile;
198-
var oldSource;
199-
var magicMarker;
200-
var magicMarkerPos;
201-
var newAsset;
202290
var asset;
203-
var newSource;
204-
205-
function processChunkRecursive(chunk) {
206-
var depChunkIds = [];
207-
208-
if (visitedByChunkId[chunk.id]) {
209-
return [];
210-
}
211-
visitedByChunkId[chunk.id] = true;
212-
213-
chunk.chunks.forEach(function mapChunk(depChunk) {
214-
depChunkIds = depChunkIds.concat(processChunkRecursive(depChunk));
215-
});
216-
217-
if (chunk.files.length > 0) {
218-
chunkFile = chunk.files[0];
219-
220-
oldSource = assets[chunkFile].source();
221-
222-
if (oldSource.indexOf('webpackHotUpdate') >= 0) {
223-
self.warnOnce(
224-
compilation,
225-
'webpack-subresource-integrity may interfere with hot reloading. ' +
226-
'Consider disabling this plugin in development mode.'
227-
);
228-
}
229-
230-
newAsset = new ReplaceSource(assets[chunkFile]);
231-
232-
depChunkIds.forEach(function forEachChunk(depChunkId) {
233-
magicMarker = makePlaceholder(depChunkId);
234-
magicMarkerPos = oldSource.indexOf(magicMarker);
235-
if (magicMarkerPos >= 0) {
236-
newAsset.replace(
237-
magicMarkerPos,
238-
(magicMarkerPos + magicMarker.length) - 1,
239-
hashByChunkId[depChunkId]);
240-
}
241-
});
242-
243-
// eslint-disable-next-line no-param-reassign
244-
assets[chunkFile] = newAsset;
245-
246-
newSource = newAsset.source();
247-
newAsset.integrity = computeIntegrity(newSource);
248-
hashByChunkId[chunk.id] = newAsset.integrity;
249-
}
250-
return [chunk.id].concat(depChunkIds);
251-
}
252291

253292
compilation.chunks.forEach(function forEachChunk(chunk) {
254-
// chunk.entry was removed in Webpack 2. Use hasRuntime()
255-
// for this check instead (if it exists)
256293
if (('hasRuntime' in chunk) ? chunk.hasRuntime() : chunk.entry) {
257-
processChunkRecursive(chunk);
294+
self.processChunk(chunk, compilation, assets);
258295
}
259296
});
260297

261298
Object.keys(assets).forEach(function loop(assetKey) {
262299
asset = assets[assetKey];
263300
if (!asset.integrity) {
264-
asset.integrity = computeIntegrity(asset.source());
301+
asset.integrity = computeIntegrity(self.options.hashFuncNames, asset.source());
265302
}
266303
});
267304
});
268305

269-
function getTagSrc(tag) {
270-
// Get asset path - src from scripts and href from links
271-
return tag.attributes.href || tag.attributes.src;
272-
}
273-
274-
function filterTag(tag) {
275-
// Process only script and link tags with a url
276-
return (tag.tagName === 'script' || tag.tagName === 'link') && getTagSrc(tag);
277-
}
278-
279-
function normalizePath(p) {
280-
return p.replace(/\?.*$/, '').split(path.sep).join('/');
281-
}
282-
283306
function getIntegrityChecksumForAsset(src) {
284307
var normalizedSrc;
285308
var normalizedKey;

test/test-webpack.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,4 +919,57 @@ describe('html-webpack-plugin', function describe() {
919919
cleanup(err);
920920
});
921921
});
922+
923+
it('works with multiple CommonsChunkPlugins', function it(callback) {
924+
var tmpDir = tmp.dirSync();
925+
var webpackConfig;
926+
927+
var pageA = path.join(tmpDir.name, './pageA.js');
928+
var pageB = path.join(tmpDir.name, './pageB.js');
929+
930+
fs.writeFileSync(pageA, '');
931+
fs.writeFileSync(pageB, '');
932+
933+
function cleanup(err) {
934+
fs.unlinkSync(pageA);
935+
fs.unlinkSync(pageB);
936+
fs.unlinkSync(path.join(tmpDir.name, 'commons1.js'));
937+
fs.unlinkSync(path.join(tmpDir.name, 'commons2.js'));
938+
tmpDir.removeCallback();
939+
callback(err);
940+
}
941+
942+
webpackConfig = {
943+
entry: {
944+
pageA: pageA,
945+
pageB: pageB
946+
},
947+
output: {
948+
path: tmpDir.name,
949+
filename: '[name].js',
950+
crossOriginLoading: 'anonymous'
951+
},
952+
plugins: [
953+
new webpack.optimize.CommonsChunkPlugin({
954+
name: 'commons1',
955+
chunks: ['pageA']
956+
}),
957+
new webpack.optimize.CommonsChunkPlugin({
958+
name: 'commons2',
959+
chunks: ['pageB']
960+
}),
961+
new SriPlugin({ hashFuncNames: ['sha256', 'sha384'] })
962+
]
963+
};
964+
webpack(webpackConfig, function webpackCallback(err, result) {
965+
expect(result.compilation.errors).toEqual([]);
966+
expect(result.compilation.warnings).toEqual([]);
967+
968+
['commons1.js', 'commons2.js'].forEach(function check(filename) {
969+
expect(fs.readFileSync(path.join(tmpDir.name, filename), 'utf-8').indexOf('CHUNK-SRI-HASH')).toEqual(-1);
970+
});
971+
972+
cleanup(err);
973+
});
974+
});
922975
});

0 commit comments

Comments
 (0)