Webpack 4 - Migrating from CommonsChunkPlugin to SplitChunksPlugin

3.3k views Asked by At

We have a traditional server rendered application (non SPA) where each page is augmented with vuejs

Our existing webpack 3 configuration is

webpack.config.js

var webpack = require('webpack')
var path = require('path')

const ExtractTextPlugin = require('extract-text-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
    entry: {
        shared: './shared.js',
        pageA: './pageA.js',
        // pageB: './pageB.js',
        // pageC: './pageC.js',
        // etc
    },

    resolve: {
        alias: { vue: 'vue/dist/vue.esm.js' },
    },

    output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].js',
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: ExtractTextPlugin.extract({
                    use: [
                        {
                            loader: 'css-loader',
                            query: {
                                sourceMap: true,
                            },
                        },
                    ],
                }),
            },
        ],
    },

    plugins: [
        new CleanWebpackPlugin('./dist'),

        new webpack.optimize.CommonsChunkPlugin({
            name: ['shared'],
            minChunks: Infinity,
        }),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'runtime',
        }),

        new ExtractTextPlugin('[name].css'),

        new CopyWebpackPlugin([{ from: 'index.html', to: '.' }]),
    ],
}

shared.js

// import shared dependencies & pollyfills
var vue = require('vue')

// import global site css file
require('./shared.css')

// initialize global defaults
// vue.setDefaults(...)

console.log('shared', { vue })

pageA.js

var vue = require('vue')

// only this page uses axios
var axios = require('axios')

console.log('pageA', { vue, axios })

shared.css

body {
    background-color: aquamarine;
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- included on every page-->
    <link rel="stylesheet" href="shared.css">
</head>
<body>
    <!-- included on every page-->
    <script src="runtime.js"></script>
    <script src="shared.js"></script>

    <script src="pageA.js"></script>
</body>
</html>

With this setup

1) runtime.js contains the webpack loader, so any changes to shared.js don't cause pageA.js to be cache busted and vice versa

2) shared.js contains any shared dependencies (in this case vue) as well as any shared global initializion for every page (setting vue defaults etc). It is also the point we import our shared global css file.

3) pageA.js does not contain any dependencies imported in shared.js (vue in this case) but does contain dependicies it imports (axios in this case).

We have been unable to reproduce this setup using the SplitChunksPlugin

1) SplitChunksPlugin doesn't seem to allow an entry point as a split point.

2) All the examples have split out ALL node module dependices into a vendor chunk. This doesn't work for us as we have 100's of pages but only a few import a graphing library or moment etc... We don't want to have this graphing library or moment included in shared.js as it will then be load for all pages.

3) It wasn't clear how to split the runtime into its own file either

SplitChunksPlugin seems to be targeted at SPA's where javascript can be loaded on demand. Is the scenario we are trageting still supported?

2

There are 2 answers

2
Eliott Robson On

Are you trying to migrate to webpack 4?

I find the optimisation cacheGroups test option works well to be specific about what goes where.

optimization: {
  splitChunks: {
    cacheGroups: {
      shared: {
        test: /node_modules[\\/](?!axios)/,
        name: "shared",
        enforce: true,
        chunks: "all"
      }
    }
  }
}

Will load everything from the node modules (except axios) and should therefore be included as part of your page entry point.

4
Cleriston On

If you want webpack to chunk some component, you will need to import it asynchronously from your main entry file. I have been using bundle-loader to do it, then I have:

In my webpack.config.js

optimization: {
  splitChunks: {
    chunks: 'all'
  },
  mergeDuplicateChunks: true,
}
module: {
  rules: [
    {
      test: /\.bundle\.js$/, //yes my output file contains the bundle in its name
      use: {
        loader: 'bundle-loader', options: {lazy: true}
      }
    }
  ]
}

In my entry file.

//this code will replace the line where you are importing this component
let Login;

// this method will go inside your component
componentWillMount() {
    require("bundle-loader!./ui/Login.jsx")((loginFile) => {
        Login = loginFile.default;
        this.setState({ loginLoaded: true });
    });
}

If you don't want to use it, there are more ways of importing your file async.