All topics
Frontend · Learning hub

Webpack notes for developers

Master Webpack with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
Webpack

Webpack Configuration

Webpack Configuration Core Concepts Entry — starting point(s) of the dependency graph Output — where to emit bundled files and how to name them Loaders — transf

Webpack Configuration

Core Concepts

  • Entry — starting point(s) of the dependency graph

  • Output — where to emit bundled files and how to name them

  • Loaders — transform non-JS files (CSS, images, TypeScript) into modules

  • Plugins — extend webpack capabilities (HTML generation, bundle analysis, env vars)

  • Mode — development (fast build, no minification) or production (minified, optimized)

  • Code splitting — split bundle into chunks loaded on demand (dynamic import, SplitChunksPlugin)

webpack.config.js

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 isDev = process.env.NODE_ENV !== 'production';

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

  entry: { main: './src/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',
    clean: true,              // clean dist before each build
    publicPath: '/',          // base path for all assets
  },

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

  module: {
    rules: [
      // TypeScript / JavaScript
      {
        test: /.[jt]sx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',
              '@babel/preset-react',
              '@babel/preset-typescript',
            ],
          },
        },
      },
      // CSS / SCSS
      {
        test: /.(css|scss)$/,
        use: [
          isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
          { loader: 'css-loader', options: { modules: true } },
          'sass-loader',
        ],
      },
      // Images / fonts
      {
        test: /.(png|jpg|gif|svg|woff2?)$/,
        type: 'asset',          // webpack 5 Asset Modules
        parser: { dataUrlCondition: { maxSize: 8 * 1024 } },  // inline if < 8KB
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    !isDev && new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css' }),
    process.env.ANALYZE && new BundleAnalyzerPlugin(),
  ].filter(Boolean),

  optimization: {
    minimizer: [new TerserPlugin()],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
    runtimeChunk: 'single',   // separate runtime chunk for better caching
  },

  devServer: {
    port: 3000,
    hot: true,
    historyApiFallback: true,  // SPA routing
    proxy: { '/api': 'http://localhost:4000' },
  },

  devtool: isDev ? 'eval-source-map' : 'source-map',
};

Performance Tips

  • Cache loaders — babel-loader with cacheDirectory: true, thread-loader for CPU-heavy transforms

  • DLLPlugin — pre-build vendor libs (React, lodash) separately; rarely re-compile them

  • Tree shaking — use ES modules (import/export, not require), enable sideEffects: false in package.json

  • Dynamic imports — const Modal = React.lazy(() => import('./Modal')) — loads chunk only when needed

  • Bundle analysis — webpack-bundle-analyzer to visualise what is large; look for duplicate deps, large libs

Webpack

Webpack Core Concepts

Webpack Core Concepts Webpack is a static module bundler for JavaScript applications. It builds a dependency graph and emits one or more optimized bundles. Entr

Webpack Core Concepts

Webpack is a static module bundler for JavaScript applications. It builds a dependency graph and emits one or more optimized bundles.

Entry, Output & Resolve

const path = require('path');

module.exports = {
  mode: 'development',            // 'development' | 'production' | 'none'

  // Entry — one or multiple starting points
  entry: {
    main: './src/index.tsx',
    admin: './src/admin/index.tsx',
  },
  // Single entry shorthand: entry: './src/index.tsx'

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js',      // [name] = entry key
    chunkFilename: '[name].[contenthash:8].chunk.js',
    assetModuleFilename: 'assets/[hash][ext][query]',
    clean: true,                  // remove dist before each build
    publicPath: '/',              // base URL prefix for all assets
  },

  resolve: {
    extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
    },
    // Prefer ES module fields (tree-shakeable)
    mainFields: ['module', 'browser', 'main'],
  },
};

Loaders — Transform Non-JS Files

module: {
  rules: [
    // TypeScript & JavaScript via Babel
    {
      test: /\.[jt]sx?$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,         // cache transforms (speeds up rebuilds)
          presets: [
            ['@babel/preset-env', { targets: '> 0.25%, not dead' }],
            ['@babel/preset-react', { runtime: 'automatic' }],
            '@babel/preset-typescript',
          ],
          plugins: ['@babel/plugin-transform-class-properties'],
        },
      },
    },

    // CSS + SCSS + CSS Modules
    {
      test: /\.(css|scss)$/,
      use: [
        isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
        {
          loader: 'css-loader',
          options: {
            modules: {
              auto: true,               // enable CSS Modules for *.module.css
              localIdentName: isDev
                ? '[name]__[local]'
                : '[hash:base64:5]',
            },
            importLoaders: 2,
          },
        },
        'postcss-loader',               // Tailwind, autoprefixer
        'sass-loader',
      ],
    },

    // Static assets — webpack 5 Asset Modules (no file-loader/url-loader needed)
    {
      test: /\.(png|jpg|gif|webp|avif|svg)$/i,
      type: 'asset',                    // auto: inline <8KB, file >=8KB
      parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
    },
    {
      test: /\.(woff2?|eot|ttf|otf)$/i,
      type: 'asset/resource',           // always emit as file
    },
  ],
}

Plugins

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

plugins: [
  // Generate index.html with injected script/link tags
  new HtmlWebpackPlugin({
    template: './public/index.html',
    favicon: './public/favicon.ico',
    minify: !isDev,
  }),

  // Extract CSS into separate files (production)
  !isDev && new MiniCssExtractPlugin({
    filename: '[name].[contenthash:8].css',
    chunkFilename: '[name].[contenthash:8].chunk.css',
  }),

  // Inject env vars as global constants
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    __APP_VERSION__: JSON.stringify(require('./package.json').version),
  }),

  // Progress output during build
  new webpack.ProgressPlugin(),

  // Bundle visualizer — set ANALYZE=true to enable
  process.env.ANALYZE && new BundleAnalyzerPlugin({
    analyzerMode: 'static',
    openAnalyzer: true,
  }),
].filter(Boolean),

optimization: {
  minimizer: [
    '...',                          // keep default JS minifier (TerserPlugin)
    new CssMinimizerPlugin(),
  ],
}

DevServer

devServer: {
  port: 3000,
  open: true,
  hot: true,                      // HMR
  compress: true,                 // gzip compression
  historyApiFallback: true,       // SPA — serve index.html for 404s
  static: {
    directory: path.join(__dirname, 'public'),
  },
  proxy: [
    {
      context: ['/api', '/auth'],
      target: 'http://localhost:4000',
      changeOrigin: true,
    },
  ],
  // HTTPS with self-signed cert
  // server: 'https',
  client: {
    overlay: { errors: true, warnings: false },
    progress: true,
  },
},

// Source maps
devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map',
// eval-cheap-module-source-map — fast rebuilds, good error messages (dev)
// source-map                   — full source maps (prod, slower)
// hidden-source-map            — no browser exposure (prod + Sentry)
Webpack

Webpack Optimization

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

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 Webpack knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever