DoneDeal0
DoneDeal0

Reputation: 6267

How to code-split webpack's vendor chunk?

I have a webpack main.bundle.js that weights 287kb thanks to codesplitting, but my vendor.js is 5mb. When the user visits the website for the firs time, he will have to download 5.2mb, which is too large.

What is the proper way to split the vendor so the user only downloads the packages he needs to run the page, and then webpack prefetches all the remaining packages in the background?

I'm using webpack 4 (I'm waiting for webpack 5 to be supported by Storybook before upgrading. If there is a new way of doing it in W5, please le me know).

Here is my config:

/* eslint-env node */
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const isProductionMode = (mode) => mode === "production";

module.exports = () => {
  const env = require("dotenv").config({ path: __dirname + "/.env" });
  const nodeEnv = env.parsed.NODE_ENV;
  return {
    mode: "development",
    entry: "./src/index.tsx",
    output: {
      path: path.join(__dirname, "./dist"),
      filename: "[name].[hash].bundle.js",
      publicPath: "/",
    },
    resolve: {
      extensions: [".ts", ".tsx", ".js", "jsx", ".json"],
    },
    module: {
      rules: [
        {
          test: /\.(ts|js)x?$/,
          exclude: /node_modules/,
          use: { loader: "babel-loader" },
        },
        { test: /\.css$/, use: ["style-loader", "css-loader"] },
        { test: /\.(png|jpg|jpeg|gif)$/, use: ["file-loader"] },
        {
          test: /\.svg$/,
          use: [
            {
              loader: "babel-loader",
            },
            {
              loader: "react-svg-loader",
              options: {
                jsx: true,
              },
            },
          ],
        },
      ],
    },
    devServer: {
      historyApiFallback: true,
      port: 3000,
      inline: true,
      hot: true,
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/index.html",
      }),
      new Dotenv(),
    ],
    optimization: {
      minimize: isProductionMode(nodeEnv),
      minimizer: isProductionMode(nodeEnv) ? [new TerserPlugin()] : [],
      splitChunks: { chunks: "all" },
    },
  };
};

Upvotes: 4

Views: 7646

Answers (2)

Simon B.
Simon B.

Reputation: 2706

This discussion https://github.com/facebook/create-react-app/discussions/9161 contains some configs (Webpack 5 and should no tneed much adjustmenst to work with Webpack 4) that may give you a good default if you want to learn and reuse a single chunking strategy.

If you prefer something simple you can add 1-2 custom cacheGroups under config.optimization.splitChunks that can look something like this:

     firebase: { // Internal identifier, just any unique name.
        test: /@firebase|redux-fire/, // This will match any file path containing @firebase or redux-fire (base and store) which is massive.
        name: "firebase", // Add a name to easily spot this chunk among outputs, or remove the `name` property to get a number instead which seems to be best-practice.
        priority: 10, // Higher prio = gets processed earlier. Defaults are on below 0.
      },

The above is compatible with Webpack 4 and 5 as far as I see: https://v4.webpack.js.org/plugins/split-chunks-plugin/

If you want more control you can also cancel out the default groups:

    cacheGroups: {
      firebase: {
        test: /@firebase|redux-fire/,
        name: "firebase",
        priority: 20,
      },
      defaultVendors: false,
      default: false,
      // Add more groups here
    },

Upvotes: 0

Krishna Kumar S
Krishna Kumar S

Reputation: 109

This helped me in splitting the vendor bundle.

Source: https://gist.github.com/davidgilbertson/c9af3e583f95de03439adced007b47f1

splitChunks: {
  chunks: 'all',
  enforce: true,
  cacheGroups: {
    vendor: {
      test: /[\\/]node_modules[\\/]/,
      name(module) {
        // get the name. E.g. node_modules/packageName/not/this/part.js
        // or node_modules/packageName
        const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

        // npm package names are URL-safe, but some servers don't like @ symbols
        return `npm.${packageName.replace('@', '')}`;
      },
    },
  },
},

Upvotes: 6

Related Questions