Now for the elephant in the room webpack.config.js :

// webpack.config.js ... entry: {

app: APP_DIR +'/index.js',

vendor: Object.keys(package.dependencies)

},

output: {

publicPath: '/',

chunkFilename: '[name].[chunkhash].js',

filename: '[name].[chunkhash].js'

}, ... plugins: [

...

new webpack.HashedModuleIdsPlugin(),

new webpack.optimize.CommonsChunkPlugin({

name:'vendor',

filename: 'vendor.[chunkhash].js'

}),

new webpack.optimize.CommonsChunkPlugin({

name:'manifest'

})

] ...

That’s all that was needed outside of a “normal” React webpack setup. Mind blown.

Here’s a little more detail on what’s going on in this config.

Define your entry split points. In our case we want a app.x.js and a vendor.x.js file:

entry: {

app: APP_DIR +'/index.js',

vendor: Object.keys(package.dependencies)

},

Use [chunkhash] for your file name hashes in the output:

output: {

publicPath: '/',

chunkFilename: '[name].[chunkhash].js',

filename: '[name].[chunkhash].js'

},

In plugins, use:

new webpack.HashedModuleIdsPlugin()

then your typical CommonsChunkPlugin with the chunkhash again:

new webpack.optimize.CommonsChunkPlugin({

name:'vendor',

filename: 'vendor.[chunkhash].js'

}),

Lastly, but an easy to overlook detail:

new webpack.optimize.CommonsChunkPlugin({

name:'manifest'

})

I am going to dive into why the manifest file is important right here. Feel free to skip to the next section for the React code.

Without the manifest chunk, Webpack will produce these files:

pageOne.58e60ef81ba97426a00d.js 679 bytes

home.b65cb7fd922ea7126e95.js 671 bytes

app.3c3fcb4d6bcba55f3bb7.js 3.44 kB

vendor.da901bd61614a0e9f2fe.js 1.25 MB

index.html 397 bytes

You might think, “Awesome, it’s working!”. You go about your business, make a change to the home component, Webpack builds your files, then you get sad.

home.5a5f308381fc5670d102.js 674 bytes <--new hash expected

vendor.c509e728faa4374eee45.js 1.25 MB <--new hash not expected

index.html 397 bytes

What gives? You didn’t change/add anything to your package.json . The Webpack docs briefly explain what’s going on here. That makes sense at a very high level but I wanted to know what is actually changing in the code to warrant generating a new hash for the vendors.x.js file.

If you look inside the generated vendor.x.js you will see:

/******/ script.src = __webpack_require__.p + "" + ({"0":"pageOne","1":"home","2":"app"}[chunkId]||chunkId) + "." + {"0":"58e60ef81ba97426a00d","1":"5a5f308381fc5670d102","2":"3c3fcb4d6bcba55f3bb7"}[chunkId] + ".js";

Ah ha! These are all of our [chunkhash] values. Since we changed the home component, a new hash was generated and added to the “webpack boilerplate” which in turn gets tossed into our vendor.x.js file. Not exactly what we want to happen seeing how our vendor file will not change very often and is generally the largest file in our build.

Let’s run the same exercise with the a webpack manifest file.

pageOne.58e60ef81ba97426a00d.js 679 bytes

home.5a5f308381fc5670d102.js 674 bytes

vendor.db2436c7653388db768a.js 1.24 MB

app.8e527bb7a890edfc1ef3.js 3.44 kB

manifest.2b89533b2a3dea66348d.js 5.96 kB

make a change to home

home.b65cb7fd922ea7126e95.js 671 bytes

manifest.c4a2e5b1ff1dcfbe35ae.js 5.96 kB

🎉 only a new hash was generated for the changed home component and the manifest .

Let’s look inside the manifest file:

/******/ script.src = __webpack_require__.p + "" + ({"0":"pageOne","1":"home","2":"vendor","3":"app"}[chunkId]||chunkId) + "." + {"0":"58e60ef81ba97426a00d","1":"b65cb7fd922ea7126e95","2":"db2436c7653388db768a","3":"8e527bb7a890edfc1ef3"}[chunkId] + ".js";