Splitting Vendor and App Javascript Files with Webpack

It’s been a couple of weeks since I last messed with my Webpack configuration as I was learning Redux. I’ve noticed, however, that my code is growing ever larger. Part of that is the sheer number of libraries I have. It’s probably fairly common, but here is what I have:

If the webpack numbers are to be believed, this accounts for approximately 90% of the code base. The real kicker is that these rarely change – certainly not as much as my main codebase during development. Even when my application is in production, I can place these libraries on a CDN. The question is, of course, how do I build two JavaScript webpack bundles? I want one for the libraries I use and one for my code.

It turns out it is relatively simple.

Step 1: Update your webpack.config.js to support vendor bundles

Here is my new webpack.config.js with the appropriate lines highlighted:

'use strict';

/* global __dirname */
var config = require('config'),
    path = require('path'),
    webpack = require('webpack');

var jsxLoader = (config.get('env') === 'development') ? 'react-hot!babel' : 'babel';

var configuration = {
    devtool: 'source-map',
    entry: {
        app: [ path.join(__dirname, 'client/app.jsx') ],
        vendor: [
    module: {
        loaders: [
            // JavaScript and React JSX Files
            { test: /\.jsx?$/, loader: jsxLoader, exclude: /node_modules/ },
            { test: /\.jsx?$/, loader: 'eslint', exclude: /node_modules/ },
    output: {
        path: path.join(__dirname, 'public'),
        publicPath: '/',
        filename: 'grumpywizards.js'
    plugins: [
        new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
        new webpack.optimize.UglifyJsPlugin({ mangle: false, compress: { warnings: false }}),
        new webpack.NoErrorsPlugin(),
        new webpack.DefinePlugin({ 'process.env.NODE_ENV': `"${config.env}"` })
    resolve: {
        modulesDirectories: [ 'node_modules' ],
        extensions: [ '', '.js', '.jsx' ]
    target: 'web',

    // Loader options
    eslint: {
        failOnWarning: false,
        failOnError: true

if (config.env === 'development') {
    configuration.plugins.push(new webpack.HotModuleReplacementPlugin());

module.exports = configuration;

Lines 13-30 are the new entry point. The old entry was just an array which is equivalent to { app: <array> }. In addition to the app entrypoint, I’ve created a vendor entrypoint which has a list of libraries that I want to bundle into the vendor bundle file. Line 44 does the actual bundling of the vendor bundle – the app bundle is handled just like before.

Note that I had to change my usage of material-ui. I included specific sub-components like this before:

import { Card, CardHeader } from 'material-ui/lib/card';

Material-UI exports everything from the main library entrypoint as well as through sub-modules, so this import becomes:

import { Card, CardHeader } from 'material-ui'

It does mean that the entire material-ui library will be included in the vendor bundle, but the bundle is easier to maintain.

Finally, because we moved the app into an object, the update for the development environment needs to be adjusted to ensure the dev server and hot module loading middleware are added properly.

If you run webpack -p (or npm run build if you have it configured) then you will see two bundles created. The new bundle is vendor.bundle.js. Note the sizes of the two bundles. The vendor.bundle.js is probably a significant part of your app.

Step 2: Add the vendor bundle to your index.html

Now that I have a vendor bundle, I need to load it. Since my code is written in ES2015, I need to load it after the existing core-js library and before my app code:

<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Grumpy Wizards</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700"/>
    <link rel="stylesheet" href="//cdn.materialdesignicons.com/1.4.57/css/materialdesignicons.min.css"/>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">

    <div id="pageview"></div>

        window.GRUMPYWIZARDS = {
            env: '${config.env}',
            base: '${config.base}'
    <script src="//cdnjs.cloudflare.com/ajax/libs/core-js/2.0.2/core.min.js"></script>
    <script src="vendor.bundle.js"></script>
    <script src="grumpywizards.js"></script>


Since I’m using serve-static to serve the JavaScript bundles from my NodeJS server, the vendor.bundle.js file won’t be re-loaded unless I actually change it. This will make reloading quicker and will ultimately be better for my users as well.

As always, you can find the code on my GitHub Repository.