Webpack code splitting impacts web performance

Viewed 908

I have a React/Node + SSR application, I am trying to create a production build, I have managed to do that but the problem is that the files that I have in the build are too large. I use latest version of react + webpack 4. Here is my webpack configuration:

clientConfig.js

const path = require('path');
const common = require('./webpack.common-config');

const clientConfig = {
  ...common,
  mode: 'production',
  name: 'client',
  target: 'web',
  devtool: false,
  entry: {
    client: [
      '@babel/polyfill',
      './src/client.js'
    ],
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'all',
          name: 'vendor',
          test: module => /node_modules/.test(module.resource),
          enforce: true
        },
        common: {
          chunks: 'all',
          name: 'client'
        }
      },
    },
  },
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
  }
};

module.exports = clientConfig;

serverConfig.js

const nodeExternals = require('webpack-node-externals');
const path = require('path');
const common = require('./webpack.common-config');

const serverConfig = {
  ...common,
  mode: 'production',
  name: 'server',
  target: 'node',
  devtool: false,
  externals: [nodeExternals()],
  entry: {
    server: ['@babel/polyfill', path.resolve(__dirname, 'src', 'server.js')],
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    }
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].js',
    chunkFilename: "[id].chunk.js"
  },
  node: {
    console: false,
    global: false,
    process: false,
    Buffer: false,
    __filename: false,
    __dirname: false,
  },
};

module.exports = serverConfig;

commonConfig.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

const common = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        include: [path.resolve(__dirname, 'src')],
        query: {
          presets: [
            ['@babel/preset-env', {loose: true, modules: false}],
            "@babel/preset-react"
          ],
          plugins: [
            "@babel/plugin-proposal-class-properties"
          ]
        },
      },
      {
        test: /\.css$/,
        use: [
            {
                loader: MiniCssExtractPlugin.loader,
            },
            'css-loader'
        ]
      },
      {
        test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[ext]',
              outputPath: 'fonts/'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new OptimizeCSSAssetsPlugin(),
    new MiniCssExtractPlugin({
      filename: "styles.css",
    })
  ],
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()]
  },
};

module.exports = common;

And another file which basically merges the client and the server config.

I run npm run build after that I run webpack -p --mode=production --optimize-minimize && node ./build/server.js

I get the following warning:

    WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
    This can impact web performance.
    Assets: 
      vendor.js (667 KiB)
    
    WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
    Entrypoints:
      client (928 KiB)
          vendor.js
          styles.css
          client.js

Any advice or idea for the above size warning would be great! Thank you!

4 Answers

I recommend that you try core-js instead of babel/pollyfill this can help you to reduce your bundle size.

Also, I recommend that you try dynamic importing with react-loadable which supports SSR. there are different strategies for splitting your code. you can read more here(most important one)

In case you are using CSS frameworks such as bootstrap you should only use parts that you need and avoid importing all of them. there is a tool for purging unused CSS named purgecss but use it with caution and you have to know what are you doing.

In case you are using libraries such as lodash or material-ui you should import your module specifically to avoid importing all the packages into your bundle.

exp: import debounce from 'lodash/debounce'

npm dedup or yarn dedup can help to remove duplicate dependencies inside bundle.

You may be able to get some reduction through adjusting your babel configuration. For instance, specifying some options for the "preset-env", such as bugfixes, targets if you don't have support older browsers, and using corejs polyfills instead of babel/polyfill.

"@babel/preset-env", {
  "targets": {
    "browsers": [
      "last 2 years"
    ]
  },
  "bugfixes": true,
  "useBuiltIns": "entry",  // experiment with "entry" vs. "usage"
  "corejs": 3
...

Depending on your codebase, the babel-plugin-transform-runtime may help too. I had some projects where it made a substantial difference, but other where it hasn't.

"@babel/plugin-transform-runtime",
{
  "corejs": 3,
  "version": "7.9.2"
}

Additional options for Webpack include using the webpack-cdn-plugin. This can greatly reduce your vendor bundle size. Of course, users will still have to download those same libraries, but they will be cached and don't need to re-downloaded every time your bundle changes or updates.

A little additional savings can be had by specifying runtimeChunk: true, and optionally inlining that chunk in your index.html with

new HTMLWebpackPlugin({
  inlineSource: 'runtime~.+[.]js',
  ...

and the html-webpack-inline-source-plugin or similar.

You can split files to lower size to remove warning using splitChunks:

optimization: {
      splitChunks: {
        chunks: 'all',
        minSize: 10000,
        maxSize: 250000,
      }
    }

Check more for minSize and maxSize.

If you want to disable the warning, you can do it using performance

performance: {
  hints: false
}

or remove warning for certain size limit like 1 MB:

performance: {
  maxAssetSize: 1000000
}

I managed to switch to Next.js so all of this is not necessary anymore.

Related