Optimizing Webpack build times and improving caching with DLL bundles

Webpack’s Dll and DllReference plugins are a way to split
a large JavaScript project into multiple bundles which
can be compiled independently. They can be used to optimize
build times (both full and incremental) and improve caching
for users by putting code which changes infrequently into
separate “library” bundles. The term ‘Dll’ is short for
Dynamically Linked Library which is a feature for native Windows
applications that solves the same problem.

For example, suppose you have an application built with
several large libraries or frameworks such as jQuery, Angular
or React which change relatively infrequently, plus some utility code such as underscore or lodash which you change very rarely. The Dll plugins enable you to
put the utility code in one bundle, the frameworks in another and your application
code in a separate one.

This has several benefits:

Your can compile the library bundles separately from the application bundle.
This means that during typical development, you would compile your library
bundles once and then run Webpack in --watch mode to recompile your application
bundles as you change your app. Since the application bundle is smaller,
the initial build will be faster and incremental builds will usually also
be quicker.

When your users load your app, they will only need to download
the bundles which have changed.

You can avoid cluttering up the configuration for your app bundle
with plugins/loaders etc. that are only needed for vendor bundles
or vice-versa. This helps with both build speed and maintainability.

For common versions of major libraries (eg. jQuery, Angular) you can also get
these benefits by consuming them from a CDN such as cdnjs using <script> tags and you should consider doing that.

The advantage of Webpack library bundles is that you can choose
how to assign modules to bundles and it works with libraries that
you consume from npm and require() from your app code.

Creating library bundles

To partition your code into library and application bundles, you will need to create two
Webpack configurations, one that creates the library bundle(s) and another that
creates the application bundle(s) that use code from the libraries.

In the configuration for your library bundle, add DllPlugin to the list of plugins.
DllPlugin does two things:

It exposes a require() function via a global variable on the page which
other bundles can use to require code from that bundle.

It generates a JSON manifest, which is a mapping between the file paths of
modules inside the bundle and the integer IDs that each module has been
assigned. This manifest is used by your application bundles to determine
whether a library bundle provides a particular module and if so, how
to access it from another bundle.

// vendor-bundles.webpack.config.js
var webpack = require('webpack')
module.exports = {
entry: {
// create two library bundles, one with jQuery and
// another with Angular and related libraries
'jquery': ['jquery'],
'angular': ['angular', 'angular-router', 'angular-sanitize']
},
output: {
filename: '[name].bundle.js',
path: 'dist/',
// The name of the global variable which the library's
// require() function will be assigned to
library: '[name]_lib',
},
plugins: [
new webpack.DllPlugin({
// The path to the manifest file which maps between
// modules included in a bundle and the internal IDs
// within that bundle
path: 'dist/[name]-manifest.json',
// The name of the global variable which the library's
// require function has been assigned to. This must match the
// output.library option above
name: '[name]_lib'
}),
],
}

After this is loaded, window.angular_lib(<id>) can be used by any
other code on the page to require code from that bundle. Since modules use integer IDs which are internal to the bundle, you also need
the JSON manifest in order to map from file path to ID.

Creating application bundles

In the configuration for your application bundle, you will add a DllRefrencePlugin
instance to the list of plugins, one per library that you want to consume.

DllReferencePlugin specifies the path of a manifest previously generated by
DllPlugin to search for modules.

When Webpack encounters a require() in your code, it will first check the
available DLLs to see if one provides that code. If it does, a stub module
containing a reference to that DLL will be generated. Otherwise, the actual
code of the required module will be included in the application bundle as normal.

Comparison with other code splitting methods

The DLL plugins are not the only code-splitting mechanism available
in Webpack. The other ones are:

CommonsChunkPlugin extracts out code that
is shared between multiple bundles and puts it into a separate
bundle. The advantage is that you don’t need to specify which
code to put in the shared bundle, Webpack can automatically
identify that.

You can force specific libraries into a ‘commons’ chunk,
and this is a good solution if you only have a moderate amount
of vendor code.

The advantage is that you only need one Webpack configuration.
The disadvantage for large projects is that the ‘commons’
chunk will be recompiled every time you run Webpack.

Code splitting via require.ensure() allows lazy-loading
of chunks of code as particular pages or features are used.
This is useful to optimize the initial page load time by keeping
the main bundle small and then pulling in code for lesser-used
features on-demand.