- Notifications
You must be signed in to change notification settings - Fork128
code splitting
For big web apps it's not efficient to put all code into a single file, especially if some blocks of code are only required under some circumstances.Webpack has a feature to split your codebase into "chunks" which are loaded on demand. Some other bundlers call them "layers", "rollups", or "fragments". This feature is called "code splitting".
It's an opt-in feature. You can define split points in your code base. Webpack takes care of the dependencies, output files and runtime stuff.
To clarify a common misunderstanding: Code Splitting is not just about extracting common code into a shared chunk. The more notable feature is that Code Splitting can be used to split code into anon demand loaded chunk. This can keep the initial download small and downloads code on demand when requested by the application.
AMD and CommonJs specify different methods to load code on demand. Both are supported and act as split points:
require.ensure(dependencies,callback)
Therequire.ensure
method ensures that every dependency independencies
can be synchronously required when calling thecallback
. An implementation of the require function is sent as a parameter to the callback.
Example:
require.ensure(["module-a","module-b"],function(){vara=require("module-a");// ...});
Note:require.ensure
only loads the modules, it doesn't evaluate them.
The AMD spec defines an asynchronousrequire
method with this definition:
require(dependencies,callback)
When called, alldependencies
are loaded and thecallback
is called with the exports of the loadeddependencies
.
Example:
require(["module-a","module-b"],function(a,b){// ...});
Note: AMDrequire
loads and evaluate the modules. In webpack modules are evaluated left to right.
Note: It's allowed to omit the callback.
tl;dr: Webpack doesn't support ES6 modules; userequire.ensure
orrequire
directly depending on which module format your transpiler creates.
Webpack1.x.x
(coming in2.0.0
!) does not natively support or understand ES6 modules. However, you can get around that by using a transpiler, like Babel, to turn the ES6import
syntax into CommonJs or AMD modules. This approach is effective but has one important caveat for dynamic loading.
The modulesyntax addition (import x from 'foo'
) is intentionally designed to bestatically analyzable, which means that you cannot do dynamic imports.
// INVALID!!!!!!!!!['lodash','backbone'].forEach(name=>importname)
Luckily, there is a JavaScript API "loader" specification being written to handle the dynamic use case:System.load
(orSystem.import
). This API will be the native equivalent to the aboverequire
variations. However,most transpilers do not support convertingSystem.load
calls torequire.ensure
so you have to do that directly if you want to make use of dynamic code splitting.
//static importsimport_from'lodash'// dynamic importsrequire.ensure([],function(){letcontacts=require('./contacts')})
All dependencies at a split point go into a new chunk. Dependencies are also recursively added.
If you pass a function expression (or bound function expression) as callback to the split point, webpack automatically puts all dependencies required in this function expression into the chunk too.
If two chunks contain the same modules, they are merged into one. This can cause chunks to have multiple parents.
If a module is available in all parents of a chunk, it's removed from that chunk.
If a chunk contains all modules of another chunk, this is stored. It fulfills multiple chunks.
Depending on the configuration optiontarget
a runtime logic for chunk loading is added to the bundle. I. e. for theweb
target chunks are loaded via jsonp. A chunk is only loaded once and parallel requests are merged into one. The runtime checks for loaded chunks whether they fulfill multiple chunks.
An entry chunk contains the runtime plus a bunch of modules. If the chunk contains the module0
the runtime executes it. If not, it waits for chunks that contains the module0
and executes it (every time when there is a chunk with a module0
).
A normal chunk contains no runtime. It only contains a bunch of modules. The structure depends on the chunk loading algorithm. I. e. for jsonp the modules are wrapped in a jsonp callback function. The chunk also contains a list of chunk id that it fulfills.
An initial chunk is a normal chunk. The only difference is that optimization treats it as more important because it counts toward the initial loading time (like entry chunks). That chunk type can occur in combination with theCommonsChunkPlugin
.
To split your app into 2 files, sayapp.js
andvendor.js
, you canrequire
the vendor files invendor.js
. Then pass this name to theCommonsChunkPlugin
as shown below.
varwebpack=require("webpack");module.exports={entry:{app:"./app.js",vendor:["jquery","underscore", ...],},output:{filename:"bundle.js"},plugins:[newwebpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor",/* filename= */"vendor.bundle.js")]};
This will remove all modules in thevendor
chunk from theapp
chunk. Thebundle.js
will now contain just your app code, without any of its dependencies. These are invendor.bundle.js
.
In your HTML page loadvendor.bundle.js
beforebundle.js
.
<scriptsrc="vendor.bundle.js"></script><scriptsrc="bundle.js"></script>
It's possible toconfigure multiple entry points that will result in multiple entry chunks. The entry chunk contains the runtime and there must only be one runtime on a page (there are exceptions).
With theCommonsChunkPlugin
the runtime is moved to the commons chunk. The entry points are now in initial chunks. While only one initial chunk can be loaded, multiple entry chunks can be loaded. This exposes the possibility to run multiple entry points in a single page.
Example:
varwebpack=require("webpack");module.exports={entry:{a:"./a",b:"./b"},output:{filename:"[name].js"},plugins:[newwebpack.optimize.CommonsChunkPlugin("init.js")]}
<scriptsrc="init.js"></script><scriptsrc="a.js"></script><scriptsrc="b.js"></script>
TheCommonsChunkPlugin
can move modules that occur in multiple entry chunks to a new entry chunk (the commons chunk). The runtime is moved to the commons chunk too. This means the old entry chunks are initial chunks now. See all options in thelist of plugins.
There are optimizing plugins that can merge chunks depending on specific criteria. Seelist of plugins.
LimitChunkCountPlugin
MinChunkSizePlugin
AggressiveMergingPlugin
Therequire.ensure
function accepts an additional 3rd parameter. This must be a string. If two split point pass the same string they use the same chunk.
require.include(request)
require.include
is a webpack specific function that adds a module to the current chunk, but doesn't evaluate it (The statement is removed from the bundle).
Example:
require.ensure(["./file"],function(){require("./file2");});// is equal torequire.ensure([],function(){require.include("./file");require("./file2");});
require.include
can be useful if a module is in multiple child chunks. Arequire.include
in the parent would include the module and the instances of the modules in the child chunks would disappear.
- Simple
- with bundle-loader
- with context
- with amd and context
- named-chunks
- multiple entry chunks
- multiple commons chunks
For a running demo see theexample-app. Check Network in DevTools.
webpack 👍