Let's first look at how bundle splitting might help a traditional webapp. Typically most webapps will have two different categories of code:
With a single-file asset bundle, when you change something in your application code, users must re-download all vendor code as well. For example, the React library didn't change, and likely won't for months, why make them re-download that? That's where bundle splitting comes in. If we can partition our asset files by those boundaries, we could cache our vendor code and only ship a new version of our application code (for most releases):
So how do we tell Webpack/Webpacker to make this happen? First we have to grok Webpacker, which provides a layer of abstraction on top of Webpack. I hear you, fellow developer, you say, “Webpack is already confusing enough, and we’re going to add more abstraction?” And to that I say, “You’re not wrong.” But hang in there, we'll step through it piece by piece so you know exactly what is going on.
To control bundle splitting, Webpack offers the SplitChunksPlugin. The documentation for that plugin is pretty good over at the Webpack docs site. I encourage you to double check all the settings that I propose in this article for yourself. The configuration we need to add is to the `optimization` section of the Webpack configuration object. But how do we do that in Webpacker?
Something that I’ve found useful when configuring Webpacker, is to start by looking at the out-of-the-box configuration. Webpacker sets up a lot for you right after you run `rake webpacker:install`, but much of it hides inside the library's source code. A great place to start is by looking at package/environments/base.js in the `@rails/webpacker` npm package.. In there, we find the splitChunks function:
How it works:
As Webpack compiles your asset bundle, it tests every file’s path against the regular expression that you provide. Files that pass go into a new file named `vendor`. All other code goes into your main bundle file like normal. Great! We’re almost there. Now let’s figure out how to configure Webpacker to make this happen.
We can use the `splitChunks` function that was shown in the webpacker source snippet. If you parse through the source, you’ll see that you can pass a callback function that’s merged with the overall Webpack configuration hash. That means that you can add this configuration to your webpacker config/webpack/environment.js file (that you control) and you will be good to go:
In the sample above, I’ve listed a couple of extra properties that work well for us and I’d encourage you to read up on them in the Webpack docs. With that, you’re 90% of the way there.
The good news is that when we get onto Webpack 5 this will all get substantially simpler: there’s a new moduleIds: ‘deterministic’ setting that was created to address this exact problem.
With that in mind, your final webpack splitChunks config might look something like this:
And that’s it! You should be able to change code in one bundle and verify that the other bundle is served from your browser cache. Be sure to correctly configure asset caching headers too!
As mentioned earlier, this approach led to a 50% reduction in asset download size for returning users to our application after a release (assuming library code did not change). Let’s recap the steps: