Skip to content

Commit db78115

Browse files
authored
Merge pull request #271 from kalcifer/feat/code-splitting
How to split code? Closes #3.
2 parents d1d05c1 + 2d59b6d commit db78115

File tree

4 files changed

+354
-8
lines changed

4 files changed

+354
-8
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
title: How to Split CSS?
3+
contributors:
4+
- pksjce
5+
---
6+
7+
In webpack, when you use the css-loader and import CSS into your JavaScript files, the CSS is bundled along with your Javascript.
8+
This has the disadvantage that, you will not be able to utilize the browser's ability to load CSS asynchronously and parallel. Instead, your page will have to wait until your whole JavaScript bundle is loaded, to style itself.
9+
webpack can help with this problem by bundling the CSS separately using [extract-text-webpack-plugin](https://github.com/webpack/extract-text-webpack-plugin) and the [css-loader](https://github.com/webpack/css-loader).
10+
11+
## Using `css-loader`
12+
13+
To import css into your JavaScript code like [any other module](concept/modules), you will have to use the [css-loader](https://github.com/webpack/css-loader)
14+
The webpack config with `css-loader` will look like
15+
16+
```javascript
17+
//webpack.config.js
18+
19+
modules.exports = function(env){
20+
entry: '..',
21+
...
22+
module: {
23+
loaders: [{
24+
test: /\.css$/,
25+
exclude: /node_modules/,
26+
loader: 'css-loader'
27+
}]
28+
}
29+
...
30+
}
31+
```
32+
33+
## Using `extract-text-webpack-plugin` - ExtractTextPlugin
34+
35+
Install this plugin as follows
36+
```
37+
npm i --save-dev extract-text-webpack-plugin
38+
```
39+
40+
To use this `ExtractTextPlugin`, it needs to be added to the `webpack.config.js` file in two steps.
41+
### In the loader
42+
43+
Adapting from the previous example with the `css-loader`, we should add `ExtractTextPlugin` as follows
44+
45+
```javascript
46+
...
47+
loader: ExtractTextPlugin.extract('css-loader?sourceMap') //Can be used without sourcemaps too.
48+
...
49+
```
50+
51+
### In the plugin
52+
53+
```javascript
54+
new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true })
55+
```
56+
57+
With above two steps, you can generate a new bundle specifically for all the CSS modules and add them as a separate tag in the `index.html`
58+
For more info on how to use the api please go to [`ExtractTextPlugin` api](https://github.com/webpack/extract-text-webpack-plugin#api).
59+
60+
The full config for splitting css with `ExtractTextPlugin` is as follows
61+
62+
```javascript
63+
var ExtractTextPlugin = require('extract-text-webpack-plugin');
64+
module.exports = function () {
65+
return {
66+
entry: './main.js',
67+
output: {
68+
path: './dist',
69+
filename: 'bundle.js'
70+
},
71+
module: {
72+
loaders: [{
73+
test: /\.css$/,
74+
exclude: /node_modules/,
75+
loader: Extract.extract({
76+
loader: 'css-loader?sourceMap'
77+
})
78+
}]
79+
},
80+
devtool: 'source-map',
81+
plugins: [
82+
new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true })
83+
]
84+
}
85+
}
86+
```
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
title: How to Split Using require.ensure()?
3+
contributors:
4+
- pksjce
5+
---
6+
7+
In this section, we will discuss how webpack splits code using `require.ensure()`.
8+
9+
## `require.ensure()`
10+
11+
webpack statically parses for `require.ensure()` in the code while building and adds the modules here into a separate chunk. This new chunk is loaded on demand by webpack through jsonp.
12+
13+
The syntax is as follows
14+
15+
```javascript
16+
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
17+
```
18+
19+
#### dependencies
20+
This is an array of strings where we can declare all the modules that need to be made available before all the code in the callback function can be executed.
21+
22+
#### callback
23+
This is the callback function that webpack will execute once the dependencies are loaded. An implementation of the require object is sent as a parameter to this function. This is so that, we can further `require()` the dependencies and any other modules for execution.
24+
25+
#### chunkName
26+
The chunkName is the name given to the chunk created by this particular `require.ensure()`. By giving the same name at various different split points of `require.ensure()`, we can make sure all the dependencies are collectively put in the same bundle.
27+
28+
Let us consider the following project
29+
30+
```bash
31+
\\ file structure
32+
|
33+
js --|
34+
| |-- entry.js
35+
| |-- a.js
36+
| |-- b.js
37+
webpack.config.js
38+
|
39+
dist
40+
```
41+
42+
```javascript
43+
\\ entry.js
44+
45+
require('a')
46+
require.ensure([], function(require){
47+
require('b')
48+
})
49+
50+
\\ a.js
51+
console.log('***** I AM a *****')
52+
53+
\\ b.js
54+
console.log('***** I AM b *****')
55+
```
56+
57+
```javascript
58+
\\ webpack.config.js
59+
60+
module.exports = function(env) {
61+
return {
62+
entry: './js/entry.js',
63+
output: {
64+
filename: 'bundle.js',
65+
path: './dist'
66+
}
67+
}
68+
}
69+
```
70+
On running webpack on this project, we find that webpack has created two new bundles, `bundle.js` and `0.bundle.js`.
71+
72+
`entry.js` and `a.js` are bundled in `bundle.js`.
73+
74+
`b.js` is bundled in `0.bundle.js`.
75+
76+
## Gotchas for `require.ensure()`
77+
78+
### Empty Array as Parameter
79+
80+
```javascript
81+
require.ensure([], function(require){
82+
require('./a.js')
83+
})
84+
```
85+
86+
The above code ensures that a split point is created and `a.js` is bundled separately by webpack.
87+
88+
### Dependencies as Parameter
89+
90+
```javascript
91+
require.ensure(['./a.js'], function(require) {
92+
require('./b.js');
93+
})
94+
```
95+
96+
In the above code, `a.js` and `b.js` are bundled together and split from the main bundle. But only the contents of `b.js` are executed. The contents of `a.js` are only made available and not executed.
97+
To execute `a.js`, we will have to require it in a sync manner like `require('./a.js')` for the JavaScript to get executed.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
title: How to Split Vendor Files?
3+
contributors:
4+
- pksjce
5+
---
6+
7+
A typical application uses third party libraries for framework/functionality needs. Particular versions of these libraries are used and code here does not change often. However, the application code changes frequently.
8+
9+
Bundling application code with third party code would be inefficient. This is because the browser can cache asset files based on the cache header and files can be cached without needing to call the cdn again if it's contents don't change. To take advantage of this, we want to keep the hash of the vendor files constant regardless of application code changes.
10+
11+
We can do this only when we separate the bundles for vendor and application code.
12+
13+
Let's consider a sample application, that uses [momentjs](https://www.npmjs.com/package/moment) which is a time formatting library commonly used.
14+
15+
Install `moment` as follows in your application directory.
16+
17+
`npm install --save moment`
18+
19+
The index file will require `moment` as a dependency and log the current date as follows
20+
21+
__index.js__
22+
```javascript
23+
24+
var moment = require('moment');
25+
console.log(moment().format())
26+
27+
```
28+
29+
We can bundle the application with webpack using the following config
30+
31+
__webpack.config.js__
32+
33+
```javascript
34+
35+
modules.export = function(env) {
36+
return {
37+
entry: './index.js',
38+
output: {
39+
filename: '[chunkhash].[name].js`,
40+
path: './dist'
41+
}
42+
}
43+
}
44+
```
45+
46+
On running `webpack` in your application, if you inspect the resulting bundle, you will see that `moment` and `index.js` have been bundled in `bundle.js`.
47+
48+
This is not ideal for the application. If the code in `index.js` changes, then the whole bundle is rebuilt. The browser will have to load a new copy of the new bundle even though most of it hasn't changed at all.
49+
50+
## Multiple Entries
51+
52+
Let's try to mitigate this by adding a separate entry point for `moment` and name it `vendor`
53+
54+
__webpack.config.js__
55+
56+
```javascript
57+
58+
modules.export = function(env) {
59+
return {
60+
entry: {
61+
main: './index.js',
62+
vendor: 'moment'
63+
},
64+
output: {
65+
filename: '[chunkhash].[name].js`,
66+
path: './dist'
67+
}
68+
}
69+
}
70+
```
71+
72+
On running `webpack` now, we see that two bundles have been created. If you inspect these though, you will find that the code for `moment` is present in both the files!
73+
74+
It is for this reason, that we will need to use the [CommonsChunkPlugin](/configuration/plugins).
75+
76+
## `CommonsChunkPlugin`
77+
78+
This is a pretty complex plugin. It fundamentally allows us to extract all the common modules from different bundles and add them to the common bundle. If a common bundle does not exist, then it creates a new one.
79+
80+
We can modify our webpack config file to use the `CommonsChunkPlugin` as follows
81+
82+
__webpack.config.js__
83+
84+
```javascript
85+
86+
var webpack = require('webpack');
87+
modules.export = function(env) {
88+
return {
89+
entry: {
90+
main: './index.js',
91+
vendor: 'moment'
92+
},
93+
output: {
94+
filename: '[chunkhash].[name].js`,
95+
path: './dist'
96+
},
97+
plugins: [
98+
new webpack.optimize.CommonsChunkPlugin({
99+
name: 'vendor' // Specify the common bundle's name.
100+
})
101+
]
102+
}
103+
}
104+
```
105+
Now run `webpack` on your application. Bundle inspection shows that `moment` code is present only in the vendor bundle.
106+
107+
## Manifest File
108+
109+
But, if we change application code and run `webpack` again, we see that the hash for the vendor file changes. Even though we achieved separate bundles for `vendor` and `main` bundles, we see that the `vendor` bundle changes when the application code changes.
110+
This means that we still don't reap the benefits of browser caching because the hash for vendor file changes on every build and the browser will have to reload the file.
111+
112+
The issue here is that on every build, webpack generates some webpack runtime code, which helps webpack do it's job. When there is a single bundle, the runtime code resides in it. But when multiple bundles are generated, the runtime code is extracted into the common module, here the `vendor` file.
113+
114+
To prevent this, we need extract out the runtime into a separate manifest file. Even though we are creating another bundle, the overhead is offset by the long term caching benefits that we obtain on the `vendor` file.
115+
116+
__webpack.config.js__
117+
118+
```javascript
119+
120+
var webpack = require('webpack');
121+
modules.export = function(env) {
122+
return {
123+
entry: {
124+
main: './index.js',
125+
vendor: 'moment'
126+
},
127+
output: {
128+
filename: '[chunkhash].[name].js`,
129+
path: './dist'
130+
},
131+
plugins: [
132+
new webpack.optimize.CommonsChunkPlugin({
133+
names: ['vendor', 'manifest'] // Specify the common bundle's name.
134+
})
135+
]
136+
}
137+
}
138+
```
139+
140+
With the above webpack config, we see three bundles being generated. `vendor`, `main` and `manifest` bundles.

content/how-to/split-code.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
---
22
title: How to Split Code?
3+
contributors:
4+
- pksjce
35
---
4-
> what is code splitting
5-
> System.import
6-
> dynamic System.import
7-
> require.ensure
8-
> AMD require
9-
10-
> see also [[Output]]
11-
> see also [[dynamic dependencies]]
6+
7+
Code splitting is the most compelling feature for `webpack` usage. You can split your code into various bundles and load them on demand with`webpack`. It allows to tweak an application for these optimisations using the configuration.
8+
You can load them at a later time in your code or in a specific route only or on an event from the user even.
9+
10+
There are mainly two kind of code-splits that need to be accomplished with `webpack`
11+
12+
## On demand code-splitting
13+
14+
`webpack` can help us split our code into logical pieces or chunks as per our application routes or as per predicted user behaviour. This means that we can load non-essential assets when the user performs an action like route change and demands for it.
15+
16+
### Code splitting with `require.ensure()`
17+
18+
`require.ensure()` is the CommonJS way of including assets asynchronously. By adding `require.ensure([<fileurl>])`, we can define a split point in the code. webpack can then create a separate bundle of all the code inside this split point.
19+
Learn [how to split your code using `require.ensure()`](/how-to/code-splitting/splitting-require)
20+
21+
?> Document `System.import()`
22+
23+
## Resource splitting for cacheing and parallel loads
24+
25+
### CSS splitting
26+
27+
An application owner would want to split all the css into a separate bundle. This enhances cacheability of the resource bundle and also allows the browser to parallely load the bundle which makes for a solid performance improvement.
28+
Learn [how to split your css using Extract-Text-Webpack-Plugin](/how-to/code-splitting/splitting-css)
29+
30+
### Vendor code splitting
31+
32+
A typical application uses third party libraries for framework/functionality needs. Particular versions of these libraries are used and code here does not change often. However, the application code changes frequently. Bundling application code with third party code would be inefficient. This is because the browser can cache asset files based on the cache header. To take advantage of this, we want to keep the hash of the vendor files constant regardless of application code changes. We can do this only when we separate the bundles for vendor and application code.
33+
34+
Learn [how to split your vendor code using CommonsChunkPlugin](/how-to/code-splitting/splitting-vendor)

0 commit comments

Comments
 (0)