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.

QQ Music Frontend Team
QQ Music Frontend Team
QQ Music Frontend Team
Master Webpack Code Splitting to Speed Up Vue Applications

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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceVueWebpackCode Splittingvuetify
QQ Music Frontend Team
Written by

QQ Music Frontend Team

QQ Music Web Frontend Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.