|
| 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. |
0 commit comments