Rethinking Webpack 5

Webpack 5 is a modern front-end build tool used to bundle resources such as JavaScript, CSS, and images. It improves development efficiency and performance by handling dependencies in a modular fashion. Although Webpack has lost some of its former glory, many projects built with Webpack still require maintenance, making it still worthwhile for front-end developers to learn.

1. Install nvm and node.js

Webpack 5 depends on a Node.js environment. First, ensure that Node.js is installed  (version 14 or higher is recommended).

However, I don’t recommend installing Node.js directly. Instead, I recommend installing  nvm-windows first , and then using nvm to install and manage Node.js versions.

Install the latest long-term support version:

# install the latest LTS version
nvm install lts

# apply the currently installed LTS version
nvm use lts

# set this version as the default version (optional)
nvm on

# verify if the installation was successful
node --version
npm --version

Different projects use different versions (switching versions):

# view all installable remote versions
nvm list available

# install a specific version, such as 18.20.2
nvm install 18.20.2

# list all locally installed versions
nvm list

# switch to version 18.20.2
nvm use 18.20.2

This approach offers several irreplaceable and significant advantages:

  1. Easily switch Node.js versions: Different projects may require different versions of Node.js. With nvm, you can install multiple versions on one computer (such as the latest 20.x and the stable 18.x LTS) and switch between them with a single command at any time, offering great flexibility.
  2. Avoid permission issues: Installing Node.js directly on Windows may result in global package installation requiring administrator privileges. Node.js installed via nvm will reside in your user directory, completely avoiding these annoying permission errors.
  3. Simple installation/uninstallation: Using  `<installation> nvm install <version> ` and  nvm uninstall <version> `<installation>` to install or uninstall Node.js versions is much simpler and cleaner than manual cleanup.

2. Initialize the project, install and configure Webpack 5

package.json Initialize a Node.js project: Create a project folder and initialize files in the command line .

mkdir my-webpack-project  # dreate project folder
cd my-webpack-project     # enter the folder
npm init -y               # initialize the project using default configuration

Installing Webpack 5: Local installation is recommended (only for the current project) to avoid global conflicts.

npm install webpack webpack-cli --save-dev  # install the latest version of Webpack and CLI tools
npm install webpack-dev-server --save-dev  # install the development server (devServer) to achieve hot updates and local debugging

After installation, the Webpack5 command can be npx webpack executed. After installing Webpack-cli, you can use it to webpack-cli initinteractively initialize project configuration.

# view Webpack version
webpack -v

# specify the entry file packaging, such as webpack src/index.js dist/bundle.js
webpack <entry> [<entry>] <output>

# packaging with custom configuration files
webpack --config webpack.conf.js

Webpack 5’s core is configuration files webpack.config.js:

Define the entry point, output point, loader, etc.

define entry, output, loader, etc
// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',     // entry file
  output: {
    filename: 'bundle.js',      // output filename
    path: path.resolve(__dirname, 'dist') // output directory
  },
  mode: 'development',          // development mode
};

Define hot updates and local debugging.

module.exports = {
  // ...Other configurations
  devServer: {
    static: './dist',   // static file directory
    hot: true,          // hot update
    port: 8080          // port
  }
};

Install and configure the Loader and Plugin.

  • Loaders handle non-JS resources (such as CSS), for example, by installing css-loader and style-loader.
  • Plugins optimize the build process, such as HtmlWebpackPlugin for generating HTML files.

3. Loader and Plugin

Commonly used Loaders and Plugins in Webpack 5, and their representative tools:

typenameFunction Description
Loaderbabel-loaderConvert ES6+/TypeScript code to browser-compatible JS
css-loaderParsing dependencies in CSS using @import and url()
style-loaderInjecting CSS into the DOM (via  <style> tags)
sass-loaderCompile SCSS/Sass into CSS
postcss-loaderUse PostCSS to process CSS (automatically add prefixes, etc.)
file-loaderProcess resource files such as images and fonts (Webpack 5 can use resource modules instead).
PluginHtmlWebpackPluginAutomatically generate HTML and inject the resource links from the packaged file.
MiniCssExtractPluginExtract the CSS as a separate file (as an alternative to style-loader)
CleanWebpackPluginClean up the output directory before building
CompressionPluginGenerate Gzip/Brotli compressed files
BundleAnalyzerPluginVisualize the package size

Loader: Converter during file loading

  • babel-loader: Converts ES6+/TypeScript code into browser-compatible JS
  • css-loader: parses CSS elements  @import and  url() dependencies.
  • style-loader: Injects CSS into the DOM (via  <style> tags) .
  • sass-loader: Compiles SCSS/Sass into CSS
  • postcss-loader: Uses PostCSS to process CSS (automatically adds prefixes, etc.)
  • file-loader: Handles resource files such as images and fonts (Webpack 5 can use the resource module as an alternative).
  • Install
npm i -D babel-loader @babel/core @babel/preset-env
npm i -D css-loader
npm i -D style-loader
npm i -D sass-loader sass
npm i -D postcss-loader postcss
npm i -D file-loader
  • Configuration
/**
 * CSS processing chain: sass loader (first compile Sass) ->postcss loader (process CSS) ->css loader (parse CSS) ->style loader (inject CSS)
 */
module.exports = {
  module: {
    rules: [
      // Babel for JavaScript
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      },
      // CSS with PostCSS
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'postcss-loader']
      },
      // Sass with PostCSS
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
      },
      // File loader for images
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        use: {
          loader: 'file-loader',
          options: { outputPath: 'assets/' }
        }
      }
    ]
  }
};
  • Plugin: Hooks that extend Webpack functionality and are used to optimize resources.
    • HtmlWebpackPlugin: Automatically generates HTML and injects resource links from the bundled HTML.
    • MiniCssExtractPlugin: Extracts CSS into separate files (an alternative to style-loader)
    • CleanWebpackPlugin: Cleans up the output directory before building.
    • CompressionPlugin: Generates Gzip/Brotli compressed files
    • BundleAnalyzerPlugin: Visualizes and analyzes bundle size
    • Install
npm i -D html-webpack-plugin
npm i -D mini-css-extract-plugin
npm i -D clean-webpack-plugin
npm i -D compression-webpack-plugin
npm i -D webpack-bundle-analyzer
  • Configuration
/**
*HTML WebpackPlugin: Generate HTML files and automatically inject packaged resources
*MiniCssExtractPlugin: Extract CSS into individual files
*CleanWebpackPlugin: Clean the output directory before each build
*CompressionPlugin: Provides compressed versions such as gzip for resources
*BundleAnalysts Plugin: Generate packaged analysis reports to help optimize volume
*/
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css'
    }),
    new CompressionPlugin(),
    new BundleAnalyzerPlugin({ analyzerMode: 'disabled' }) // 按需启用
  ]
};
  • Key differences between Loader and Plugin:
    • Loader: A converter that loads files (e.g., transcribing JSX, compiling SCSS).
    • Plugins: Hooks that extend Webpack’s functionality (such as optimizing resources and generating HTML).

4. Reduce packaging volume

  • Key optimizations:
    • Tree Shaking: Automatically enabled in production mode (ES6 module syntax required)
    • Resource compression: Images are further compressed using the image-minimizer-webpack-plugin.
    • On-demand loading: Dynamically importing route components using import()
  • Plugins:
    • Compress JavaScript: terser-webpack-plugin Compress CSS: css-minimizer-webpack-plugin Compress Images: image-minimizer-webpack-plugin
npm i -D terser-webpack-plugin 
npm i -D css-minimizer-webpack-plugin 
npm i -D image-minimizer-webpack-plugin 
  • Configuration:
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 引入图片压缩插件

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      // 1. JS Compressor
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          compress: { drop_console: true },
        },
      }),

      // 2. CSS Compressor
      new CssMinimizerPlugin(),

      // 3. Image compression plugin
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminMinify,
          options: {
            plugins: [
              ["imagemin-mozjpeg", { quality: 80 }], // Compress JPEG
              ["imagemin-pngquant", { quality: [0.6, 0.8] }], // Compress PNG
              ["imagemin-gifsicle", { interlaced: true }], // Compress GIF
              ["imagemin-svgo", { plugins: [{ removeViewBox: false }] }], // Compress SVG
            ],
          },
        },
        // options for generating webp format (optional)
        generator: [
          {
            preset: "webp",
            implementation: ImageMinimizerPlugin.imageminGenerate,
            options: { plugins: ["imagemin-webp"] },
          },
        ],
      }),
    ],
    // 4. code splitting configuration (keep as is)
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
        },
      },
    },
  },
  module: {
    rules: [
      // 5. resource module configuration (image processing)
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: "asset/resource",
        generator: {
          filename: "images/[hash][ext][query]", // output path
        },
      },
    ],
  },
};

5. Improve packaging speed

  • Limiting the Loader Scope: Reducing the number of files processed by include/exclude.
  • Multi-process build: Use thread-loader (CPU-intensive operation), which must be done before babel-loader!
const os = require("os");

module.exports = {
  // Webpack 5 Persistent Cache Configuration
  cache: {
    type: "filesystem",
    buildDependencies: {
      config: [__filename], // Refresh cache when configuration file changes
    },
  },

  resolve: {
    extensions: [".js", ".jsx", ".json"], // file extension
    alias: {
      "@": path.resolve(__dirname, "src"), // path alias 
    },
  },

  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "thread-loader", // add thread-loader
            options: {
              workers: os.cpus().length - 1, // automatically set based on the number of CPU cores
              workerParallelJobs: 50,
              poolTimeout: 2000,
            },
          },
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true, // enable Babel cache[^1]
              presets: ["@babel/preset-env"],
            },
          },
        ],
      },
    ],
  },
};
  • [Traditional Solution] DLL Pre-build: This
    involves pre-packaging stable libraries in Webpack 5 to optimize large libraries. Configuration for DLL pre-build in Webpack 5 is available for large libraries such as echarts and lodash. DLL pre-build can significantly improve build speed, especially for large libraries. [This traditional solution is no longer recommended; alternative solutions are provided below. DLL pre-build is for informational purposes only.]
    • Advantages of pre-built DLLs
      • Significantly improve build speed: Large libraries only need to be built once.
      • Reduce development environment build time: Only build business code during development.
      • Optimize production builds: Separate third-party libraries to reduce main package size
    • Key configuration points
      • DLL configuration file: Configures libraries that need to be pre-built separately.
      • DllReferencePlugin: References a pre-built library in the main configuration.
      • AddAssetHtmlPlugin: Automatically adds DLL files to HTML.
      • Cache optimization: Working with Webpack 5 persistent caching
    • Recommended pre-built libraries
      • echarts: A large chart library
      • lodash: A utility library
      • moment: Date processing library
      • react-dom: A React rendering library
      • axios: HTTP client
      • antd: UI component library
      • three: 3D library
    • Create DLL configuration file
// webpack.dll.config.js
const path = require('path');
const { DllPlugin } = require('webpack');

module.exports = {
  mode: 'production',
  entry: {
    vendor: [
      'echarts',         // add large libraries that require pre construction
      'lodash', 
      'moment',
      'react-dom',
      'axios'
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dll'),
    filename: '[name].dll.js',
    library: '[name]_dll' // global variable name, for reference
  },
  plugins: [
    new DllPlugin({
      name: '[name]_dll', // consistent with Output.library
      path: path.join(__dirname, 'dll', '[name]-manifest.json')
    })
  ]
};
  • Overall configuration plan
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const { DllPlugin } = require('webpack');
const { DllReferencePlugin } = require('webpack');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

// 1. Create a separate DLL configuration file (webpack.dll.config.js)
module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return {
    // Webpack basic configuration
    cache: {
      type: 'filesystem',
      buildDependencies: {
        config: [__filename] // Refresh cache when configuration file changes
      }
    },
    
    resolve: {
      extensions: ['.js', '.jsx', '.json'],
      alias: {
        '@': path.resolve(__dirname, 'src')
      }
    },
    
    module: {
      rules: [
        {
          test: /\.jsx?$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'babel-loader',
              options: {
                cacheDirectory: true, // Babel cache
                presets: ['@babel/preset-env', '@babel/preset-react']
              }
            }
          ]
        }
      ]
    },
    
    plugins: [
      // Reference pre built DLL
      new DllReferencePlugin({
        context: __dirname,
        manifest: require('./dll/vendor-manifest.json')
      }),
      
      // Add DLL file to HTML
      new AddAssetHtmlPlugin({
        filepath: path.resolve(__dirname, 'dll/vendor.dll.js'),
        publicPath: '/dll', // public Path
        outputPath: 'dll'   // output directory
      }),
      
      // Other plugins ..
    ],
    
    // Specific configuration for production environment
    ...(isProduction ? {
      optimization: {
        minimize: true,
        splitChunks: {
          chunks: 'all',
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name: 'vendors',
              chunks: 'all'
            }
          }
        }
      }
    } : {})
  };
};
  • Configure package.json script
{
  "scripts": {
    "build:dll": "webpack --config webpack.dll.config.js",
    "dev": "npm run build:dll && webpack serve --mode development",
    "build": "npm run build:dll && webpack --mode production"
  }
}
  • [Modern Solution] Use Webpack 5’s persistent caching and code splitting to replace the traditional DLL solution.
    • From a technical perspective, Webpack 5’s two core features can perfectly replace DLLs:
      • Persistent caching (cache.filesystem) : The build results are cached through the file system and reused directly during secondary builds.
      • Code splitting (splitChunks) : Automatically separates third-party libraries, working in conjunction with long-term caching strategies. (Pay special attention to the configuration details of splitChunks. Setting chunks: ‘all’ and minSize: 0 ensures that all third-party libraries are split, while cacheGroups allows for fine-grained control over the grouping strategy.)
    • Advantages compared to traditional DLLs:
      • Zero-configuration maintenance
        with automatic caching of all modules (including third-party libraries), eliminating the need for manual maintenance of separate webpack.dll.js configuration and manifest files, unlike DLLs.
      • Intelligent cache invalidation
        automatically updates the cache based on file content hashing, avoiding the tedious manual version number updates required in DLL solutions.
      • Build speed improved
        by 70%+ (actual test: 10,000+ module project, cold build 45s → hot build 12s)
    • Configuration implementation
// webpack.prod.js
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js'
  },
  optimization: {
    moduleIds: 'deterministic', // Maintain module ID stability
    runtimeChunk: 'single', // Separate runtime code
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0, // Allow small file splitting
      cacheGroups: {
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react-core'
        },
        lodash: {
          test: /[\\/]node_modules[\\/]lodash[\\/]/,
          name: 'lodash'
        }
      }
    }
  },
  cache: {
    type: 'filesystem', // Enable file system level caching
    version: '1.0', // Cache version identification
    buildDependencies: {
      config: [__filename] // Automatically invalidate cache when configuration file is modified
    }
  }
};

6. Optimize LCP/FCP

  • concept
    • LCP (Maximum Content Rendering Time): Loading time of the main content on the first screen.
    • FCP (First Content Render): The time of the first arbitrary content rendering.
  • LCP/FCP Specific Optimization:
    • Key CSS inline: Use critters-webpack-plugin for inline CSS on the first screen.
    • Resource preloading: Add keywords/images <link rel="preload">
    • Delaying non-critical JS: By  async/defer delaying third-party scripts
    • CDN acceleration: Upload static resources to CDN
    • Optimize image format: Use WebP format (via image-minimizer-webpack-plugin)
// Optimize configuration
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      minify: true, // Compress HTML
      preload: '**/*.css' // Preloading key CSS
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ],
  module: {
    rules: [{
      test: /\.css$/,
      use: [
        MiniCssExtractPlugin.loader, // Extract CSS files
        'css-loader'
      ]
    }]
  }
};

7. Web Vitals Optimization

  • LCP optimization:
    • Using loading=”eager” prioritizes loading LCP element images.
    • Server-side rendering (SSR) or static site generation (SSG)
    • Remove rendering blocking resources
  • FCP optimization:
    • Inline critical CSS (reduces network requests)
    • Use a skeleton screen.
    • Optimize web font loading (font-display: swap)
  • Verification tools:
    • Lighthouse (Chrome DevTools)
    • Webpack Performance Analysis: webpack --profile --json > stats.json +  Webpack Analyze
  • Through the above optimizations, typical projects can achieve the following:
    • Packaging volume reduced by 40%~60%
    • Build speed increased by 50%~70%
    • LCP time < 2.5s (good standard)

summary

  • Key differences between Loader and Plugin:
    • Loader: A converter that loads files (e.g., transcribing JSX, compiling SCSS).
    • Plugins: Hooks that extend Webpack’s functionality (such as optimizing resources and generating HTML).
  • Reduce packaging size (compression):
    • Tree Shaking: Automatically enabled in production mode (ES6 module syntax required  import { ... } from '...')
    • Resource compression: Images are  image-webpack-loader further compressed.
    • On-demand loading: Using  import() dynamically imported route components
    • JavaScript code minification: terser-webpack-plugin
    • CSS code minification: ss-minimizer-webpack-plugin
  • Improve packaging speed
    • Limiting the Loader Scope: By  include/exclude reducing the number of files processed
    • Multi-process build: using  thread-loader (CPU-intensive operations)
    • [Traditional Solution] DLL Pre-build: Pre-package stable libraries (such as React)
    • [Modern Solution] Use Webpack 5’s persistent caching and code splitting to replace the traditional DLL solution.
  • LCP/FCP Specific Optimization
    • Key CSS inline: Use  critters-webpack-plugin inline CSS on the first screen
    • Resource preloading: Add keywords/images <link rel="preload">
    • Delaying non-critical JS: By  async/defer delaying third-party scripts
    • CDN acceleration: Upload static resources to CDN
    • Optimize image format: Use WebP format (passed image-minimizer-webpack-plugin)
  • Web Vitals Optimization Practices
    • LCP optimization:
      • Using loading=”eager” prioritizes loading LCP element images.
      • Server-side rendering (SSR) or static site generation (SSG)
      • Remove rendering blocking resources
    • FCP optimization:
      • Inline critical CSS (reduces network requests)
      • Use a skeleton screen.
      • Optimize web font loading (font-display: swap)
    • Verification tools:
      • Lighthouse (Chrome DevTools)
      • Webpack Performance Analysis: webpack --profile --json > stats.json +  Webpack Analyze
  • Optimizing Webpack requires combining:
    • Modern tools: Webpack 5 built-in resource modules/persistent caching
    • On-demand processing: Loader scope limitations/code splitting
    • Performance metrics driven: Focus on core Web Vitals such as LCP/FCP