1

I am not sure how to address this. If you have any ideas, please let me know. As the image shows, these are bundles from main app, and vendors js files. They both show massive unused js for both. They are mostly third party vendors and such, but I don't know how one would go about removing the unused portions.

Anything look amiss? See anything I can improve. enter image description here

I am doing the following in my app.

  1. Code splitting, and using React.Lazy

Here is my base webpack file:

const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const AsyncStylesheetWebpackPlugin = require('async-stylesheet-webpack-plugin');
const ScriptExtHtmlWebPackPlugin = require('script-ext-html-webpack-plugin');
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { getConfigFileByEnv } = require('./src/utils/getConfigFileByEnv');
const imageWebpackQuery = require('./imageWebpackQuery');
const vendor = require('./vendor');

const configFile = getConfigFileByEnv();
const publicPath = process.env.CLIENT_HOST || '';
const assetsPath = `${process.env.PROTOCOL}://${publicPath}/assets/`;

module.exports = {
  mode: 'production',
  entry: {
    vendor,
    app: './src/index.js'
  },
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].chunk.js',
    publicPath: assetsPath,
    path: path.resolve(__dirname, 'build', 'public'), // string
  },
  devtool: 'source-map',
  module: {
    rules: [
      {
        test: /\.js?$/,
        include: [/src/],
        use: ['babel-loader'],
      },
      {
        test: /\.s?css$/,
        include: [
          path.resolve(__dirname, 'src/styles'),
        ],
        use: [
          MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'resolve-url-loader', 'sass-loader'
        ],
      },
      {
        test: /\.s?css$/,
        exclude: [
          path.resolve(__dirname, 'src/styles'),
        ],
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            query: {
              modules: {
                localIdentName: '[hash:base64]',
              },
              importLoaders: true,
              sourceMap: true,
            },
          },
          'postcss-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(eot|otf|ttf|woff|woff2)$/,
        use: 'file-loader?name=[name]_[hash:base64:5].[ext]',
      },
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: [
          {
            loader: 'file-loader',
            query: {
              name: '[name]_[contenthash].[ext]',
              publicPath: assetsPath,
            },
          },
          {
            loader: 'image-webpack-loader',
            options: imageWebpackQuery,
          },
        ],
      },
      {
        test: /\.(mp4)$/,
        loader: 'file-loader',
        query: {
          name: '[name]_[contenthash].[ext]',
          publicPath: assetsPath,
        },
      }
    ],
  },
  resolve: {
    modules: ['src', 'node_modules'],
    alias: {
      config: path.resolve(__dirname, `src/config/`)
    }
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        sourceMap: true,
        terserOptions: {
          warnings: false,
          ie8: true,
        },
      }),
    ],
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'vendor',
          // chunks: 'all',
          reuseExistingChunk: true,
          priority: -10,
          test(module, chunks) {
            const name = module.nameForCondition && module.nameForCondition();
            return chunks.some((chunk) => chunk.name === 'app' && /[\\/]node_modules[\\/]/.test(name));
          }
        },
        appStyles: {
           name: 'appStyles',
           test: (m, chunks) => {
             return m.constructor.name === 'CssModule' && !m.issuer.rawRequest.includes('vendor.main.scss') && chunks.some((chunk) => chunk.name === 'app');
           },
           chunks: 'all',
           enforce: true,
         },
         vendorStyles: {
           name: 'vendorStyles',
           test: (m) => {
             return m.constructor.name === 'CssModule' && m.issuer.rawRequest.includes('vendor.main.scss');
           },
           chunks: 'all',
           enforce: true,
         },
        default: {
          priority: -20,
          reuseExistingChunk: true
        },
      },
    },
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.browser': JSON.stringify(true),
      __CONFIG__: JSON.stringify(configFile)
    }),
    new ProgressBarPlugin(),
    new webpack.ContextReplacementPlugin(/\.\/locale$/, 'empty-module', false, /js$/),
    new MiniCssExtractPlugin({
      filename: '[name]-[contenthash].min.css',
      ignoreOrder: true,
    }),
    new OptimizeCssAssetsPlugin({}),
    new HtmlWebpackPlugin({
      template: './src/document/index.html',
      chunks: ['vendorStyles', 'appStyles', 'app', 'vendor'],
      chunksSortMode: 'manual',
      config: configFile
    }),
    new AsyncStylesheetWebpackPlugin({
      preloadPolyfill: true,
    }),
    new ScriptExtHtmlWebPackPlugin({
      defaultAttribute: 'async'
    }),
    new MomentLocalesPlugin(),
    new InlineManifestWebpackPlugin(),
    new webpack.NamedModulesPlugin(),
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false,
    }),
    new FaviconsWebpackPlugin({}),
  ],
};


james emanon
  • 11,185
  • 11
  • 56
  • 97

1 Answers1

2

The short answer when using third party code is that you can't do much with it without spending a lot of time.

Code splitting doesn't actually change the amount of unused code, it simply means you deliver non-essential code later (which is good for initial page rendering etc).

So you have two options.

The first is that you look through every single function in the code that is "unused" and remove it. Obviously this is fraught with danger as you could easily remove something that may not be used on initial page load but is later needed.

The second is to use a different library entirely or even better yet write the thing from the ground up.

As you can probably tell there are no good options here.

As such my advice would be to see if there are any quick wins (i.e. are you loading a whole library just to lazy load images that could be done with a couple of lines of JS). If so find a smaller library or just write your own implementation without all the extra features you probably don't need.

Then after that don't worry about it and concentrate on sending the minimum amount of JS down the wire to render the "above the fold" content and make the site usable initially. Initial (perceived) page load speed is more important than total load speed as long as the site doesn't feel sluggish.

I have to admit that over 4 megabytes of JS is excessive but I do not know if this is a complex application that needs it or if you just picked the "wrong tool for the job" (which is always a real pain depending on how far into the project you are!).

You need to also weight up time vs reward. I find the following questions are useful:-

  1. Am I / the company spending a significant amount of money on marketing. Is this a page / site that needs to rank well. If the answer is yes to both then speed is important and you have to weight up the time needed to make massive page weight cuts vs potential profits increase through better conversions, rankings etc.
  2. If the site does not need to rank well then does the library (libraries) I am using speed up development enough to justify the slower site speed?
  3. Does the site need to be fast, if it is a sales tool then the answer is yes (as every second load time reduces conversions by 7%-10%). If it is Software as a Service (SaaS) then initial load speed is less important as long as it isn't very slow. Obviously load speed after everything is cached is then important to avoid frustration but that is a lot easier to fix.
  4. Have I fixed everything else and the site still scores poorly? If not then fix the other bits first. What you have to consider here is that a score of 50 actually means the site is in the 25th percentile for performance, this may be sufficient (or good) if this is a SaaS application. The average score for a mobile page is 31 not 50!
GrahamTheDev
  • 22,724
  • 2
  • 32
  • 64