DevToolBoxFREE
Blog

Webpack 5 Configuration Guide: Loaders, Plugins, Code Splitting & Optimization

13 min readby DevToolBox

Webpack 5 remains the dominant bundler for large-scale production applications, particularly those requiring Module Federation, complex code splitting, or migration from legacy setups. This guide covers everything from basic loaders to advanced optimization techniques that reduce bundle sizes by 40-60%.

Webpack 5 Basics and Entry/Output

The webpack.config.js file controls all bundling behavior. Understanding the core concepts — entry, output, mode, and resolve — is essential before diving into loaders and plugins.

// webpack.config.js — Complete Production Config

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

const isDev = process.env.NODE_ENV !== 'production';

module.exports = {
  mode: isDev ? 'development' : 'production',

  entry: {
    main: './src/index.tsx',
    // Multiple entry points for different pages
    // admin: './src/admin/index.tsx',
  },

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: isDev ? '[name].js' : '[name].[contenthash:8].js',
    chunkFilename: isDev ? '[name].chunk.js' : '[name].[contenthash:8].chunk.js',
    assetModuleFilename: 'assets/[hash:8][ext][query]',
    clean: true,   // Remove old files on each build
    publicPath: '/',
  },

  resolve: {
    extensions: ['.tsx', '.ts', '.js', '.jsx', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
    },
  },

  // Persistent filesystem cache — huge speed improvement for incremental builds
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename], // Invalidate cache when config changes
    },
  },

  devServer: {
    port: 3000,
    hot: true,
    historyApiFallback: true,
    compress: true,
  },
};

Essential Loaders

Loaders transform files before they are added to the dependency graph. Each loader handles a specific file type or transformation.

// Essential Webpack Loaders Configuration

module.exports = {
  module: {
    rules: [
      // 1. TypeScript/JavaScript with Babel
      {
        test: /\.(ts|tsx|js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', { targets: '>0.25%, not dead' }],
              ['@babel/preset-react', { runtime: 'automatic' }],
              '@babel/preset-typescript',
            ],
            // Cache Babel results for faster rebuilds
            cacheDirectory: true,
            cacheCompression: false,
          },
        },
      },

      // 2. CSS / SCSS / PostCSS
      {
        test: /\.module\.(css|scss)$/,  // CSS Modules
        use: [
          isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: { modules: true, importLoaders: 2 },
          },
          'postcss-loader',
          'sass-loader',
        ],
      },
      {
        test: /(?!\.module)\.(css|scss)$/,  // Global CSS
        use: [
          isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },

      // 3. Images and fonts (Webpack 5 Asset Modules — no loader needed)
      {
        test: /\.(png|jpg|jpeg|gif|webp)$/i,
        type: 'asset',  // Auto-chooses between inline and resource
        parser: {
          dataUrlCondition: { maxSize: 10 * 1024 }, // Inline if < 10KB
        },
      },
      {
        test: /\.svg$/,
        use: ['@svgr/webpack'],  // Import SVGs as React components
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
    ],
  },
};

Code Splitting and Lazy Loading

Code splitting is Webpack's most important optimization feature. It splits your code into multiple bundles that can be loaded on demand, reducing initial load time.

// Code Splitting and Lazy Loading

// 1. Dynamic imports (automatic code splitting)
// React.lazy creates a separate chunk for each lazy component
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/analytics" element={<Analytics />} />
      </Routes>
    </Suspense>
  );
}

// 2. Manual chunk naming with magic comments
const HeavyChart = lazy(
  () => import(/* webpackChunkName: "charts" */ './components/HeavyChart')
);

// 3. Webpack SplitChunksPlugin configuration
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // Vendor chunk: node_modules
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 20,
        },
        // Separate React from other vendors
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
          name: 'react-vendor',
          chunks: 'all',
          priority: 30,
        },
        // Shared chunks used by 2+ modules
        common: {
          name: 'common',
          minChunks: 2,
          priority: 10,
          reuseExistingChunk: true,
        },
      },
    },
    runtimeChunk: 'single',  // Separate runtime chunk
  },
};

Production Optimization

Production builds require aggressive optimization to minimize bundle sizes. Webpack 5's built-in optimizations plus careful configuration can achieve significant size reductions.

// Production Optimization Configuration
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,  // Use all CPU cores
        terserOptions: {
          compress: {
            drop_console: true,    // Remove console.log in production
            drop_debugger: true,
            pure_funcs: ['console.log'],
          },
          format: { comments: false },
        },
      }),
      new CssMinimizerPlugin(),
    ],
    // Tree shaking — only include used exports
    usedExports: true,
    sideEffects: true,  // Respect package.json sideEffects field
  },

  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true,
      },
    }),
    // Only add analyzer when needed: ANALYZE=true npm run build
    process.env.ANALYZE && new BundleAnalyzerPlugin(),
  ].filter(Boolean),
};

Frequently Asked Questions

When should I use Webpack instead of Vite?

Choose Webpack for: Module Federation (micro-frontends), very large existing codebases with complex Webpack configs, specific loaders with no Vite equivalent, or when you need IE11 support. For new projects in 2026, Vite is usually the better choice.

How do I analyze my Webpack bundle size?

Use webpack-bundle-analyzer: npm install --save-dev webpack-bundle-analyzer, then add it as a plugin with BundleAnalyzerPlugin. Run your build and it opens an interactive treemap showing what's consuming your bundle size. Focus on the largest rectangles first.

What is Module Federation in Webpack 5?

Module Federation allows multiple separate Webpack builds to share code at runtime without a shared build process. Each application can expose components/modules and consume from others. It's the primary architecture for micro-frontends at scale.

Why is my Webpack build so slow?

Common causes: (1) No persistent caching (add cache: { type: "filesystem" }), (2) Too many loaders in chain, (3) babel-loader without exclude: /node_modules/, (4) large devtool source maps in development, (5) not using thread-loader for parallel builds. Enable cache first as it provides the biggest speedup.

Related Tools

𝕏 Twitterin LinkedIn
Was this helpful?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Try These Related Tools

{ }JSON FormatterJSJavaScript Minifier

Related Articles

Vite Plugin Development: Build Custom Plugins from Scratch 2026

Learn how to create custom Vite plugins in 2026: plugin hooks, transform API, virtual modules, HMR integration, and publishing to npm.

Webpack vs Vite in 2026: Which Build Tool Should You Choose?

A comprehensive comparison of Webpack and Vite in 2026. Performance benchmarks, ecosystem support, migration strategies, and when to use each build tool.

Monorepo Tools 2026: Turborepo vs Nx vs Lerna vs pnpm Workspaces Compared

Complete comparison of monorepo tools in 2026: Turborepo, Nx, Lerna, and pnpm workspaces. Choose the right tool for your team with benchmarks and real-world use cases.