I have successfully got Webpack and the CommonsChunkPlugin to split my code in two - one bundle with my codebase, and one with everything imported from node_modules. That was the relatively easy bit.
The next thing I'm trying to achieve in my app is to dynamically import a "sub-app", which has quite different vendor package requirements (e.g. React - my main app doesn't use it, but my sub-app does), without all of those packages appearing in the main vendor file.
If I add in the import(), but leave my Webpack config relatively untouched, I end up with 4 bundles
- Webpack runtime
- The main app codebase
- Vendor bundle with everything imported in the main app codebase
- The dynamically imported bundle, also containing all of the node_modules imported in it too :(
This isn't desirable. I'd like the same benefit of 'my code vs vendor code' that I get from my main codebase for my sub-app too. Ideally I'd end up with 5 bundles, with #4 in my list above split into two. When the dynamic import occurs at runtime, it would somehow magically load in both my sub-app code bundle, AND the accompanying sub-app-vendor bundle too. Ideally that sub vendor bundle wouldn't contain anything that was present in the main vendor bundle.
After attempting lots of things I've found in various blog posts, I got one situation working where I was manually selecting the node_modules directories I'd like to include in a separate vendor bundle, but the problem was that it wouldn't include their dependencies automatically, so I'd still end up with lots of node_modules in my sub app bundle - ones that I haven't specifically imported.
If I can get this to work correctly, I'd then like to replicate it for more sub-apps of my main app.
UPDATE 1
My Webpack config is split into 3 files - common, dev and prod. Only common and dev are relevant to this, so I'll share them here.
webpack.common.js
const webpack = require('webpack');
const path = require('path');
const NameAllModulesPlugin = require('name-all-modules-plugin');
module.exports = {
entry: {
/**
* main.js - our global platform JS
*/
main: './src/app.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: [
[
'env',
{
'targets': {
'browsers': ['last 3 versions', 'ie >= 11']
}
}
],
'react'
],
plugins: [
'transform-class-properties',
'transform-object-rest-spread',
// Followed instructions here to get dynamic imports working
// http://docs1.w3cub.com/webpack~2/guides/code-splitting-import/
'syntax-dynamic-import',
'transform-async-to-generator',
'transform-regenerator',
'transform-runtime'
]
}
}
]
},
resolve: {
alias: {
src: path.resolve(__dirname, 'src/')
}
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
CodeMirror: 'codemirror'
}),
new webpack.NamedModulesPlugin(),
new webpack.NamedChunksPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: (m) => /node_modules/.test(m.context)
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
minChunks: Infinity
}),
new NameAllModulesPlugin()
]
};
webpack.dev.js
const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'inline-source-map',
output: {
filename: '[name].js',
chunkFilename: '[name]-chunk.js', // used for async chunks (those loaded via import())
path: path.resolve(__dirname, 'build'),
publicPath: '/js/build/'
},
plugins: [
// Uncomment and run build, to launch the bundle analyzer webpage
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify('dev') }
})
]
});
UPDATE 2
I've stumbled on a config that appears to work. It even automatically loads in the sub-app's vendor chunk at the same time as the actual import.
// For the main app's modules
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: (m, count) => /node_modules/.test(m.context)
}),
// For my sub app's modules
new webpack.optimize.CommonsChunkPlugin({
name: 'any-name-here', // doesn't appear to be used anywhere, but prevents 'main' from showing up in the chunk filename (?!)
chunks: ['name-of-dynamic-import'], // this has to be the 'webpackChunkName' you've used within the import() statement
async: 'name-of-dynamic-import-vendor', // name the chunk filename
minChunks: (m, count) => /node_modules/.test(m.context)
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
minChunks: Infinity
}),