Note: Webpack could generate a different hash even if the bundle stays the same
– e.g. if you rename a file or compile the bundle under a different OS. This is
a bug, and there’s no clear solution yet. See the discussion on GitHub

If you need the
file name to send it to the client, use either the HtmlWebpackPlugin or the
WebpackManifestPlugin.

The HtmlWebpackPlugin is a
simple, but less flexible approach. During compilation, this plugin generates an
HTML file which includes all compiled resources. If your server logic isn’t
complex, then it should be enough for you:

The
WebpackManifestPlugin
is a more flexible approach which is useful if you have a complex server part.
During the build, it generates a JSON file with a mapping between file names
without hash and file names with hash. Use this JSON on the server to find out
which file to work with:

// manifest.json
{
"bundle.js": "bundle.8e0d62a03.js"
}

Further reading

Extract dependencies and runtime into a separate file

Dependencies

App dependencies tend to change less often than the actual app code. If you move
them into a separate file, the browser will be able to cache them separately –
and won’t re-download them each time the app code changes.

Key Term: In webpack terminology, separate files with the app code are called
chunks. We’ll use this name later.

This option enables smart code splitting. With it, webpack would extract the vendor code if
it gets larger than 30 kB (before minification and gzip). It would also extract the common code –
this is useful if your build produces several bundles (e.g.
if you split your app into routes).

// webpack.config.js (for webpack 3)
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
// A name of the chunk that will include the dependencies.
// This name is substituted in place of [name] from step 1
name: 'vendor',
// A function that determines which modules to include into this chunk
minChunks: module => module.context &&
module.context.includes('node_modules'),
}),
],
};

This plugin takes all modules which paths include node_modules and
moves them into a separate file called vendor.[chunkhash].js.

After these changes, each build will generate two files instead of one: main.[chunkhash].js and
vendor.[chunkhash].js (vendors~main.[chunkhash].js for webpack 4). In case of webpack 4,
the vendor bundle might not be generated if dependencies are small – and that’s fine:

This happens because the webpack bundle, apart from the code of modules, has a
runtime – a small piece of code
that manages the module execution. When you split the code into multiple files,
this piece of code starts including a mapping between chunk ids and
corresponding files:

Lazy-load code that you don’t need right now

If you load a video page on YouTube, you care more about the video than about
comments. Here, the video is more important than comments.

If you open an article on a news site, you care more about the text of the
article than about ads. Here, the text is more important than ads.

In such cases, improve the initial loading performance by downloading only the
most important stuff first, and lazy-loading the remaining parts later. Use the
import() function and
code-splitting for this:

Further reading

Split the code into routes and pages

If your app has multiple routes or pages, but there’s only a single JS file with
the code (a single main chunk), it’s likely that you’re serving extra bytes on
each request. For example, when a user visits a home page of your site:

they don’t need to load the code for rendering an article that’s on a different
page – but they will load it. Moreover, if the user always visits only the home
page, and you make a change in the article code, webpack will invalidate the
whole bundle – and the user will have to re-download the whole app.

If we split the app into pages (or routes, if it’s a single-page app), the user
will download only the relevant code. Plus, the browser will cache the app code
better: if you change the home page code, webpack will invalidate only the
corresponding chunk.

So, if only the article page uses Lodash, the home and the profile bundles
won’t include it – and the user won’t have to download this library when
visiting the home page.

Separate dependency trees have their drawbacks though. If two entry points use
Lodash, and you haven’t moved your dependencies into a vendor bundle, both entry
points will include a copy of Lodash. To solve this, in webpack 4, add the
optimization.splitChunks.chunks: 'all' option into your webpack config:

This option enables smart code splitting. With this option, webpack would automatically
look for common code and extract it into separate files.

Or, in webpack 3, use the CommonsChunkPlugin
– it will move common dependencies into a new specified file:

// webpack.config.js (for webpack 3)
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
// A name of the chunk that will include the common dependencies
name: 'common',
// The plugin will move a module into a common file
// only if it’s included into `minChunks` chunks
// (Note that the plugin analyzes all chunks, not only entries)
minChunks: 2, // 2 is the default value
}),
],
};

Feel free to play with the minChunks value to find the best one. Generally,
you want to keep it small, but increase if the number of chunks grows. For
example, for 3 chunks, minChunks might be 2, but for 30 chunks, it might be 8
– because if you keep it at 2, too many modules will get into the common file,
inflating it too much.

By default, IDs are calculated using a counter (i.e. the first module has ID 0,
the second one has ID 1, and so on). The problem with this is that when you add
a new module, it might appear in the middle of the module list, changing all the
next modules’ IDs:

This invalidates all chunks that include or depend on modules with changed IDs –
even if their actual code hasn’t changed. In our case, the 0 chunk (the chunk
with comments.js) and the main chunk (the chunk with the other app code) get
invalidated – whereas only the main one should’ve been.

To solve this, change how module IDs are calculated using the
HashedModuleIdsPlugin.
It replaces counter-based IDs with hashes of module paths: