cheat sheet

webpack

Package-level reference for webpack on npm — loaders, plugins, code splitting, asset modules, module federation, and the slow migration toward Rspack and Vite.

webpack

What it is

webpack is the classic JavaScript module bundler — the tool that made it possible to write import statements across thousands of files and ship one (or a few) optimised browser bundles. It pioneered loaders (transform any file type into a module), plugins (hook into the build lifecycle), code splitting, tree shaking, and module federation. For most of the 2010s, "JavaScript build" meant webpack.

It's no longer the default for new apps — Vite, Rspack, Rolldown, and esbuild-based pipelines have eaten that market. But it remains overwhelmingly the most-used bundler in production: every Create React App descendant, the older Next.js builds, most enterprise React/Vue/Angular setups, and every project that depends on a webpack-only loader or plugin is still on webpack. Its plugin ecosystem is the largest of any bundler — by an order of magnitude.

Install

bash
# Project-local (always — webpack should never be global)
npm install -D webpack webpack-cli
pnpm add -D webpack webpack-cli
yarn add -D webpack webpack-cli

Output: webpack binary in node_modules/.bin/; ready to run via npx webpack or in npm scripts.

bash
# Dev server (separate package)
npm install -D webpack-dev-server

Output: adds the webpack serve subcommand for local development with HMR.

bash
# Common companion loaders/plugins
npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react
npm install -D css-loader style-loader mini-css-extract-plugin
npm install -D html-webpack-plugin

Output:

text
added 84 packages in 12s
added 5 packages in 2s
added 1 package in 1s

Versioning & Node support

  • Current line is 5.x (released October 2020). Long-running 5.x — no 6.x announced as of writing.
  • Requires Node 10.13+ for webpack 5; in practice all modern Node releases work.
  • webpack-cli is a separate package; pin both to the same lockfile.
  • SemVer respected for the core, but loaders and plugins often have their own breaking changes. Pin everything in lockfile.
  • Long-term maintenance is steady. Public roadmap: the team focuses on Rspack (drop-in Rust port) as the path forward, while webpack itself receives bug-fix releases.

Package metadata

  • Maintainers: Tobias Koppers + webpack core team / OpenJS Foundation
  • Project home: github.com/webpack/webpack
  • Docs: webpack.js.org
  • npm: npmjs.com/package/webpack
  • License: MIT
  • First released: 2012
  • Downloads: still ~30M+ weekly — top-tier despite the shift to Vite for new projects.

Peer dependencies & extras

webpack itself has zero peers. Its plugin ecosystem is huge — a few that nearly every project uses:

PackageRole
webpack-cliCLI front-end for the webpack binary; required for npx webpack.
webpack-dev-serverLocal dev with HMR and proxy.
babel-loaderTranspile JS/TS/JSX via Babel. Most common loader.
ts-loader / swc-loader / esbuild-loaderTypeScript transpilers; swc-loader is the fastest.
css-loader, style-loader, sass-loader, postcss-loaderCSS pipeline.
mini-css-extract-pluginEmit CSS to separate .css files in prod.
html-webpack-pluginGenerate index.html referencing emitted assets.
terser-webpack-pluginJS minifier (default in prod mode).
webpack-bundle-analyzerTreemap of bundle composition.
@module-federation/*Module Federation runtime + helpers (now also works in Rspack and Vite).

Alternatives

ToolTrade-off
ViteESM-first dev server, Rollup-based prod builds. Default for new SPAs. Drops some webpack-specific patterns (Module Federation MVP works but matured slower).
RspackRust port of webpack with API compatibility. ~10× faster builds. The recommended migration for performance-sensitive webpack projects.
RolldownRust rewrite of Rollup. Aiming for Rollup-compat first; webpack-compat is a stretch goal.
esbuildSingle-binary Go bundler. Extremely fast; minimal plugin ecosystem; less ergonomic for SPA dev.
ParcelZero-config bundler. Great for prototypes; smaller plugin ecosystem.
TurbopackRust bundler from Vercel, default in Next.js. Tied to Next; not yet a general drop-in.
swc / oxcTransformers, not bundlers — usable inside webpack via loaders.

Real-world recipes

Minimal webpack config

javascript
// webpack.config.js
const path = require("node:path");

module.exports = {
  mode: "production",
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.[contenthash].js",
    clean: true,
  },
};
bash
npx webpack

Output:

text
asset bundle.f3a9c2d8.js 1.2 KiB [emitted] [immutable]
webpack 5.96.0 compiled successfully in 154 ms

The [contenthash] placeholder gives long-cache filenames; clean: true wipes dist/ before each build.

Loader chain — TypeScript + CSS + assets

javascript
// webpack.config.js
module.exports = {
  mode: "development",
  entry: "./src/index.tsx",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [{ loader: "swc-loader", options: { jsc: { parser: { syntax: "typescript", tsx: true } } } }],
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /\.(png|jpg|svg)$/,
        type: "asset/resource",
      },
    ],
  },
  resolve: { extensions: [".ts", ".tsx", ".js"] },
};

Loaders apply right-to-left: css-loader parses imports, postcss-loader transforms, style-loader injects <style> at runtime. Reverse order = silent failure.

Code splitting

javascript
// webpack.config.js
module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
          name: "react",
          chunks: "all",
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor",
          chunks: "all",
          priority: -10,
        },
      },
    },
  },
};
javascript
// src/index.js — dynamic import emits a separate chunk
const Settings = () => import(/* webpackChunkName: "settings" */ "./Settings");

Output: three chunks — react.js, vendor.js, settings.js — loaded on demand.

Tree shaking

javascript
// webpack.config.js — production mode enables this by default
module.exports = {
  mode: "production",
  optimization: {
    usedExports: true,
    sideEffects: true,
  },
};
json
// package.json — tell webpack which files have side effects
{
  "sideEffects": ["*.css", "./src/polyfills.js"]
}

sideEffects: false for the whole package means webpack can drop any unused export. "sideEffects": [...] whitelists files that must remain even when no export is imported (e.g. CSS, polyfills, automatic registration).

Multi-target build

javascript
// webpack.config.js — array config builds multiple bundles in one run
module.exports = [
  {
    name: "web",
    target: "web",
    entry: "./src/web/index.ts",
    output: { path: __dirname + "/dist/web", filename: "bundle.js" },
  },
  {
    name: "node",
    target: "node",
    entry: "./src/server/index.ts",
    output: { path: __dirname + "/dist/server", filename: "bundle.cjs" },
    externals: ["express"],
  },
];
bash
npx webpack --config-name web   # build a single target by name

Output:

text
[web]    asset bundle.js 154 KiB
[node]   asset bundle.cjs 89 KiB

Module Federation (multi-app composition)

javascript
// host webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        checkout: "checkout@https://checkout.example.com/remoteEntry.js",
      },
      shared: { react: { singleton: true }, "react-dom": { singleton: true } },
    }),
  ],
};
javascript
// host application code
const Checkout = React.lazy(() => import("checkout/CartPage"));

Module Federation is webpack's killer feature for micro-frontends — runtime composition of independently-deployed apps sharing dependencies. Rspack mirrors the API; Vite has a community plugin (@originjs/vite-plugin-federation) that's close but not perfect.

Production deployment

javascript
// webpack.prod.js
module.exports = {
  mode: "production",
  devtool: "source-map",   // separate .map; do NOT ship
  output: {
    filename: "[name].[contenthash].js",
    publicPath: "/assets/",
  },
  optimization: {
    minimize: true,
    runtimeChunk: "single",
    moduleIds: "deterministic",
  },
  performance: {
    hints: "warning",
    maxAssetSize: 250_000,
    maxEntrypointSize: 250_000,
  },
};

Run:

bash
NODE_ENV=production npx webpack --config webpack.prod.js

Output:

text
asset main.f3a9c2d8.js 47 KiB [emitted] [immutable] [minimized]
asset main.f3a9c2d8.js.map 144 KiB [emitted] [dev]
asset vendor.b1d4ac88.js 89 KiB [emitted] [immutable] [minimized]
webpack 5.96.0 compiled with 2 warnings in 1893 ms
  • Always hash filenames + immutable cache headers for the assets, no-cache on index.html.
  • devtool: "source-map" emits separate .map files; upload to your error tracker (Sentry / Datadog), don't ship publicly.
  • runtimeChunk: "single" extracts the runtime so app code can change without invalidating the runtime chunk.

Performance tuning

webpack is slow by default. The five knobs that matter most:

Swap babel-loader for swc-loader or esbuild-loader

javascript
{
  test: /\.tsx?$/,
  use: { loader: "swc-loader" },     // ~5–10× faster than babel-loader
}

This is usually the single biggest speedup — Babel parses every file with a slow JS-implemented AST.

cache: { type: "filesystem" }

javascript
module.exports = {
  cache: {
    type: "filesystem",
    buildDependencies: { config: [__filename] },
  },
};

Persists compiled artefacts across builds. First build is normal; subsequent builds are ~3× faster.

Narrow the loader test scope

javascript
{
  test: /\.tsx?$/,
  include: path.resolve(__dirname, "src"),
  exclude: /node_modules/,
  // ...
}

include is faster than exclude (positive matching short-circuits). Excluding node_modules is almost always correct unless you have an ESM-only dep that needs transpiling.

Parallel minification

javascript
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  optimization: {
    minimizer: [new TerserPlugin({ parallel: true })],
  },
};

Default parallel: true since webpack 5 — confirm in your config that no other minimizer overrides it.

Profile the build

bash
npx webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json

Output: opens a treemap visualisation. Look for huge transitive deps (lodash, moment, three.js) and replace with smaller alternatives.

Version migration guide

From → ToHighlights
3 → 4`mode: "development"
4 → 5Big migration. Node polyfills removed (Buffer, process, path no longer auto-bundled — must resolve.fallback). Asset Modules replaced file-loader / url-loader / raw-loader. Module Federation introduced. Long-term caching by default.
5.x ongoingMinor releases add Rspack-compatibility shims. Stable since 2020. No 6.x roadmap.

The 4→5 migration is the painful one. Common breakages:

  • Module not found: Can't resolve 'path' (or 'crypto') — add resolve.fallback: { path: require.resolve("path-browserify"), ... } or remove the Node-only import from browser code.
  • file-loader / url-loader no longer auto-installed — replace with Asset Modules (type: "asset/resource", type: "asset/inline").
  • IgnorePlugin constructor signature changed.

Security considerations

  1. Supply-chain risk in loaders/plugins. webpack's plugin API runs full Node — a malicious loader can read your filesystem, exfiltrate env vars, or inject into emitted bundles. Pin loader versions in lockfile; audit suspicious additions.
  2. Bundle source maps. devtool: "source-map" writes separate .map files; do NOT ship them publicly. Either upload to Sentry/Datadog and skip the public copy, or use devtool: "hidden-source-map" (no //# sourceMappingURL comment).
  3. DefinePlugin substitutions are literal. new DefinePlugin({ API_KEY: process.env.API_KEY }) inlines whatever API_KEY is at build time into bundle source. Never DefinePlugin server secrets.
  4. webpack-dev-server exposure. host: "0.0.0.0" binds on every interface. Anyone on your LAN can hit the dev server and download source. Default to localhost.
  5. Module Federation cross-origin trust. Loading a federated remote runs code from another origin in your security context. Pin the remoteEntry URL, enforce CSP, audit the remote's deploy process.
  6. CSP and inline scripts. Default webpack output includes an inline runtime — incompatible with strict CSP. Use output.scriptType: "module" + runtimeChunk: "single" and serve the runtime via <script src=...>.

Testing & CI integration

webpack isn't a test runner. Pair with:

  • Jest — has its own transform pipeline (babel-jest / ts-jest); doesn't use webpack.
  • Vitest / Playwright — completely independent of webpack.
  • storybook — uses webpack (or Vite) under the hood for its dev server.

CI integration is just npx webpack --config webpack.prod.js && verify — make sure the build is reproducible and fail the pipeline on warnings if your config opts into strict performance budgets.

yaml
# .github/workflows/ci.yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx webpack --config webpack.prod.js
      - uses: actions/upload-artifact@v4
        with: { name: dist, path: dist/ }

Ecosystem integrations

  • React — Create React App, Next.js (legacy webpack mode), Storybook.
  • Vue — Vue CLI (now in maintenance; Vite is the recommended path).
  • Angular — historically uses webpack via @angular-devkit/build-angular; newer versions also offer esbuild.
  • NestJS — uses webpack for the --webpack build flag (alternative to tsc-based output).
  • Module Federation — webpack's killer feature. Rspack now matches the API; Vite has community plugins.
  • Storybook — supports both webpack and Vite; webpack remains the default for many addons.

Troubleshooting common errors

Module not found: Can't resolve 'X'

The import path doesn't resolve. Causes:

  • Missing npm install X.
  • resolve.extensions doesn't include the file's extension (.ts, .tsx).
  • resolve.alias mismatched a real path.
  • Node-only module imported from browser code — add to resolve.fallback or remove the import.

You may need an appropriate loader to handle this file type

A loader rule's test regex doesn't match the file. Either fix the regex or add a new rule. Common pitfall: forgetting .tsx? to cover both .ts and .tsx.

Build is slow (10+ seconds for a small project)

Almost always babel-loader on every file. Switch to swc-loader or esbuild-loader. Enable cache: { type: "filesystem" }.

Cannot read properties of undefined (reading 'tap')

A plugin is expected by another plugin but isn't loaded — usually a plugin order issue. Check that all plugins are constructed (new MyPlugin()), not just referenced.

Hot Module Replacement doesn't fire

webpack-dev-server must be in HMR mode (hot: true), and a module must call module.hot.accept(). Framework loaders (react-refresh-webpack-plugin) wire this for components; arbitrary code falls back to full reload.

Bundle is huge despite tree shaking

Mostly: a transitive dep is CJS-only (CJS exports can't be tree-shaken). Use webpack-bundle-analyzer to find the culprit; swap for an ESM-only alternative (lodashlodash-es, date-fns already ESM, momentdayjs).

When NOT to use this

  • New SPAs. Vite is faster, simpler, ESM-first. Pick it for green-field projects.
  • Performance-critical builds. Rspack is a near-drop-in webpack-API-compatible Rust port. ~10× faster builds. The recommended migration target for "we love our webpack config, hate the build time" teams.
  • Pure libraries. Rollup or tsup produces cleaner outputs (smaller, with proper ESM exports).
  • Server-side bundling. Most Node code doesn't need bundling; ship source. Bundle only if the runtime requires it (Lambdas with cold-start constraints, edge workers).
  • Greenfield with no Module Federation need. webpack's main moat is the plugin/loader catalogue and Module Federation. Without those, Vite is strictly easier.

See also