Skip to content

Commit 6d484e8

Browse files
committed
option to merge chunks
1 parent b627690 commit 6d484e8

File tree

16 files changed

+188
-34
lines changed

16 files changed

+188
-34
lines changed

lib/buildDeps.js

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module.exports = function buildDeps(context, mainModule, options, callback) {
3333
modules: {},
3434
modulesById: {},
3535
chunks: {},
36+
chunkCount: 0,
3637
nextModuleId: 0,
3738
nextChunkId: 1,
3839
chunkModules: {} // used by checkObsolete
@@ -71,13 +72,17 @@ module.exports = function buildDeps(context, mainModule, options, callback) {
7172
// remove modules which are included in parent chunk
7273
removeParentsModules(depTree, depTree.chunks[chunkId]);
7374

74-
// remove the chunk if it is empty
75-
removeChunkIfEmpty(depTree, depTree.chunks[chunkId]);
76-
77-
// remove duplicate chunks
75+
// remove duplicate and empty chunks
7876
checkObsolete(depTree, depTree.chunks[chunkId]);
7977
}
8078

79+
if(options.maxChunks) {
80+
while(depTree.chunkCount > options.maxChunks) {
81+
if(!removeOneChunk(depTree, true))
82+
break;
83+
}
84+
}
85+
8186
createRealChunkIds(depTree, options);
8287
options.events.emit("task-end", "optimize");
8388

@@ -649,6 +654,7 @@ function addChunk(depTree, chunkStartpoint, options) {
649654
usages: 1
650655
};
651656
depTree.chunks[chunk.id] = chunk;
657+
depTree.chunkCount++;
652658
}
653659
if(chunkStartpoint) {
654660
chunkStartpoint.chunkId = chunk.id;
@@ -712,39 +718,95 @@ function removeParentsModules(depTree, chunk) {
712718
}
713719
}
714720

715-
function removeChunkIfEmpty(depTree, chunk) {
716-
var hasModules = false;
717-
for(var moduleId in chunk.modules) {
718-
if(chunk.modules[moduleId] === "include") {
719-
hasModules = true;
720-
break;
721-
}
722-
}
723-
if(!hasModules) {
724-
chunk.contexts.forEach(function(c) { c.chunkId = null; });
725-
chunk.empty = true;
726-
}
727-
}
728-
729721
function checkObsolete(depTree, chunk) {
730722
var modules = [];
731723
for(var moduleId in chunk.modules) {
732724
if(chunk.modules[moduleId] === "include") {
733725
modules.push(moduleId);
734726
}
735727
}
736-
if(modules.length === 0) return;
728+
if(modules.length === 0) {
729+
chunk.contexts.forEach(function(c) { c.chunkId = null; });
730+
chunk.empty = true;
731+
depTree.chunkCount--;
732+
return;
733+
}
737734
modules.sort();
738735
var moduleString = modules.join(" ");
739736
if(depTree.chunkModules[moduleString]) {
740737
chunk.equals = depTree.chunkModules[moduleString];
741738
chunk.contexts.forEach(function(c) {
742739
c.chunkId = chunk.equals;
743740
});
741+
depTree.chunkCount--;
744742
} else
745743
depTree.chunkModules[moduleString] = chunk.id;
746744
}
747745

746+
function moduleSize(depTree, moduleId) {
747+
return depTree.modulesById[moduleId].source && depTree.modulesById[moduleId].source.length || 0;
748+
}
749+
750+
function removeOneChunk(depTree, force) {
751+
var chunks = [];
752+
for(var chunkId in depTree.chunks) {
753+
var chunk = depTree.chunks[chunkId];
754+
if(!chunk.empty && !chunk.equals && chunk.id != "main") {
755+
chunks.push(chunk);
756+
}
757+
}
758+
var best = null;
759+
chunks.forEach(function(chunkA, idxA) {
760+
chunks.forEach(function(chunkB, idxB) {
761+
if(idxB <= idxA) return;
762+
var sizeSum = 60, sizeMerged = 30;
763+
for(var moduleId in chunkA.modules) {
764+
if(chunkA.modules[moduleId] === "include") {
765+
var size = moduleSize(depTree, moduleId);
766+
sizeSum += size + 10;
767+
sizeMerged += size + 10;
768+
}
769+
}
770+
for(var moduleId in chunkB.modules) {
771+
if(chunkB.modules[moduleId] === "include") {
772+
var size = moduleSize(depTree, moduleId);
773+
sizeSum += size + 10;
774+
if(chunkA.modules[moduleId] !== "include")
775+
sizeMerged += size + 10;
776+
}
777+
}
778+
var value = sizeSum * 2 - sizeMerged;
779+
if(best == null || best[0] < value)
780+
best = [value, chunkA.id, chunkB.id];
781+
});
782+
});
783+
console.dir(best);
784+
if(!best) return false;
785+
if(force || best[0] > 0) {
786+
var chunk = depTree.chunks[best[1]];
787+
chunk.equals = best[2];
788+
chunk.contexts.forEach(function(c) {
789+
c.chunkId = chunk.equals;
790+
});
791+
chunks.forEach(function(chunk) {
792+
if(chunk.equals == best[1]) {
793+
chunk.equals = best[2];
794+
chunk.contexts.forEach(function(c) {
795+
c.chunkId = chunk.equals;
796+
});
797+
}
798+
});
799+
var otherChunk = depTree.chunks[best[2]];
800+
for(var moduleId in chunk.modules) {
801+
if(chunk.modules[moduleId] === "include") {
802+
otherChunk.modules[moduleId] = "include";
803+
}
804+
}
805+
depTree.chunkCount--;
806+
return true;
807+
}
808+
}
809+
748810
// rename the chunk ids after a defined sheme
749811
function createRealChunkIds(depTree, options) {
750812
var sortedChunks = [];

lib/webpack.js

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ function webpack(context, moduleName, options, callback) {
270270
});
271271

272272
// the template used
273-
var template = getTemplate(options, {chunks: chunkIds.length > 1});
273+
var templateOptions = {chunks: chunkIds.length > 1};
274+
var template = getTemplate(options, templateOptions);
274275

275276
// hash as crypto.Hash instance
276277
// for compution
@@ -281,6 +282,9 @@ function webpack(context, moduleName, options, callback) {
281282
hash.update(JSON.stringify(options.outputPostfix));
282283
hash.update(JSON.stringify(options.outputJsonpFunction));
283284
hash.update(JSON.stringify(options.publicPrefix));
285+
try {
286+
hash.update(JSON.stringify(options));
287+
} catch(e) {}
284288
hash.update(template);
285289
hash.update("1");
286290
} catch(e) {
@@ -335,18 +339,23 @@ function webpack(context, moduleName, options, callback) {
335339
buffer.push(JSON.stringify(options.publicPrefix.replace(HASH_REGEXP, hash)));
336340
buffer.push(",\n");
337341
} else { // lazy loaded chunk
338-
// write only jsonp function and chunk id as function call
339-
buffer.push("/******/");
340-
buffer.push(options.outputJsonpFunction);
341-
buffer.push("(");
342-
buffer.push(chunk.realId);
343-
buffer.push(", {\n");
342+
// write chunk template
343+
var chunkTemplate = getTemplate.chunk(chunk, options, templateOptions);
344+
buffer.push(chunkTemplate[0]);
345+
buffer.push("{\n");
344346
}
345347
// write content of chunk
346348
buffer.push(content);
347349

348350
// and close object
349-
buffer.push("/******/})");
351+
buffer.push("/******/}");
352+
if(chunk.realId === 0) { // initial chunk
353+
// end function call
354+
buffer.push(")");
355+
} else {
356+
// write chunk template
357+
buffer.push(chunkTemplate[1]);
358+
}
350359

351360
// convert buffer to string
352361
buffer = buffer.join("");
@@ -506,14 +515,29 @@ function webpack(context, moduleName, options, callback) {
506515
}
507516

508517
// returns the template for specific options
509-
function getTemplate(options, templateOptions) {
518+
function getTemplateFunction(options) {
510519
if(options.template) {
511520
if(typeof options.template === "string")
512-
return require(options.template)(options, templateOptions);
521+
return require(options.template);
513522
else
514-
return options.template(options, templateOptions);
523+
return options.template;
515524
} else
516-
return require("../templates/browser")(options, templateOptions);
525+
return require("../templates/browser");
526+
527+
}
528+
function getTemplate(options, templateOptions) {
529+
return getTemplateFunction(options)(options, templateOptions);
530+
}
531+
getTemplate.chunk = function(chunk, options, templateOptions) {
532+
function fallback(chunk, options) {
533+
return [
534+
"/******/" + options.outputJsonpFunction + "(" + chunk.realId + ",",
535+
")"
536+
]
537+
}
538+
var templateFunction = getTemplateFunction(options);
539+
templateFunction = templateFunction && templateFunction.chunk || fallback;
540+
return templateFunction(chunk, options, templateOptions);
517541
}
518542

519543
// minimize it the uglify

templates/node.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = function(options, templateOptions) {
2+
return require("fs").readFileSync(require("path").join(__dirname, "nodeTemplate.js"));
3+
}
4+
module.exports.chunk = function(chunk, options, templateOptions) {
5+
return [
6+
"/******/module.exports = ",
7+
""
8+
]
9+
}

templates/nodeTemplate.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/******/(function(modules) {
2+
/******/ var installedModules = {};
3+
/******/ function req(moduleId) {
4+
/******/ if(typeof moduleId !== "number") throw new Error("Cannot find module '"+moduleId+"'");
5+
/******/ if(installedModules[moduleId])
6+
/******/ return installedModules[moduleId].exports;
7+
/******/ var module = installedModules[moduleId] = {
8+
/******/ exports: {},
9+
/******/ id: moduleId,
10+
/******/ loaded: false
11+
/******/ };
12+
/******/ modules[moduleId](module, module.exports, req);
13+
/******/ module.loaded = true;
14+
/******/ return module.exports;
15+
/******/ }
16+
/******/ req.e = function(chunkId, callback) {
17+
/******/ var mods = require("./" + chunkId + modules.a);
18+
/******/ for(var id in mods)
19+
/******/ modules[id] = mods[id];
20+
/******/ callback(req);
21+
/******/ };
22+
/******/ req.modules = modules;
23+
/******/ req.cache = installedModules;
24+
/******/ return req(0);
25+
/******/})

test/buildDeps.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe("buildDeps", function() {
5252
done();
5353
});
5454
});
55-
55+
5656
it("should compile", function() {
5757
depTree.should.have.property("modulesByFile").and.be.a("object");
5858
depTree.should.have.property("modules").and.be.a("object");
@@ -90,7 +90,7 @@ describe("buildDeps", function() {
9090
done();
9191
});
9292
});
93-
93+
9494
it("should compile", function() {
9595
depTree.should.have.property("modulesByFile").and.be.a("object");
9696
depTree.should.have.property("modules").and.be.a("object");
@@ -111,7 +111,7 @@ describe("buildDeps", function() {
111111
depTree.modulesByFile[path.join(__dirname, "fixtures", "a.js")].chunks.should.be.eql(["main", 1]);
112112
depTree.modulesByFile[path.join(__dirname, "fixtures", "c.js")].chunks.should.be.eql([1]);
113113
});
114-
114+
115115
it("should have correct chucks", function() {
116116
var main3id = ""+depTree.modulesByFile[path.join(__dirname, "fixtures", "main3.js")].id;
117117
var aid = ""+depTree.modulesByFile[path.join(__dirname, "fixtures", "a.js")].id;
@@ -126,4 +126,26 @@ describe("buildDeps", function() {
126126
depTree.chunks[1].modules[cid].should.be.equal("include");
127127
});
128128
});
129+
130+
describe("of main4", function() {
131+
var depTree;
132+
before(function(done) {
133+
buildDeps(path.join(__dirname, "fixtures"), "./main4.js", {
134+
maxChunks: 5,
135+
template: require("../templates/node")
136+
}, function(err, tree) {
137+
if(err) return done(err);
138+
should.not.exist(err);
139+
should.exist(tree);
140+
depTree = tree;
141+
done();
142+
});
143+
});
144+
145+
it("should have 5 chunks", function() {
146+
console.dir(depTree.chunks);
147+
Object.keys(depTree.chunkCount).length.should.be.eql(5);
148+
});
149+
150+
});
129151
});

test/fixtures/items/item (0).js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 0;

test/fixtures/items/item (1).js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 1;

test/fixtures/items/item (2).js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 2;

test/fixtures/items/item (3).js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 3;

test/fixtures/items/item (4).js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 4;

0 commit comments

Comments
 (0)