This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Grouping vendor files with the Webpack CommonsChunkPlugin

5:36 Tools lesson by

Often, you have dependencies which you rarely change. In these cases, you can leverage the CommonsChunkPlugin to automatically put these modules in a separate bundled file so they can be cached and loaded separately from the rest of your code (leveraging the browser cache much more effectively).

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Often, you have dependencies which you rarely change. In these cases, you can leverage the CommonsChunkPlugin to automatically put these modules in a separate bundled file so they can be cached and loaded separately from the rest of your code (leveraging the browser cache much more effectively).

Avatar
Marc

Realistically, the vendors files will change from time to time, so I expect hashing should be used there too.

In reply to egghead.io
Avatar
Rob

Would it be a bad idea to set it up so that the strings inside of the vendor array are dynamically generated by parsing the package.json's dependencies and grabbing the keys? That way all vendor dependencies are automatically put in the vendor bundle?

In reply to egghead.io
Avatar
Francisco

I believe there is a spelling error when you added a entry on webpack config file. You wrote "vender" instead of "vendor"

Avatar
hipertracker

How to use it together with HtmlWebpackPlugin and chunkhash? In my settings index.html has injected only vendor file

const webpack = require('webpack')
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

//const isProd = process.env.NODE_ENV === 'production'
const isTest = process.env.NODE_ENV === 'test'

module.exports = env => {
  return {
    entry: {
      app: './js/app.js',
      vendor: ['lodash', 'jquery'],
    },
    output: {
      filename: 'bundle.[name].[chunkhash].js',
      path: resolve(__dirname, 'priv/static'),
      pathinfo: true, //!env.prod,
    },
    context: resolve(__dirname, 'web/static'),
    devtool: env.prod ? 'source-map' : 'eval',
    bail: env.prod,
    module: {
      loaders: [
        {test: /\.js$/, loader: 'babel!eslint', exclude: /node_modules/},
        {test: /\.css$/, loader: 'style!css'},
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: './index.html'
      }),
      isTest ? undefined : new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor',
      })
    ].filter(p => !!p),
  }
}
Avatar
hipertracker

Found solution a minute later :) I don't need to add [chunkhash] to output filename. I need to add hash: true to HtmlWebpackPlugin.

// ...
    output: {
      filename: 'bundle.[name].js',
      path: resolve(__dirname, 'priv/static'),
      pathinfo: !env.prod,
    },
    // ...
    plugins: [
      new HtmlWebpackPlugin({
        template: './index.html',
        hash: true
      }),

  }
}
In reply to hipertracker
Avatar
Kent C.

Good catch Francisco! I looked into it and wrote you (and several others) an explanation of my blunder!

In reply to Francisco
Avatar
Kent C.

Absolutely, and I mention this in another lesson. I was going for isolated lessons in this course, for more info, see this blogpost.

In reply to Marc
Avatar
Kent C.

Not a terrible idea, though you'd probably want to measure the effect. It really just depends on your specific situation, but dynamically creating the vendor bundle isn't a bad idea IMO :-)

In reply to Rob
Avatar
Kent C.

Thanks for sharing!

In reply to hipertracker
Avatar
Rajeev

I have a ApiCaller js module which generate calls to our api server for data. It has const field APIURL which points to server url. This APIURL const changes for dev and prod environments. So when I need to deploy to dev I need to change that url and vice-versa.

I want these configuration parameters outside the code and during build process I change them dynamically so that we can build with different settings.
Is that possible in webpack?

In reply to egghead.io
Avatar
Kent C.

This is definitely possible. I know that many people do this kind of thing. There are a few approaches. I recommend you formulate a bigger example of your use case in the form of a Stack Overflow question. You'll likely get some good recommendations.

In reply to Rajeev
Avatar
Dean

Hey - when looking at your git, I am curious about the following.

In your app.js file, you reference jquery.
const $ = ....

But in your seperate files, you are brining in lodash via method incantation.

Why not also just include it in your app.js like you did with jquery?

const $ = ...
const _ =

I ask because i want to make sure I understand the thought process and I don't implement inappropriate things. I would have included it in the app.js file. Let me know - thanks!

Avatar
Kent C.

I'm not sure where you're talking about to be honest. But normally yes, you'll want to explicitly require/import dependencies. I rarely (hardly ever/never) reference global variables in my apps these days, even for major libraries/frameworks.

In reply to Dean
Avatar
Dean

Hey hipertracker, what does your index.html file look like? I am confused as to the implementation using HtmlWebpackPlugin.

In reply to hipertracker

Here's our running application. Let's take a look at the size of the bundle. If we refresh, we'll see that we're getting the bundle JS file and it's a whopping 1.1 megabytes for this small application. Most of this is because of jQuery and lodash which we've added to the project.

Because we won't be changing these dependencies very often, we can maximize our use of the browser cache by splitting these dependencies out into their own chunk and loading them separate from our application code which does change frequently. To do this, we're going to utilize a plugin built into webpack called Commons Chunk Plugin.

First we need to have an entirely separate entry for the vendor files that we want in this extra bundle. We're going to update our entry to have an app which is the original app that we have and then a vendor which is an array of the vendor modules that we want to have in this bundle. In our case, that will be a lodash and jQuery.

If we go ahead and run the build, we'll see that we get an error, and that's because we now have two entry files that are being output to the exact same file. We can see that here.

We are going to use webpack's interpolation shorthand to make these unique. We'll reference the name of the entry by entering bundle.name. It's notable that you can use the same shorthand in other properties of the output object, but we'll just go ahead and use the file name.

If we run the build, we'll see that it generates two files for us, one, that's bundled to app.js and another that's bundled at vendor.js following the name of our entry that we've given it.

If we try to run the server, we're going to see that our application has a little bit of a hard time loading our bundle, and that's because now the bundle for our application is called bundle.app.js. Let's go ahead and update the index HTML so that it uses the bundle.app.js. Let's go down here, bundle.app.

We'll restart our server and now our application is working. You'll see that the bundle.app.js is still 1.1 megabytes. We haven't totally finished to splitting out our vendor code from our application code. The Commons Chunk Plugin is built into webpack so we can require webpack here, and then we'll add a new property to our configuration called plugins where we'll reference a new webpack optimize Commons Chunk Plugin.

This will accept an object, and we can provide several options to the plugin. For our scenario, we can simply provide the name of the entry that we want for the track, so we'll specify vendors here. We'll save that, restart our server and if we refresh, we're now going to have a problem.

We get this webpack JSONP is not defined and that's because lodash and jQuery are in a separate file entirely. You'll notice that the bundle.app.js file is much smaller now, and we need to add the bundle.vendor.js file to our index HTML so that we can get lodash and jQuery for our application to work.

Let's go ahead and do that now. We'll open our index HTML, scroll down here and add our bundle.vendor.js file. We can restart the server and refresh our browser and the application's working. We have the bundle.vendor.js and the bundle.app.js files, the vendor file being really big and the app file being quite small.

The vendor file will be cached long term and the app file will be updated anytime that we update our code. I wish I could say we were done, but we're not. If we run the npm t for npm test or npm run test, we'll see that we setting the node environment to test and then running comma start. Comma's going to have a bit of a hard time here, and that's because it's this exact same error webpack JSONP is not defined.

For our test, we don't really care that the Commons Chunk Plugin is used so we're going to conditionally use this plugin by determining whether we're in a test environment. As you saw earlier, we're specifying that the node environment is test when we're running our test. Just like we're doing with this is prod variable, we could just as easily create a test variable and check whether the node environment is equal to test.

We'll just have a ternary operator here is test and if we are in test mode, then we'll just say this is undefined. Otherwise, we'll add that new webpack optimized Commons Chunk Plugin. Unfortunately, undefined is not a valid plugin, and so we're going to filter out any undefined plugins with the filter method where we'll take the plugin and we'll coerce that to a bouillon value.

If we run the test, we're going to see that everything is going to work just fine because in a test environment, we don't have the Commons Chunk Plugin. In review, to check vendor files, you first add an entry for the modules you want to have chunks together. This is our vendor entry that we have here. We had to change our entry to be an object rather than just this value as a string here so that we could have an app entry for our application and a vendor entry for the vendor files.

Because of this, we had to also update the file name so that each one of these entries could have a unique file associated with them, and we used webpack's interpolation shorthand to make these unique based on their name. We updated our index HTML to reference these files properly and we added the Commons Chunk Plugin referencing our vendor entry by name.

Finally, to prevent this plugin from being used in a test environment, we added this ternary operator and this filter on our plugins array. That's how you check your vendor files separate from your application code to take full advantage of the browser cache for the vendor files that change infrequently.


Featured course:

Create an SVG Icon System

Create an SVG Icon System

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?