1. 20
    Grouping vendor files with the Webpack CommonsChunkPlugin
    5m 36s
⚠️ This lesson is retired and might contain outdated information.

Grouping vendor files with the Webpack CommonsChunkPlugin

Kent C. Dodds
InstructorKent C. Dodds
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 2 years ago

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).

[00:00] 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.

[00:15] 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.

[00:33] 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.

[00:52] 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.

[01:02] 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.

[01:19] 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.

[01:31] 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.

[01:52] 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.

[02:22] 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.

[02:36] 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.

[02:57] 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.

[03:21] 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.

[03:43] 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.

[04:06] 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.

[04:32] 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.

[04:59] 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.

[05:20] 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.

Francisco
Francisco
~ 8 years ago

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

hipertracker
hipertracker
~ 8 years ago

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),
  }
}
hipertracker
hipertracker
~ 8 years ago

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
      }),

  }
}
Kent C. Dodds
Kent C. Doddsinstructor
~ 8 years ago

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

Kent C. Dodds
Kent C. Doddsinstructor
~ 8 years ago

Thanks for sharing!

Dean
Dean
~ 8 years ago

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!

Kent C. Dodds
Kent C. Doddsinstructor
~ 8 years ago

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.

Dean
Dean
~ 8 years ago

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

Markdown supported.
Become a member to join the discussionEnroll Today