blog.jakoblind.no

Code split vendors with webpack for faster load speed

We all want faster apps. Google rewards it. Our fellow devs are intrigued by it. And most importantly our users love it.

There are many ways to optimize the load speed. What we are going to focus on in this article is how to make the app quicker by splitting up the bundle into two. But why would that help?

Think about this: how many of your users access every page of your app and use every possible feature of your app? I guess few are. And yet the default behavior of webpack is to ship ONE bundle. All users are forced to download the whole app even though they rarely have use for it all. It’s a waste of downloaded data!

One way to get faster loading speed for our users is to split the bundle up into smaller pieces. Then users only have to load the code they actually want instead of the whole site. This technique is called code splitting.

There are many approaches to code splitting. Let’s start with the most simple code splitting technique: Code split vendors.

Code split vendors with webpack improve caching

By default, webpack spits out one bundle containing both the code you write and the dependencies you use from package.json

This bundle is cached by the browsers when the user accesses the app for the first time. The next time it’s much quicker to load because they get a cache hit.

But every time we change the code, or add a new dependency, or update the version of a dependency, the bundle is completely changed. The cached one is now old and has to be thrown away. The browser must now do a new fetch of the whole bundle.

With lean and agile practices, we want to ship often, preferably every day. Unfortunately, this means that our users get less advantage of the caching.

Code splitting vendors to the rescue!

What we want to do is to split the bundle up into two. One of the bundles containing the code that we write. And the other bundle containing only the dependencies in the package.json file.

How to code split vendors with webpack

Ok, now that we know we can improve caching with code splitting, let’s look into how we implement it with webpack.

What I like to do when learning a new concept is to start with a minimal project and play around there. You can create such a minimal webpack project and download it on createapp.dev.

First, let’s look into the naming of the output bundle. In a simple webpack project, your output section might look like this:

output: {
  filename: 'bundle.js',
  path: path.resolve(__dirname, 'dist'),
},

That means that the bundle will be written to the file bundle.js. But when we are going to create two webpack bundles, this configuration can no longer be used. Otherwise, we would have two bundles named bundle.js. And that is just not possible.

So we need to do a slight change to the config.

output: {
  filename: '[name].bundle.js',
  path: path.resolve(__dirname, 'dist'),
},

Now the generated bundle would be name.bundle.js where the name is the name of the bundle. We are now enabled to create as many bundles as we want!

Do the splitting with SplitChunksPlugin

So the next step is to split up the bundle and to do that we are going to use the SplitChunksPlugin. Why SplitChunks and not SplitBundle you might ask. What is a “chunk”?

Every file used in your project such as index.js, app.js, and react is called a module internally in webpack. A chunk is also an internal webpack concept. A chunk is a group of modules. Usually, chunks directly correspond with the output bundle. There are some configurations where this is true, but for now, you can think of a one-to-one mapping between a chunk and a bundle.

With the SplitChunksPlugin we can split up a chunk into smaller chunks. This plugin is pretty smart and out-of-the-box it will split chunks where the plugin thinks it makes sense. If you have a very large application it splits up your production code, and it also extracts the files in node_modules to a separate chunk (and bundle).

A simple config looks like this:

optimization: {
    splitChunks: {
      chunks: "all",
    },
  },

The SplitChunksPlugin is built into webpack, so you don’t need to import it explicitly. Everything under optimization.splitChunks is the configuration for SplitChunksPlugin.

This configuration says that all types of chunks will be applied to the plugin. There are two types of chunks: initial and non-initial, we are going to apply this plugin to both of them. Now if you look closely at the output of your build you’ll see the following (make sure you have some dependencies in the project you build for):

asset 935.bundle.js 127 KiB [emitted] [minimized] (id hint: vendors) 1 related asset
asset main.bundle.js 2.93 KiB [emitted] [minimized] (name: main)

Now SplitChunksPlugin is smart and will take creative liberty in what chunks it creates and what it names them by default. As you see in our example, it’s named 935. Also If you are running this on a large project, you’ll see that you might get several bundles with dependencies. You can get more fine-grained control over what kind of bundle this plugin spits out. Let’s configure the plugin to create one bundle and only one bundle for the dependencies. Also, let’s name it vendors.

optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  },

What is a cacheGroup? Yet another internal webpack concept. It’s another way to group modules internally. This configuration will create a separate bundle for all modules that have node_modules in the path, which all our dependencies have. The output is now like this:

asset vendors.bundle.js 127 KiB [emitted] [minimized] (name: vendors) (id hint: vendor) 1 related asset
asset main.bundle.js 2.93 KiB [emitted] [minimized] (name: main)

Now we have a separate bundle for our vendors! But one last thing before i let you off. It’s good practice to extract the webpack runtime into a separate bundle when doing code splitting. You do that by adding runtimeChunk: 'single', under optimization. If you’re curious why then you can read this article on multiple entry points that is referenced in the webpack docs. When you have added that configuration, your complete optimization configuration will then look like this:

  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  },

Ok, let’s wrap this up. We have now created a separate webpack bundle for our vendors. We have improved caching. This is a good start, you can tweak the SplitChunksPlugin in all eternity. Here are the docs for SplitChunksPlugin. I wish you the best!

Follow me on Twitter for real-time updates about webpack and other frontend-related topics!


tags: