Internals and build performance

webpack · Expert · 15 min

Large webpack builds can take minutes; understanding the internals points to the real fixes.

Inside a compilation, webpack builds a module graph (modules and their dependency edges), then a chunk graph (which modules belong to which chunks, and how chunk groups load each other). The runtime in the output implements __webpack_require__: a module cache, a registry of module factory functions, and chunk loading via JSONP-style script tags or import() for ESM output. Reading a development bundle (devtool: false, mode: "development") makes all of this visible.

Where time goes in slow builds, usually:

  • Loaders, especially Babel and ts-loader with type checking. Replace with swc-loader or esbuild-loader, and type-check in a separate process (fork-ts-checker-webpack-plugin or tsc --noEmit).
  • Minification: use the SWC or esbuild minifier via TerserPlugin.swcMinify / esbuildMinify.
  • Source maps: expensive variants in development.
  • Too much work: missing include/exclude on rules, large resolve.extensions, building unused locales (moment.js), and no persistent cache.

Measure before changing: npx webpack --profile --json > stats.json for the analyzer, speed-measure-webpack-plugin for per-loader timings, and ProgressPlugin for phase timing.

When a codebase outgrows these fixes, Rspack (Rust, webpack-compatible API and most loaders/plugins) often gives 5-10x faster builds with modest migration effort, while new projects typically choose Vite.

Example

// Simplified view of what webpack's runtime does (development output)
var __webpack_modules__ = {
  "./src/greet.js": (module, exports, __webpack_require__) => {
    exports.greet = (n) => `Hello, ${n}`;
  },
  "./src/index.js": (module, exports, __webpack_require__) => {
    const { greet } = __webpack_require__("./src/greet.js");
    console.log(greet("Ann"));
  },
};
var __webpack_module_cache__ = {};
function __webpack_require__(id) {
  if (__webpack_module_cache__[id]) return __webpack_module_cache__[id].exports;
  const module = (__webpack_module_cache__[id] = { exports: {} });
  __webpack_modules__[id](module, module.exports, __webpack_require__);
  return module.exports;
}
__webpack_require__("./src/index.js"); // Hello, Ann
// Faster builds: { loader: "swc-loader" } instead of babel-loader,
// cache: { type: "filesystem" }, and npx webpack --profile --json > stats.json

Remember

  • Module graph -> chunk graph -> runtime with __webpack_require__
  • Loaders, minifiers and source maps dominate build time
  • Profile with --profile --json and per-loader timings
  • Rspack is the drop-in path to much faster builds

Watch out for

  • Optimizing config blindly without profiling which loader or plugin is slow.

Check yourself

  1. What does `__webpack_require__` do with a module that was already executed?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: webpack.js.org/guides/build-performance/