Skip to content
Go back

Webpack 5 Configuration Guide - Complete Reference

Published:  at  05:23 PM

Introduction

Webpack 5 introduced significant improvements including persistent caching, module federation, better tree shaking, and improved build performance. This guide covers essential configurations with modern best practices.

Essential Dependencies

Core Dependencies

PackagePurpose
webpackCore bundling functionality
webpack-cliCommand-line interface
webpack-dev-serverDevelopment server with HMR
webpack-mergeConfiguration merging

HTML & CSS

PackagePurpose
html-webpack-pluginGenerate HTML files
mini-css-extract-pluginExtract CSS to separate files
css-minimizer-webpack-pluginCSS optimization
postcssCSS transformation
postcss-loaderPostCSS integration
postcss-preset-envModern CSS polyfills

JavaScript & TypeScript

PackagePurpose
babel-loaderBabel integration
@babel/coreBabel core
@babel/preset-envES+ transpilation
swc-loaderFast Rust-based transpilation
ts-loaderTypeScript support
core-jsPolyfills for modern JS

Optimization & Analysis

PackagePurpose
terser-webpack-pluginJS minification
esbuild-webpack-pluginUltra-fast minification
webpack-bundle-analyzerBundle size analysis
compression-webpack-pluginGzip/Brotli compression
split-chunks-visualizerChunk visualization

Utilities

PackagePurpose
copy-webpack-pluginCopy static files
image-minimizer-webpack-pluginImage optimization
eslint-webpack-pluginLinting integration
cross-envCross-platform env variables

devtool Options Explained

Keyword Meanings

KeywordDescriptionUse Case
source-mapFull source map, generates .map fileProduction (accurate errors)
inline-source-mapEmbeds map in bundleDevelopment
evalUses eval(), no .map fileFast development
cheapOnly maps lines, not columnsDevelopment
moduleMaps original source before loadersDebugging source
const devtool =
  process.env.NODE_ENV === "production"
    ? "source-map" // Production: accurate, separate file
    : "eval-cheap-module-source-map"; // Development: fast, good quality

devtool Quick Reference

ValueSpeedQualityOutput
source-mapSlowestHighestSeparate .map file
hidden-source-mapSlowHighestHidden .map file
inline-source-mapSlowHighestEmbedded in bundle
eval-source-mapMediumHighVia eval()
eval-cheap-module-source-mapFastMediumVia eval(), lines only
evalFastestLowestVia eval(), no mapping
cheap-source-mapMediumMediumSeparate file, lines only
cheap-module-source-mapMediumHighOriginal source, lines only

Complete Webpack Configuration

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const { EsbuildPlugin } = require("esbuild-loader");
const CopyPlugin = require("copy-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const CompressionPlugin = require("compression-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");

module.exports = (env, argv) => {
  const isProduction = argv.mode === "production";
  const isDevelopment = !isProduction;

  return {
    // Entry configuration
    entry: {
      main: "./src/index.js",
      // Multiple entry points
      // vendor: './src/vendor.js',
    },

    // Output configuration
    output: {
      path: path.resolve(__dirname, "dist"),
      filename: isProduction ? "js/[name].[contenthash:8].js" : "js/[name].js",
      chunkFilename: isProduction
        ? "js/[name].[contenthash:8].chunk.js"
        : "js/[name].chunk.js",
      assetModuleFilename: "assets/[name].[contenthash:8][ext]",
      publicPath: "/",
      clean: true, // Clean output directory

      // Library target (if building a library)
      // library: {
      //   type: 'umd',
      //   name: 'MyLibrary',
      //   export: 'default',
      // },

      // Hashing options
      hashFunction: "xxhash64", // Faster hashing

      // Module IDs
      chunkIds: isProduction ? "deterministic" : "named",
      moduleIds: isProduction ? "deterministic" : "named",
    },

    // Source map configuration
    devtool: isProduction ? "source-map" : "eval-cheap-module-source-map",

    // File resolution
    resolve: {
      extensions: [".js", ".jsx", ".ts", ".tsx", ".json"],
      alias: {
        "@": path.resolve(__dirname, "src"),
        "@components": path.resolve(__dirname, "src/components"),
        "@utils": path.resolve(__dirname, "src/utils"),
        "@hooks": path.resolve(__dirname, "src/hooks"),
        // Disable symlinks for faster resolution
        // '@': path.resolve(__dirname, 'src'),
      },
      // Cache module resolution
      cache: true,
      // Exclude node_modules from symlink resolution
      symlinks: false,
    },

    // Module rules
    module: {
      rules: [
        // TypeScript with SWC (faster than Babel)
        {
          test: /\.tsx?$/,
          exclude: /node_modules/,
          use: {
            loader: "swc-loader",
            options: {
              jsc: {
                parser: {
                  syntax: "typescript",
                },
                target: "es2017",
                transform: {
                  react: {
                    runtime: "automatic",
                  },
                },
              },
            },
          },
        },

        // JavaScript with Babel (if not using SWC)
        // {
        //   test: /\.jsx?$/,
        //   exclude: /node_modules/,
        //   use: {
        //     loader: 'babel-loader',
        //     options: {
        //       cacheDirectory: true,
        //       cacheCompression: false,
        //     },
        //   },
        // },

        // CSS with PostCSS
        {
          test: /\.css$/,
          use: [
            isProduction ? MiniCssExtractPlugin.loader : "style-loader",
            {
              loader: "css-loader",
              options: {
                importLoaders: 1,
              },
            },
            {
              loader: "postcss-loader",
              options: {
                postcssOptions: {
                  plugins: [
                    [
                      "postcss-preset-env",
                      {
                        stage: 2,
                        autoprefixer: { grid: true },
                      },
                    ],
                  ],
                },
              },
            },
          ],
        },

        // SCSS/SASS
        // {
        //   test: /\.s[ac]ss$/,
        //   use: [
        //     isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
        //     'css-loader',
        //     'postcss-loader',
        //     'sass-loader',
        //   ],
        // },

        // Asset modules (images, fonts, etc.)
        {
          test: /\.(png|svg|jpg|jpeg|gif|webp)$/i,
          type: "asset",
          parser: {
            dataUrlCondition: {
              maxSize: 8 * 1024, // 8kb
            },
          },
          generator: {
            filename: "images/[name].[hash:8][ext]",
          },
        },

        // Fonts
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: "asset/resource",
          generator: {
            filename: "fonts/[name].[hash:8][ext]",
          },
        },

        // Audio/Video
        {
          test: /\.(mp3|mp4|webm|ogg|wav)$/i,
          type: "asset/resource",
          generator: {
            filename: "media/[name].[hash:8][ext]",
          },
        },

        // CSV/XML/JSON data files
        {
          test: /\.(csv|xml|json)$/i,
          type: "asset/resource",
          generator: {
            filename: "data/[name].[hash:8][ext]",
          },
        },

        // Raw files
        {
          test: /\.txt$/,
          type: "asset/source",
        },
      ],
    },

    // Plugins
    plugins: [
      // Generate HTML
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        filename: "index.html",
        inject: "body",
        minify: isProduction
          ? {
              removeComments: true,
              collapseWhitespace: true,
              removeRedundantAttributes: true,
              useShortDoctype: true,
              removeEmptyAttributes: true,
              removeStyleLinkTypeAttributes: true,
              keepClosingSlash: true,
              minifyJS: true,
              minifyCSS: true,
              minifyURLs: true,
            }
          : false,
        // Template parameters
        templateParameters: {
          title: "My Application",
          lang: "en",
        },
      }),

      // Extract CSS in production
      isProduction &&
        new MiniCssExtractPlugin({
          filename: "css/[name].[contenthash:8].css",
          chunkFilename: "css/[name].[contenthash:8].chunk.css",
          // Module filename for extracted CSS
          moduleFilename: module => {
            return `css/${module.name}.[contenthash:8].css`;
          },
        }),

      // Copy static files
      new CopyPlugin({
        patterns: [
          {
            from: "public",
            to: ".",
            noErrorOnMissing: true,
            globOptions: {
              ignore: ["**/.gitkeep"],
            },
          },
        ],
      }),

      // Define environment variables
      new webpack.DefinePlugin({
        "process.env.NODE_ENV": JSON.stringify(
          process.env.NODE_ENV || (isProduction ? "production" : "development")
        ),
        "process.env.API_URL": JSON.stringify(
          process.env.API_URL || "http://localhost:3000"
        ),
      }),

      // ESLint (development only)
      isDevelopment &&
        new ESLintPlugin({
          extensions: ["js", "jsx", "ts", "tsx"],
          exclude: "node_modules",
          fix: true,
        }),

      // Bundle analyzer (development only)
      isDevelopment &&
        new BundleAnalyzerPlugin({
          analyzerMode: "static",
          reportFilename: "../bundle-report.html",
          openAnalyzer: false,
          statsFilename: "../bundle-stats.json",
        }),

      // Compression (production only)
      isProduction &&
        new CompressionPlugin({
          filename: "[path][base].gz",
          algorithm: "gzip",
          test: /\.(js|css|html|svg)$/,
          threshold: 8192, // Only compress > 8kb
          minRatio: 0.8,
        }),

      // Brotli compression (production only)
      isProduction &&
        new CompressionPlugin({
          filename: "[path][base].br",
          algorithm: "brotliCompress",
          test: /\.(js|css|html|svg)$/,
          threshold: 8192,
          minRatio: 0.8,
        }),
    ].filter(Boolean),

    // Development server
    devServer: {
      static: {
        directory: path.join(__dirname, "public"),
      },
      hot: true,
      port: process.env.PORT || 3000,
      open: true,
      compress: true,

      // API proxy
      proxy: {
        "/api": {
          target: "http://localhost:8080",
          changeOrigin: true,
          secure: false,
          pathRewrite: { "^/api": "" },
        },
      },

      // History API fallback
      historyApiFallback: true,

      // Client overlay for errors
      client: {
        overlay: {
          errors: true,
          warnings: false,
          runtimeErrors: true,
        },
        progress: true,
      },

      // Live reload
      liveReload: true,

      // Allowed hosts
      allowedHosts: "all",

      // CORS headers
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
    },

    // Optimization
    optimization: {
      // Minimize in production
      minimize: isProduction,

      // Minimizer plugins
      minimizer: [
        // ESBuild (faster than Terser)
        new EsbuildPlugin({
          target: "es2017",
          css: true,
          minify: isProduction,
        }),

        // Fallback to Terser
        // new TerserPlugin({
        //   terserOptions: {
        //     parse: { ecma: 2017 },
        //     compress: {
        //       ecma: 2017,
        //       comparisons: false,
        //       inline: 2,
        //       drop_console: isProduction,
        //       pure_funcs: isProduction ? ['console.log'] : [],
        //     },
        //     mangle: { safari10: true },
        //     output: {
        //       ecma: 2017,
        //       comments: false,
        //       ascii_only: true,
        //     },
        //   },
        //   parallel: true,
        //   extractComments: false,
        // }),

        // CSS minimizer
        new CssMinimizerPlugin({
          minimizerOptions: {
            preset: [
              "default",
              {
                discardComments: { removeAll: true },
                minifyFontValues: { removeQuotes: false },
              },
            ],
          },
        }),
      ],

      // Split chunks configuration
      splitChunks: {
        chunks: "all",
        maxInitialRequests: 25,
        maxAsyncRequests: 25,
        minSize: 20000,
        maxSize: 244000,
        cacheGroups: {
          // Default vendor chunk
          defaultVendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            reuseExistingChunk: true,
            name: "vendors",
          },

          // React ecosystem
          react: {
            test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)[\\/]/,
            name: "react-vendor",
            chunks: "all",
            priority: 20,
          },

          // Large libraries
          largeVendor: {
            test: /[\\/]node_modules[\\/].*(lodash|moment|date-fns|axios)[\\/]/,
            name: "utils-vendor",
            chunks: "all",
            priority: 10,
          },

          // Common chunks (shared between entry points)
          common: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true,
          },

          // Styles
          styles: {
            name: "styles",
            type: "css/mini-extract",
            chunks: "all",
            enforce: true,
          },
        },
      },

      // Separate runtime chunk
      runtimeChunk: {
        name: entrypoint => `runtime-${entrypoint.name}`,
      },

      // Consistent module and chunk IDs
      moduleIds: isProduction ? "deterministic" : "named",
      chunkIds: isProduction ? "deterministic" : "named",

      // Merge duplicate chunks
      mergeDuplicateChunks: true,

      // Split modules across chunks
      splitModuleChunks: true,

      // CSS ordering
      cssProcessorOptions: {
        parser: true,
        map: {
          inline: false,
          annotation: true,
        },
      },

      // Source map for minimizer
      minimizerOpts: {
        sourceMap: true,
      },

      // Used exports (tree shaking)
      usedExports: true,
      sideEffects: true,

      // Concatenate modules where possible
      concatenateModules: true,
    },

    // Performance hints
    performance: {
      hints: isProduction ? "warning" : false,
      maxEntrypointSize: 512000, // 500kb
      maxAssetSize: 512000,
      assetFilter: assetFilename => {
        return !assetFilename.endsWith(".map");
      },
    },

    // Stats configuration
    stats: {
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false,
      errors: true,
      errorDetails: true,
      warnings: true,
      entrypoints: "auto",
      env: true,
      hash: false,
    },

    // Caching configuration (Webpack 5 feature)
    cache: {
      type: isProduction ? "filesystem" : "memory",
      buildDependencies: {
        config: [__filename],
      },
      cacheDirectory: path.resolve(__dirname, ".webpack-cache"),
      compression: "gzip",
      hashAlgorithm: "xxhash64",
    },

    // Experiments
    experiments: {
      // Top-level await
      topLevelAwait: true,

      // Asset modules
      asset: true,

      // WebAssembly modules
      wasm: true,

      // Async WebAssembly modules
      asyncWebAssembly: true,

      // CSS modules
      css: true,

      // FutureDefaults
      futureDefaults: true,
    },

    // External dependencies (for library building)
    // externals: {
    //   react: 'React',
    //   'react-dom': 'ReactDOM',
    // },

    // Devtool fallback
    // snapshot: {
    //   managedPaths: [path.resolve(__dirname, 'node_modules')],
    //   immutablePaths: [],
    // },

    // Ignore warnings
    ignoreWarnings: [
      /Failed to parse source map/,
      /Critical dependency: the request of a dependency is an expression/,
    ],

    // Infrastructure logging
    infrastructureLogging: {
      level: "info",
      console: {
        InfoPlugin: true,
      },
    },
  };
};

Environment-Based Configuration

Environment Variables

# .env.development
NODE_ENV=development
API_URL=http://localhost:8080
PORT=3000

# .env.production
NODE_ENV=production
API_URL=https://api.example.com

Using dotenv

// Load environment variables
require("dotenv").config({
  path:
    process.env.NODE_ENV === "production"
      ? ".env.production"
      : ".env.development",
});

Advanced Features

Module Federation (Micro-frontends)

// Host application
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        remoteApp: "remoteApp@http://localhost:3001/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
      },
    }),
  ],
};

// Remote application
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "remoteApp",
      filename: "remoteEntry.js",
      exposes: {
        "./Button": "./src/Button",
        "./Header": "./src/Header",
      },
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true },
      },
    }),
  ],
};

React Fast Refresh

// webpack.config.js
module.exports = {
  plugins: [
    new ReactRefreshWebpackPlugin({
      overlay: {
        sockPort: 3000,
        useLegacyWDSSockets: false,
      },
    }),
  ],
  // Disable Fast Refresh for production
  !isProduction && new ReactRefreshWebpackPlugin(),
};

Bundle Optimization Strategies

1. Code Splitting

// Dynamic imports
const handleClick = () => {
  import(/* webpackChunkName: "analytics" */ "./analytics")
    .then(module => module.trackClick())
    .catch(err => console.error(err));
};

// Prefetch hints
import(/* webpackPrefetch: true */ "./PrefetchComponent");
import(/* webpackPreload: true */ "./PreloadComponent");

2. Tree Shaking

// package.json
{
  "sideEffects": ["*.css", "*.scss"]
}

3. Bundle Analysis

// Visualizer plugin
new BundleAnalyzerPlugin({
  analyzerMode: "static",
  openAnalyzer: false,
  reportFilename: "bundle-report.html",
});

// Or use webpack-bundle-analyzer CLI
// npx webpack-bundle-analyzer dist/stats.json

Development Workflow

Common Scripts (package.json)

{
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "webpack --mode production",
    "build:analyze": "webpack --mode production --env analyze",
    "serve": "webpack serve --mode production",
    "lint": "eslint src --ext .js,.jsx,.ts,.tsx",
    "typecheck": "tsc --noEmit",
    "clean": "rm -rf dist .webpack-cache"
  }
}

Common Issues & Solutions

IssueSolution
Slow buildEnable cache: { type: 'filesystem' }
Large bundleUse splitChunks and analyze with bundle analyzer
Source maps brokenUse source-map devtool in production
HMR not workingEnsure hot: true in devServer
Memory issuesUse spawn: false option for fork-ts-checker
Module not foundCheck resolve.alias and extensions

Summary

Key Takeaways

  1. Use Webpack 5 features: Persistent caching, asset modules, module federation
  2. Choose appropriate devtool: source-map for production, eval-cheap-module-source-map for development
  3. Optimize with splitChunks: Separate vendor and common code
  4. Enable filesystem caching: Dramatically faster rebuilds
  5. Use modern minimizers: ESBuild or SWC for faster builds
  6. Configure resolve properly: Extensions, alias, cache settings
  7. Enable tree shaking: Use sideEffects in package.json
  8. Monitor bundle size: Use bundle analyzer regularly


Previous Post
docker常用命令
Next Post
React源码学习