Skip to content

Commit 722bc10

Browse files
committed
Merge caching articles
Closes webpack#2.
1 parent b548aea commit 722bc10

File tree

3 files changed

+257
-261
lines changed

3 files changed

+257
-261
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Fully completed can be removed from this list. If there's a topic you would like
4747
### `/how-to`
4848

4949
* Author libraries - ![](https://img.shields.io/badge/progress-5%25-yellowgreen.svg) - [#9](https://github.com/webpack/webpack.io/issues/9)
50-
* Cache - ![](https://img.shields.io/badge/progress-5%25-yellowgreen.svg) - [#2](https://github.com/webpack/webpack.io/issues/2)
50+
* Cache - ![](https://img.shields.io/badge/progress-100%25-yellowgreen.svg) - [#2](https://github.com/webpack/webpack.io/issues/2)
5151
* Develop - ![](https://img.shields.io/badge/progress-5%25-yellowgreen.svg) - [#26](https://github.com/webpack/webpack.io/issues/26)
5252
* Generate a production build - ![](https://img.shields.io/badge/progress-5%25-yellowgreen.svg) - [#11](https://github.com/webpack/webpack.io/issues/11)
5353
* Handle compatibility - ![](https://img.shields.io/badge/progress-5%25-yellowgreen.svg) - [#4](https://github.com/webpack/webpack.io/issues/4)

content/how-to/cache.md

Lines changed: 256 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,259 @@
11
---
22
title: How to Cache?
33
---
4-
> https://gist.github.com/sokra/ff1b0290282bfa2c037bdb6dcca1a7aa
5-
> [hash], [chunkhash], CommonsChunkPlugin, NamedModulesPlugin, HashedModuleIdsPlugin, records
4+
5+
To enable long-term caching of static resources produced by webpack:
6+
7+
1. Use `[chunkhash]` to add a content-dependent cache-buster to each file.
8+
1. Use compiler stats to get the file names when requiring resources in HTML.
9+
1. Generate the chunk-manifest JSON and inline it into the HTML page before loading resources.
10+
1. Ensure that the entry point chunk containing the bootstrapping code doesn’t change its hash over time for the same set of dependencies.
11+
12+
## The problem
13+
14+
Each time something needs to be updated in our code, it has to be re-deployed on the server and then re-downloaded by all clients. This is clearly very inefficient since fetching resources over the network can be slow. This is why browsers cache static resources. But the way it works has a pitfall: if we don’t change filenames of our resources when deploying a new version, browser might think it hasn’t been updated and client will get a cached version of it.
15+
16+
Probably the simplest way to tell the browser to download a newer version is to alter asset’s file name. In a pre-webpack era we used to add a build number to the filenames as a parameter and then increment it:
17+
18+
```
19+
application.js?build=1
20+
application.css?build=1
21+
```
22+
23+
It is even easier to do with webpack: each webpack build generates a unique hash which can be used to compose a filename. The following example config will generate 2 files (1 per entry point) with a hash in filenames:
24+
25+
```js
26+
// webpack.config.js
27+
module.exports = {
28+
entry: {
29+
vendor: './src/vendor.js',
30+
main: './src/index.js'
31+
},
32+
output: {
33+
path: path.join(__dirname, 'build'),
34+
filename: '[name].[hash].js'
35+
}
36+
};
37+
```
38+
39+
Running webpack with this config will produce the following output:
40+
41+
```bash
42+
Hash: 55e783391098c2496a8f
43+
Version: webpack 1.10.1
44+
Time: 58ms
45+
Asset Size Chunks Chunk Names
46+
main.55e783391098c2496a8f.js 1.43 kB 0 [emitted] main
47+
vendor.55e783391098c2496a8f.js 1.43 kB 1 [emitted] vendor
48+
[0] ./src/index.js 46 bytes {0} [built]
49+
[0] ./src/vendor.js 40 bytes {1} [built]
50+
```
51+
52+
But the problem here is that, *each* time we create a new build, all filenames will get altered and clients will have to re-download the whole application code again. So how can we guarantee that clients always get the latest versions of assets without re-downloading all of them?
53+
54+
## Generating unique hashes for each file
55+
56+
What if we could produce the same filename if the contents of the file did not change between builds? For example, the file where we put all our libraries and other vendor stuff does not change that often.
57+
58+
T> Separate your vendor and application code with [CommonsChunkPlugin](http://webpack.github.io/docs/list-of-plugins.html#2-explicit-vendor-chunk) and create an explicit vendor chunk to prevent it from changing too often.
59+
60+
Webpack allows you to generate hashes depending on the file contents. Here is the updated config:
61+
62+
```js
63+
// webpack.config.js
64+
module.exports = {
65+
...
66+
output: {
67+
...
68+
filename: '[name].[chunkhash].js'
69+
}
70+
};
71+
```
72+
73+
This config will also create 2 files, but in this case, each file will get its own unique hash.
74+
75+
```bash
76+
main.155567618f4367cd1cb8.js 1.43 kB 0 [emitted] main
77+
vendor.c2330c22cd2decb5da5a.js 1.43 kB 1 [emitted] vendor
78+
```
79+
80+
T> Don’t use [chunkhash] in development since this will increase compilation time. Separate development and production configs and use [name].js for development and [name].[chunkhash].js in production.
81+
82+
W> Due to this [issue in Webpack](https://github.com/webpack/webpack/issues/1315), this method of generating hashes still isn’t deterministic. To ensure hashes are generated based on the file contents, use [webpack-md5-hash plugin](https://github.com/erm0l0v/webpack-md5-hash). Here is an example how to integrate it into your project: https://github.com/okonet/webpack-long-term-cache-demo/pull/3/files
83+
84+
## Get filenames from webpack compilation stats
85+
86+
When working in development mode, you just reference a JavaScript file by entry point name in your HTML.
87+
88+
```html
89+
<script src="main.js"></script>
90+
```
91+
92+
Although, each time we build for production, we’ll get different file names. Something, that looks like this:
93+
94+
```html
95+
<script src="main.155567618f4367cd1cb8.js"></script>
96+
```
97+
98+
In order to reference a correct file in the HTML, we’ll need some information about our build. This can be extracted from webpack compilation stats by using this simple plugin:
99+
100+
```js
101+
// webpack.config.js
102+
module.exports = {
103+
...
104+
plugins: [
105+
function() {
106+
this.plugin("done", function(stats) {
107+
require("fs").writeFileSync(
108+
path.join(__dirname, "...", "stats.json"),
109+
JSON.stringify(stats.toJson()));
110+
});
111+
}
112+
]
113+
};
114+
```
115+
116+
Alternatively, just use one of these plugins to export JSON files:
117+
118+
* https://www.npmjs.com/package/webpack-manifest-plugin
119+
* https://www.npmjs.com/package/assets-webpack-plugin
120+
121+
A sample output of webpack-manifest-plugin for our config looks like:
122+
123+
```json
124+
{
125+
"main.js": "main.155567618f4367cd1cb8.js",
126+
"vendor.js": "vendor.c2330c22cd2decb5da5a.js"
127+
}
128+
```
129+
130+
The rest depends on your server setup. There is a nice [walk through for Rails-based projects](http://clarkdave.net/2015/01/how-to-use-webpack-with-rails/#including-precompiled-assets-in-views). Or, if your application doesn’t rely on any server-side rendering, it’s often enough to generate a single `index.html` file for your application. To do so, just use these 2 amazing plugins:
131+
132+
* https://github.com/ampedandwired/html-webpack-plugin
133+
* https://github.com/szrenwei/inline-manifest-webpack-plugin
134+
135+
It will simplify the setup dramatically.
136+
137+
We’re done, you might think. Well, almost.
138+
139+
## Deterministic hashes
140+
141+
To minimise the size of generated files, webpack uses identifiers instead of module names. During compilation identifiers are generated, mapped to chunk filenames and then put into a JavaScript object called *chunk manifest*. It is (along with some bootstrapping code) then placed into the entry chunk and it is crucial for webpack-packaged code to work.
142+
143+
The problem with this is the same as before: whenever we change any part of the code, it will, even if the rest of its contents wasn’t altered, update our entry chunk to include the new manifest. This, in turn, will lead to a new hash and dismiss the long-term caching.
144+
145+
To fix that, we should use [chunk-manifest-webpack-plugin](https://github.com/diurnalist/chunk-manifest-webpack-plugin) which will extract that manifest to a separate JSON file. Here is an updated webpack.config.js which will produce chunk-manifest.json in our build directory:
146+
147+
```js
148+
// webpack.config.js
149+
var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
150+
151+
module.exports = {
152+
// your config values here
153+
plugins: [
154+
new ChunkManifestPlugin({
155+
filename: "chunk-manifest.json",
156+
manifestVariable: "webpackManifest"
157+
})
158+
]
159+
};
160+
```
161+
162+
Since we removed the manifest from entry chunk, now it’s our responsibility to provide webpack with it. You have probably noticed the `manifestVariable` option in the example above. This is the name of the global variable where webpack will look for the manifest JSON and this is why *it should be defined before we require our bundle in HTML*. This is easy as inlining the contents of the JSON in HTML. Our HTML head section should look like this:
163+
164+
```html
165+
<html>
166+
<head>
167+
<script>
168+
//<![CDATA[
169+
window.webpackManifest = {"0":"main.3d038f325b02fdee5724.js","1":"1.c4116058de00860e5aa8.js"}
170+
//]]>
171+
</script>
172+
</head>
173+
<body>
174+
</body>
175+
</html>
176+
```
177+
178+
So the final webpack.config.js should look like this:
179+
180+
```js
181+
var path = require('path');
182+
var webpack = require('webpack');
183+
var ManifestPlugin = require('webpack-manifest-plugin');
184+
var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
185+
var WebpackMd5Hash = require('webpack-md5-hash');
186+
187+
module.exports = {
188+
entry: {
189+
vendor: './src/vendor.js',
190+
main: './src/index.js'
191+
},
192+
output: {
193+
path: path.join(__dirname, 'build'),
194+
filename: '[name].[chunkhash].js',
195+
chunkFilename: '[name].[chunkhash].js'
196+
},
197+
plugins: [
198+
new webpack.optimize.CommonsChunkPlugin({
199+
name: "vendor",
200+
minChunks: Infinity,
201+
}),
202+
new WebpackMd5Hash(),
203+
new ManifestPlugin(),
204+
new ChunkManifestPlugin({
205+
filename: "chunk-manifest.json",
206+
manifestVariable: "webpackManifest"
207+
}),
208+
new webpack.optimize.OccurenceOrderPlugin()
209+
]
210+
};
211+
212+
```
213+
214+
T> If you're using [webpack-html-plugin](https://github.com/ampedandwired/html-webpack-plugin), you can use [inline-manifest-webpack-plugin](https://github.com/szrenwei/inline-manifest-webpack-plugin) to do this.
215+
216+
Using this config the vendor chunk should not be changing its hash unless you change its code or dependencies. Here is a sample output for 2 runs with `moduleB.js` being changed between the runs:
217+
218+
```bash
219+
> webpack
220+
221+
Hash: 92670583f688a262fdad
222+
Version: webpack 1.10.1
223+
Time: 65ms
224+
225+
Asset Size Chunks Chunk Names
226+
chunk-manifest.json 68 bytes [emitted]
227+
vendor.6d107863983028982ef4.js 3.71 kB 0 [emitted] vendor
228+
1.c4116058de00860e5aa8.js 107 bytes 1 [emitted]
229+
main.5e17f4dff47bc1a007c0.js 373 bytes 2 [emitted] main
230+
231+
[0] ./src/index.js 186 bytes {2} [built]
232+
[0] ./src/vendor.js 40 bytes {0} [built]
233+
[1] ./src/moduleA.js 28 bytes {2} [built]
234+
[2] ./src/moduleB.js 28 bytes {1} [built]
235+
236+
> webpack
237+
238+
Hash: a9ee1d1e46a538469d7f
239+
Version: webpack 1.10.1
240+
Time: 67ms
241+
242+
Asset Size Chunks Chunk Names
243+
chunk-manifest.json 68 bytes [emitted]
244+
vendor.6d107863983028982ef4.js 3.71 kB 0 [emitted] vendor
245+
1.2883246944b1147092b1.js 107 bytes 1 [emitted]
246+
main.5e17f4dff47bc1a007c0.js 373 bytes 2 [emitted] main
247+
248+
[0] ./src/index.js 186 bytes {2} [built]
249+
[0] ./src/vendor.js 40 bytes {0} [built]
250+
[1] ./src/moduleA.js 28 bytes {2} [built]
251+
[2] ./src/moduleB.js 28 bytes {1} [built]
252+
```
253+
254+
Notice that vendor chunk has the same filename!
255+
256+
## References
257+
258+
* https://medium.com/@okonetchnikov/long-term-caching-of-static-assets-with-webpack-1ecb139adb95#.vtwnssps4
259+
* https://gist.github.com/sokra/ff1b0290282bfa2c037bdb6dcca1a7aa

0 commit comments

Comments
 (0)