improved chunk loading

now a chunk is not loaded if all included modules are already loaded (by other chunks)
This commit is contained in:
Tobias Koppers 2013-02-24 02:05:55 +01:00
parent 1a449cefb6
commit 9fe83b3dc8
12 changed files with 205 additions and 143 deletions

View File

@ -263,6 +263,10 @@ Compilation.prototype.seal = function seal(callback) {
this.applyPlugins("after-optimize-chunks", this.chunks);
this.applyModuleIds();
this.applyChunkIds();
this.applyPlugins("optimize-module-ids", this.modules);
this.applyPlugins("after-optimize-module-ids", this.modules);
this.applyPlugins("optimize-chunk-ids", this.chunks);
this.applyPlugins("after-optimize-chunk-ids", this.chunks);
this.sortItems();
this.createChunkAssets();
this.summarizeDependencies();
@ -395,6 +399,8 @@ Compilation.prototype.applyChunkIds = function applyChunkIds() {
this.chunks.forEach(function(chunk) {
if(chunk.id === null)
chunk.id = ++i;
if(!chunk.ids)
chunk.ids = [chunk.id];
});
};

View File

@ -12,7 +12,7 @@ module.exports = JsonpChunkTemplate;
JsonpChunkTemplate.prototype.render = function(chunk, moduleTemplate, dependencyTemplates) {
var jsonpFunction = this.outputOptions.jsonpFunction || ("webpackJsonp" + (this.outputOptions.library || ""));
var buf = [];
buf.push(jsonpFunction + "(" + chunk.id + ", {\n");
buf.push(jsonpFunction + "(" + JSON.stringify(chunk.ids) + ", {\n");
chunk.modules.forEach(function(module, idx) {
if(idx != 0) buf.push(",\n");
buf.push("\n/***/ " + module.id + ":\n");
@ -25,7 +25,7 @@ JsonpChunkTemplate.prototype.render = function(chunk, moduleTemplate, dependency
JsonpChunkTemplate.prototype.updateHash = function(hash) {
hash.update("jsonp");
hash.update("1");
hash.update("2");
hash.update(this.outputOptions.jsonpFunction + "");
hash.update(this.outputOptions.library + "");
};

View File

@ -46,7 +46,7 @@ JsonpMainTemplate.prototype.render = function(hash, chunk, moduleTemplate, depen
if(chunk.chunks.length == 0) {
addLine(2, "callback.call(null, require);");
} else {
addLine(2, "if(installedChunks[chunkId] === 1) return callback.call(null, require);");
addLine(2, "if(installedChunks[chunkId] === 0) return callback.call(null, require);");
addLine(2, "if(installedChunks[chunkId] !== undefined)");
addLine(3, "installedChunks[chunkId].push(callback);");
addLine(2, "else {");
@ -63,12 +63,16 @@ JsonpMainTemplate.prototype.render = function(hash, chunk, moduleTemplate, depen
addLine(1, "require.modules = modules;");
addLine(1, "require.cache = installedModules;");
if(chunk.chunks.length > 0) {
addLine(1, "var installedChunks = {0:1};");
addLine(1, "window[" + JSON.stringify(jsonpFunction) + "] = function webpackJsonpCallback(chunkId, moreModules) {");
addLine(1, "var installedChunks = {0:0};");
addLine(1, "window[" + JSON.stringify(jsonpFunction) + "] = function webpackJsonpCallback(chunkIds, moreModules) {");
addLine(2, "for(var moduleId in moreModules)");
addLine(3, "modules[moduleId] = moreModules[moduleId];");
addLine(2, "var callbacks = installedChunks[chunkId];");
addLine(2, "installedChunks[chunkId] = 1;");
addLine(2, "var callbacks = [];");
addLine(2, "for(var i = 0; i < chunkIds.length; i++) {");
addLine(3, "var installedChunk = installedChunks[chunkIds[i]];");
addLine(3, "if(installedChunk) callbacks.push.apply(callbacks, installedChunk);");
addLine(3, "installedChunks[chunkIds[i]] = 0;");
addLine(2, "}");
addLine(2, "for(var i = 0; i < callbacks.length; i++)");
addLine(3, "callbacks[i].call(null, require);");
addLine(1, "};");
@ -89,7 +93,7 @@ JsonpMainTemplate.prototype.render = function(hash, chunk, moduleTemplate, depen
JsonpMainTemplate.prototype.updateHash = function(hash) {
hash.update("jsonp");
hash.update("1");
hash.update("2");
hash.update(this.outputOptions.publicPath + "");
hash.update(this.outputOptions.filename + "");
hash.update(this.outputOptions.chunkFilename + "");

View File

@ -94,7 +94,9 @@ Stats.prototype.toJson = function toJson(options, forToString) {
compilation.chunks.forEach(function(chunk) {
chunk.files.forEach(function(asset) {
if(assetsByFile[asset]) {
assetsByFile[asset].chunks.push(chunk.id);
chunk.ids.forEach(function(id) {
assetsByFile[asset].chunks.push(id);
});
if(chunk.name)
assetsByFile[asset].chunkNames.push(chunk.name);
}

View File

@ -38,6 +38,7 @@ var MinChunkSizePlugin = require("./optimize/MinChunkSizePlugin");
var RemoveParentModulesPlugin = require("./optimize/RemoveParentModulesPlugin");
var RemoveEmptyChunksPlugin = require("./optimize/RemoveEmptyChunksPlugin");
var MergeDuplicateChunksPlugin = require("./optimize/MergeDuplicateChunksPlugin");
var FlagIncludedChunksPlugin = require("./optimize/FlagIncludedChunksPlugin");
var ModulesInDirectoriesPlugin = require("enhanced-resolve/lib/ModulesInDirectoriesPlugin");
var ModulesInRootPlugin = require("enhanced-resolve/lib/ModulesInRootPlugin");
@ -120,7 +121,8 @@ WebpackOptionsApply.prototype.process = function(options, compiler) {
compiler.apply(
new RemoveParentModulesPlugin(),
new RemoveEmptyChunksPlugin(),
new MergeDuplicateChunksPlugin()
new MergeDuplicateChunksPlugin(),
new FlagIncludedChunksPlugin()
);
if(options.optimize && options.optimize.minChunkSize)
compiler.apply(new MinChunkSizePlugin(options.optimize.minChunkSize));

View File

@ -11,18 +11,19 @@ module.exports = NodeChunkTemplate;
NodeChunkTemplate.prototype.render = function(chunk, moduleTemplate, dependencyTemplates) {
var buf = [];
buf.push("module.exports = ({\n");
buf.push("exports.ids = " + JSON.stringify(chunk.ids) + ";\n");
buf.push("exports.modules = {\n");
chunk.modules.forEach(function(module, idx) {
if(idx != 0) buf.push(",\n");
buf.push("\n/***/ " + module.id + ":\n");
var source = moduleTemplate.render(module, dependencyTemplates);
buf.push(source.source());
});
buf.push("\n\n})");
buf.push("\n\n};");
return new RawSource(buf.join(""));
};
NodeChunkTemplate.prototype.updateHash = function(hash) {
hash.update("node");
hash.update("1");
hash.update("2");
};

View File

@ -45,9 +45,12 @@ NodeMainTemplate.prototype.render = function(hash, chunk, moduleTemplate, depend
addLine(2, "callback.call(null, __require);");
} else {
addLine(2, "if(installedChunks[chunkId] === 1) return callback.call(null, __require);");
addLine(2, "var moreModules = require(" + JSON.stringify("./" + chunkFilename.replace(REGEXP_HASH, hash).replace(REGEXP_NAME, "")).replace(REGEXP_ID, "\"+chunkId+\"") + ");");
addLine(2, "var chunk = require(" + JSON.stringify("./" + chunkFilename.replace(REGEXP_HASH, hash).replace(REGEXP_NAME, "")).replace(REGEXP_ID, "\"+chunkId+\"") + ");");
addLine(2, "var moreModules = chunk.modules, chunkIds = chunk.ids;");
addLine(2, "for(var moduleId in moreModules)");
addLine(3, "modules[moduleId] = moreModules[moduleId];");
addLine(2, "for(var i = 0; i < chunkIds.length; i++)");
addLine(3, "installedChunks[chunkIds[i]] = 1;");
addLine(2, "callback.call(null, __require);");
}
addLine(1, "};");
@ -73,7 +76,7 @@ NodeMainTemplate.prototype.render = function(hash, chunk, moduleTemplate, depend
NodeMainTemplate.prototype.updateHash = function(hash) {
hash.update("node");
hash.update("1");
hash.update("2");
hash.update(this.outputOptions.filename + "");
hash.update(this.outputOptions.chunkFilename + "");
};

View File

@ -0,0 +1,26 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
function FlagIncludedChunksPlugin() {
}
module.exports = FlagIncludedChunksPlugin;
FlagIncludedChunksPlugin.prototype.apply = function(compiler) {
compiler.plugin("compilation", function(compilation) {
compilation.plugin("optimize-chunk-ids", function(chunks) {
chunks.forEach(function(chunkA) {
chunks.forEach(function(chunkB) {
if(chunkA === chunkB) return;
// is chunkB in chunkA?
if(chunkA.modules.length < chunkB.modules.length) return;
for(var i = 0; i < chunkB.modules.length; i++) {
if(chunkA.modules.indexOf(chunkB.modules[i]) < 0) return;
}
chunkA.ids.push(chunkB.id);
});
});
});
});
};

View File

@ -12,7 +12,7 @@ module.exports = WebWorkerChunkTemplate;
WebWorkerChunkTemplate.prototype.render = function(chunk, moduleTemplate, dependencyTemplates) {
var chunkCallbackName = this.outputOptions.chunkCallbackName || ("webpackChunk" + (this.outputOptions.library || ""));
var buf = [];
buf.push(chunkCallbackName + "({\n");
buf.push(chunkCallbackName + "(" + JSON.stringify(chunk.ids) + ", {\n");
chunk.modules.forEach(function(module, idx) {
if(idx != 0) buf.push(",\n");
buf.push("\n/***/ " + module.id + ":\n");
@ -25,7 +25,7 @@ WebWorkerChunkTemplate.prototype.render = function(chunk, moduleTemplate, depend
WebWorkerChunkTemplate.prototype.updateHash = function(hash) {
hash.update("webworker");
hash.update("1");
hash.update("2");
hash.update(this.outputOptions.chunkCallbackName + "");
hash.update(this.outputOptions.library + "");
};

View File

@ -55,9 +55,11 @@ WebWorkerMainTemplate.prototype.render = function(hash, chunk, moduleTemplate, d
addLine(1, "require.cache = installedModules;");
if(chunk.chunks.length > 0) {
addLine(1, "var installedChunks = {0:1};");
addLine(1, "this[" + JSON.stringify(chunkCallbackName) + "] = function webpackChunkCallback(moreModules) {");
addLine(1, "this[" + JSON.stringify(chunkCallbackName) + "] = function webpackChunkCallback(chunkIds, moreModules) {");
addLine(2, "for(var moduleId in moreModules)");
addLine(3, "modules[moduleId] = moreModules[moduleId];");
addLine(2, "for(var i = 0; i < chunkIds.length; i++)");
addLine(3, "installedChunks[chunkIds[i]] = 1;");
addLine(1, "};");
}
addLine(1, "return require(0);");
@ -76,7 +78,7 @@ WebWorkerMainTemplate.prototype.render = function(hash, chunk, moduleTemplate, d
WebWorkerMainTemplate.prototype.updateHash = function(hash) {
hash.update("webworker");
hash.update("1");
hash.update("2");
hash.update(this.outputOptions.publicPath + "");
hash.update(this.outputOptions.filename + "");
hash.update(this.outputOptions.chunkFilename + "");

View File

@ -1,6 +1,6 @@
{
"name": "webpack",
"version": "0.9.0-beta27",
"version": "0.9.0-beta28",
"author": "Tobias Koppers @sokra",
"description": "Packs CommonJs/AMD Modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jade, coffee, css, less, ... and your custom stuff.",
"dependencies": {

View File

@ -132,61 +132,6 @@ describe("main", function() {
});
});
describe("context", function() {
it("should be able to load a file with the require.context method", function() {
require.context("../templates")("./tmpl").should.be.eql("test template");
(require.context("../././templates"))("./tmpl").should.be.eql("test template");
(require.context(".././templates/.")("./tmpl")).should.be.eql("test template");
require . context ( "." + "." + "/" + "templ" + "ates" ) ( "./subdir/tmpl.js" ).should.be.eql("subdir test template");
});
it("should automatically create contexts", function() {
var template = "tmpl", templateFull = "./tmpl.js";
var mp = "mp", tmp = "tmp", mpl = "mpl";
require("../templates/" + template).should.be.eql("test template");
require("../templates/" + tmp + "l").should.be.eql("test template");
require("../templates/t" + mpl).should.be.eql("test template");
require("../templates/t" + mp + "l").should.be.eql("test template");
require("../templates/templateLoader")(templateFull).should.be.eql("test template");
require("../templates/templateLoaderIndirect")(templateFull).should.be.eql("test template");
});
it("should also work in a chunk", function(done) {
require.ensure([], function(require) {
var contextRequire = require.context(".");
contextRequire("./two").should.be.eql(2);
var tw = "tw";
require("." + "/" + tw + "o").should.be.eql(2);
done();
});
});
it("should be able to use a context with a loader", function() {
var abc = "abc", scr = "script.coffee";
require("../resources/" + scr).should.be.eql("coffee test");
require("raw!../resources/" + abc + ".txt").should.be.eql("abc");
});
it("should be able to require.resolve with automatical context", function() {
var template = "tmpl";
require.resolve("../templates/" + template).should.be.eql(require.resolve("../templates/tmpl"));
});
it("should resolve loaders relative to require", function() {
var index = "index", test = "test";
require("../loaders/queryloader?query!!!!../node_modules/subcontent/" + index + ".js").should.be.eql({
resourceQuery: null,
query: "?query",
prev: "module.exports = \"error\";"
});
require("!../loaders/queryloader?query!../node_modules/subcontent/" + test + ".jade").should.be.eql({
resourceQuery: null,
query: "?query",
prev: "xyz: abc"
});
});
});
describe("parsing", function() {
it("should parse complex require calls", function() {
@ -260,7 +205,6 @@ describe("main", function() {
});
});
describe("chunks", function() {
it("should handle duplicate chunks", function(done) {
var firstOne = false, secondOne = false;
@ -308,6 +252,145 @@ describe("main", function() {
should.exist(value);
value.should.be.eql("require.include");
});
it("should not load a chunk which is included in a already loaded one", function(done) {
var async = false;
require.ensure(["./empty?x", "./empty?y", "./empty?z"], function(require) {
async.should.be.eql(true);
loadChunk();
});
async = true;
function loadChunk() {
var sync = true;
require.ensure(["./empty?x", "./empty?y"], function(require) {
sync.should.be.eql(true);
done();
});
sync = false;
}
});
});
describe("AMD", function() {
it("should be able to use AMD-style require", function(done) {
var template = "tmpl";
require(["./circular", "../templates/" + template, true ? "./circular" : "./circular"], function(circular, testTemplate, circular2) {
circular.should.be.eql(1);
circular2.should.be.eql(1);
testTemplate.should.be.eql("test template");
done();
});
});
it("should be able to use require.js-style define", function(done) {
define("name", ["./circular"], function(circular) {
circular.should.be.eql(1);
done();
});
});
it("should be able to use require.js-style define, without name", function(done) {
define(["./circular"], function(circular) {
circular.should.be.eql(1);
done();
});
});
it("should be able to use require.js-style define, with empty dependencies", function(done) {
define("name", [], function() {
done();
});
});
it("should be able to use require.js-style define, without dependencies", function(done) {
define("name", function() {
done();
});
});
var obj = {};
it("should be able to use require.js-style define, with an object", function() {
define("blaaa", obj);
});
after(function() {
module.exports.should.be.equal(obj);
});
it("should offer AMD-style define for CommonJs", function(done) {
var _test_require = require.valueOf();
var _test_exports = exports;
var _test_module = module;
define(function(require, exports, module) {
(typeof require).should.be.eql("function");
require.valueOf().should.be.equal(_test_require);
exports.should.be.equal(_test_exports);
module.should.be.equal(_test_module);
require("./circular").should.be.eql(1);
done();
});
});
it("should not crash on require.js require only with array", function() {
require(["./circular"]);
});
it("should create a chunk for require.js require", function(done) {
var sameTick = true;
require(["./c"], function(c) {
sameTick.should.be.eql(false);
c.should.be.eql("c");
require("./d").should.be.eql("d");
done();
});
sameTick = false;
});
});
describe("context", function() {
it("should be able to load a file with the require.context method", function() {
require.context("../templates")("./tmpl").should.be.eql("test template");
(require.context("../././templates"))("./tmpl").should.be.eql("test template");
(require.context(".././templates/.")("./tmpl")).should.be.eql("test template");
require . context ( "." + "." + "/" + "templ" + "ates" ) ( "./subdir/tmpl.js" ).should.be.eql("subdir test template");
});
it("should automatically create contexts", function() {
var template = "tmpl", templateFull = "./tmpl.js";
var mp = "mp", tmp = "tmp", mpl = "mpl";
require("../templates/" + template).should.be.eql("test template");
require("../templates/" + tmp + "l").should.be.eql("test template");
require("../templates/t" + mpl).should.be.eql("test template");
require("../templates/t" + mp + "l").should.be.eql("test template");
require("../templates/templateLoader")(templateFull).should.be.eql("test template");
require("../templates/templateLoaderIndirect")(templateFull).should.be.eql("test template");
});
it("should also work in a chunk", function(done) {
require.ensure([], function(require) {
var contextRequire = require.context(".");
contextRequire("./two").should.be.eql(2);
var tw = "tw";
require("." + "/" + tw + "o").should.be.eql(2);
done();
});
});
it("should be able to use a context with a loader", function() {
var abc = "abc", scr = "script.coffee";
require("../resources/" + scr).should.be.eql("coffee test");
require("raw!../resources/" + abc + ".txt").should.be.eql("abc");
});
it("should be able to require.resolve with automatical context", function() {
var template = "tmpl";
require.resolve("../templates/" + template).should.be.eql(require.resolve("../templates/tmpl"));
});
it("should resolve loaders relative to require", function() {
var index = "index", test = "test";
require("../loaders/queryloader?query!!!!../node_modules/subcontent/" + index + ".js").should.be.eql({
resourceQuery: null,
query: "?query",
prev: "module.exports = \"error\";"
});
require("!../loaders/queryloader?query!../node_modules/subcontent/" + test + ".jade").should.be.eql({
resourceQuery: null,
query: "?query",
prev: "xyz: abc"
});
});
});
describe("loaders", function() {
@ -428,73 +511,6 @@ describe("main", function() {
});
});
describe("AMD", function() {
it("should be able to use AMD-style require", function(done) {
var template = "tmpl";
require(["./circular", "../templates/" + template, true ? "./circular" : "./circular"], function(circular, testTemplate, circular2) {
circular.should.be.eql(1);
circular2.should.be.eql(1);
testTemplate.should.be.eql("test template");
done();
});
});
it("should be able to use require.js-style define", function(done) {
define("name", ["./circular"], function(circular) {
circular.should.be.eql(1);
done();
});
});
it("should be able to use require.js-style define, without name", function(done) {
define(["./circular"], function(circular) {
circular.should.be.eql(1);
done();
});
});
it("should be able to use require.js-style define, with empty dependencies", function(done) {
define("name", [], function() {
done();
});
});
it("should be able to use require.js-style define, without dependencies", function(done) {
define("name", function() {
done();
});
});
var obj = {};
it("should be able to use require.js-style define, with an object", function() {
define("blaaa", obj);
});
after(function() {
module.exports.should.be.equal(obj);
});
it("should offer AMD-style define for CommonJs", function(done) {
var _test_require = require.valueOf();
var _test_exports = exports;
var _test_module = module;
define(function(require, exports, module) {
(typeof require).should.be.eql("function");
require.valueOf().should.be.equal(_test_require);
exports.should.be.equal(_test_exports);
module.should.be.equal(_test_module);
require("./circular").should.be.eql(1);
done();
});
});
it("should not crash on require.js require only with array", function() {
require(["./circular"]);
});
it("should create a chunk for require.js require", function(done) {
var sameTick = true;
require(["./c"], function(c) {
sameTick.should.be.eql(false);
c.should.be.eql("c");
require("./d").should.be.eql("d");
done();
});
sameTick = false;
});
});
describe("cross module system", function() {
it("should answer typeof require correctly", function() {
(typeof require).should.be.eql("function");