Vendor and code splitting in webpack 2

Bundling and code splitting

Let’s start by getting webpack to bundle an application, and see how we can tweak and optimize its default code splitting mechanism.

Preliminary setup

As with my prior posts, this will use my Booklist project for the code. See the repo’s readme for a fuller description, but essentially it’s a fully-functioning book-tracking web application I use to try out new tools.

Basic build

What would the simplest possible code look like that would bundle this application to something that could be executed?

  • entry — this is the entry point of the application. It’s where the application starts. It’s what pulls everything else in, and gets the application doing things. For me, that’s reactStartup.js.
  • output — this tells webpack where to create the resulting bundle. Again, you’ll need to create a script tag in your site to load this script.
  • resolve — this is some basic housekeeping. My simple-react-bootstrap has a main field in its package.json with un-transpiled files with .es6 extensions. I should probably fix this, since it confuses some tooling, but for now I’ll alias that away. And I’m also aliasing an old — not-on-npm — JavaScript color picker called jscolor.
  • resolve.modules — this tells webpack where to go searching to resolve import statements it finds while parsing the application. node_modules is of course for npm utilities, but more interesting is that I needed to add ./. This allows webpack to find modules with an absolute path. For example, rather than link to ../../applicationRoot/components/button I just link to applicationRoot/components/button. Unfortunately I was unable to find a single configuration item that would simply tell webpack “this is the base for all absolute paths.”
  • module.loaders — this sets up my Babel transpilation.

Improving the output

Let’s add the webpack-bundle-analyzer, like this

Breaking up our main bundle

As a crude, simple, easy win for making our main bundle smaller, I’ll pull out all npm modules with the CommonsChunkPlugin — see the plugins array at the bottom, in particular the minChunks function, in which I filter in everything that comes from node_modules.

Why are there still npm items in the code split bundles?

Notice that async chunks, like 0.bundle.js, still have things from node_modules hanging around. Why didn’t our prior bundle pick them up?

So how DO we use CommonsChunk with code split modules?

The CommonsChunk config has an async property. We’ll use that property to provide the name, which will cause webpack to only search through our code’s async split modules.

Refining some things

Rather than providing a manual filename for each CommonsChunk, webpack allows you to just specify a general pattern, which is then applied by name, automatically. It’ll look something like this.

Splitting out the webpack runtime

There’s a neat trick listed in the webpack docs here, whereby we can extract the webpack runtime, which contains references to all bundles and chunks anywhere in the build, into a separate bundle. The reason we might want to do this is because this code will change frequently, as anything in the app changes, and if our react code is residing in the same file, we might unnecessarily invalidate the cache of that file.

Be sure to actually load your static build files

Be sure to add script tags for each of these static build files, with the manifest file being listed first (the webpack runtime needs to load before anything else). Just add regular script tags to whatever htm file is the root of your SPA.

Where to, from here?

As it is, my static, shared node_modules bundle has every npm utility the main, initial bundle of my application needs. My code split bundles are all separate already, with anything that’s used in more than one place automatically pulled into a shared chunk. And I have react-dnd, and its dependencies pulled into its own async chunk.

Odds and ends

Here’s a smattering of tips I had to sort through, which may be helpful if you’re just starting out with webpack.

babelHelpers is undefined

I eventually switched babel from the es2015 Babel plugin, to es2015-rollup to make the most of my ES6 modules. This caused the babel-external-modules to automatically be applied, which presumed that a babelHelpers global variable existed, with various helpers like classCallback, etc. Follow the instructions here to create this file, and be sure to load it from a script tag (first) as well.

webpack-dev-server

Be sure to install it globally. Starting it from a local installation didn’t seem to work, no matter what I tried.

Bundling for production

Webpack ships with a nice -p flag that will flip the process.env.NODE_ENV flag to 'production' (libraries like React use this to produce a production build), as well as minify for you.

Remember, the webpack.config file is “just JavaScript”

If you’re wondering how to do something in the webpack.config file, remember, it’s just JavaScript. If you want that BundleAnalyzePlugin to run only when not doing a production build, there’s nothing stopping you from just throwing a ternary operator in the array of plugins, and then filtering out the possible null at the end.

Final Webpack Config

Here’s my final webpack config. It’ll probably change some more — check out webpack.config.js in the react-redux directory of the github repo to see the absolute latest.

Conclusions

I’ll end by briefly describing what this application’s build was like prior to webpack. I was using SystemJS to load my scripts on demand, and I was using its related bundler for production deployment. While ostensibly more convenient, SystemJS as a loader meant that I could only use npm utilities by manually setting up paths to UMD builds, which were often far, far larger than needed. As to the manual building, it was an immense pain for even an application as small and simple as my booklist. Here’s what it looked like just prior to switching to webpack

Did I miss anything?

If you know of a better way to do any of this, please either leave a comment, or send me a tweet.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store