Webpack
03 / 03

Webpack Optimization

Webpack Optimization

Advanced webpack techniques: code splitting, tree shaking, long-term caching, bundle analysis, and merging dev/prod configs.

Code Splitting & Dynamic Imports

// Dynamic import — webpack creates a separate chunk automatically
const { default: Chart } = await import('./Chart');

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

// Magic comments — control chunk behavior
import(
  /* webpackChunkName: "user-dashboard" */
  /* webpackPrefetch: true */         // <link rel="prefetch"> — load after idle
  /* webpackPreload: true */          // <link rel="preload"> — load in parallel
  './pages/Dashboard'
);

// SplitChunksPlugin — automatic chunk splitting
optimization: {
  splitChunks: {
    chunks: 'all',                    // async + initial chunks
    minSize: 20000,                   // min size to split (bytes)
    maxSize: 244000,                  // try to split larger chunks
    minChunks: 1,                     // min times chunk must be used
    maxAsyncRequests: 30,
    maxInitialRequests: 30,
    cacheGroups: {
      // Vendor chunk — node_modules
      defaultVendors: {
        test: /[\\/]node_modules[\\/]/,
        priority: -10,
        reuseExistingChunk: true,
        name(module) {
          const name = module.context.match(/[\\/]node_modules[\\/](.*?)(\/|$)/)[1];
          return `vendor.${name.replace('@', '')}`;
        },
      },
      default: {
        minChunks: 2,
        priority: -20,
        reuseExistingChunk: true,
      },
    },
  },
  runtimeChunk: 'single',           // extract webpack runtime for better caching
}

Tree Shaking & Caching

// Tree shaking — works automatically in production mode with ES modules
// Requirements:
// 1. Use ES module syntax (import/export, NOT require/module.exports)
// 2. Mark package as side-effect-free in package.json:
//    { "sideEffects": false }
//    { "sideEffects": ["*.css", "./src/polyfills.js"] }

// Named imports — webpack can tree-shake unused exports
import { debounce } from 'lodash-es';    // tree-shakeable
// import _ from 'lodash';              // NOT tree-shakeable (CJS)

// Long-term caching with contenthash
output: {
  filename: '[name].[contenthash:8].js',   // hash changes only when content changes
  chunkFilename: '[name].[contenthash:8].chunk.js',
},

// Persist cache between builds (webpack 5)
cache: {
  type: 'filesystem',                  // 'memory' (default) | 'filesystem'
  buildDependencies: {
    config: [__filename],              // invalidate cache when config changes
  },
  cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
},

// Speed up builds with thread-loader (parallel processing)
{
  test: /\.[jt]sx?$/,
  use: [
    {
      loader: 'thread-loader',
      options: { workers: require('os').cpus().length - 1 },
    },
    'babel-loader',
  ],
}

webpack-merge — Dev/Prod Configs

// webpack.common.js — shared config
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.tsx',
  resolve: { extensions: ['.tsx', '.ts', '.js'] },
  plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
};

// webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  devServer: { port: 3000, hot: true, historyApiFallback: true },
});

// webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map',
  output: { filename: '[name].[contenthash:8].js', clean: true },
  plugins: [new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css' })],
  optimization: {
    minimizer: [new TerserPlugin({ parallel: true })],
    splitChunks: { chunks: 'all' },
  },
});

// package.json scripts
// "dev": "webpack serve --config webpack.dev.js"
// "build": "webpack --config webpack.prod.js"
// "analyze": "ANALYZE=true webpack --config webpack.prod.js"

Bundle Analysis & Performance

# Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer

# Generate stats file
webpack --profile --json > stats.json

# Open bundle analyzer UI
npx webpack-bundle-analyzer stats.json dist

# Or use the plugin (set ANALYZE=true env var)
# const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
# new BundleAnalyzerPlugin({ analyzerMode: 'static' })

# Measure build speed
npm install --save-dev speed-measure-webpack-plugin

# Check individual asset sizes
npx bundlesize     # define limits in package.json bundlesize field

# Lighthouse CI for performance regression
npx lhci autorun

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free