Master Webpack Code Splitting to Speed Up Vue Applications
This article explains how to improve front‑end performance by separating business code from libraries, using on‑demand async loading, configuring CommonsChunkPlugin, externalizing large dependencies, and applying HTTP/2 optimizations, all demonstrated with a real Vue/Vuetify project.
Preface
In the HTTP/1 era, a common performance optimization was to merge HTTP requests by concatenating many JS files, but a very large bundle can be counter‑productive. Splitting code into logical chunks—separating first‑screen from non‑first‑screen code and business code from library code—allows the browser cache to be used efficiently and improves first‑screen load speed.
Core Idea
Separating Business Code and Library Code
This is easy to understand: business code changes frequently while the underlying library updates slowly, so extracting the library into its own chunk lets the browser cache it.
On‑Demand Asynchronous Loading
Load only the code needed for the initial screen, avoiding the download of all route code.
Practical Example
We refactored an internal system with Vuetify using a standard webpack configuration, but the bundles were large.
Using webpack‑bundle‑analyzer we saw that Vue, Vuetify and other modules were duplicated in the bundles.
CommonsChunkPlugin
We discovered that the vendor entry did not capture all node_modules (e.g., axios), so they ended up in app.js. The following configuration separates them:
entry: {
vendor: ['vue','vue-router','vuetify','axios'],
app: './src/main.js'
},To avoid manually listing modules we use minChunks to automatically extract any module from node_modules that is a .js file.
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: ({resource}) => (
resource && resource.indexOf('node_modules') >= 0 && resource.match(/\.js$/)
),
})After this step the node_modules are bundled into the vendor chunk.
We observed that some components like codemirror were still duplicated because they were loaded asynchronously. By converting the routes to direct imports we forced codemirror into the vendor chunk, which is not desirable.
async
The solution is to use CommonsChunkPlugin with async and a minChunks count of 2, so any module used in at least two async chunks becomes a shared chunk.
new webpack.optimize.CommonsChunkPlugin({
async: 'used-twice',
minChunks: (module, count) => (count >= 2)
})After rebuilding, shared components moved to 0.used-twice-app.js, reducing each page size by about 10 KB.
However, Vuetify’s JS and CSS are still large, so we externalize them and load via CDN, removing them from the bundle.
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons" rel="stylesheet">
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify/dist/vuetify.js"></script>We also added externals to the webpack config:
externals: {
'vue': 'Vue',
'vuetify': 'Vuetify'
}Rebuilding shows Vue‑related code removed from the bundles, and the remaining used-twice-app.js is about 200 KB smaller.
Finally, we made codemirror load asynchronously only on the pages that need it, reducing the first‑screen bundle further.
// components: { MCode: () => import(/* webpackChunkName: "MCode" */ '../component/MCode.vue') }After this change the first‑screen size decreased by another ~150 KB.
Conclusion
Even though splitting increases the number of requests, using HTTP/2 with multiplexing and browser caching keeps the request count reasonable. The final webpack configuration is provided below for reference.
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const generateHtml = new HtmlWebpackPlugin({
title: 'Xiaoyao System',
template: './src/index.html',
minify: { removeComments: true }
});
module.exports = {
entry: { app: './src/main.js' },
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].[hash].js',
chunkFilename: '[id].[name].[chunkhash].js'
},
resolve: {
extensions: ['.js', '.vue'],
alias: { 'vue$': 'vue/dist/vue.esm.js', 'public': path.resolve(__dirname, './public') }
},
externals: { 'vue': 'Vue', 'vuetify': 'Vuetify' },
module: {
rules: [
{ test: /\.vue$/, loader: 'vue-loader', options: { loaders: {} } },
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { objectAssign: 'Object.assign' } },
{ test: /\.css$/, loader: ['style-loader', 'css-loader'] },
{ test: /\.styl$/, loader: ['style-loader', 'css-loader', 'stylus-loader'] }
]
},
devServer: { historyApiFallback: true, noInfo: true },
performance: { hints: false },
devtool: '#eval-source-map',
plugins: [
new CleanWebpackPlugin(['dist']),
generateHtml,
new BundleAnalyzerPlugin(),
new webpack.optimize.CommonsChunkPlugin({ name: 'ventor', minChunks: ({resource}) => (resource && resource.indexOf('node_modules') >= 0 && resource.match(/\.js$/)) }),
new webpack.optimize.CommonsChunkPlugin({ async: 'used-twice', minChunks: (module, count) => (count >= 2) }),
new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }),
new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }),
new webpack.LoaderOptionsPlugin({ minimize: true })
]
};
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map';
module.exports.plugins = (module.exports.plugins || []).concat([
new BundleAnalyzerPlugin()
// other production plugins...
]);
}References:
Webpack Code Splitting: https://zhuanlan.zhihu.com/p/26710831
Vue + webpack async component loading: http://blog.csdn.net/weixin_36094484/article/details/74555017
Vue2 component lazy loading: https://www.cnblogs.com/zhanyishu/p/6587571.html
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
