Frontend Development 9 min read

Implementing Hot Module Replacement (HMR) for esbuild: Module Loader, Resolver, and Bundling Strategies

This article describes how to add Hot Module Replacement to esbuild by disabling scope hosting, converting modules to CommonJS, building a custom ModuleLoader and resolver, handling module URLs, and comparing two bundling approaches while also covering React Refresh integration.

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
Implementing Hot Module Replacement (HMR) for esbuild: Module Loader, Resolver, and Bundling Strategies

esbuild is a very fast bundler but it does not provide Hot Module Replacement (HMR); developers must rely on full page reloads, which hurts the development experience. To address this, the article proposes adding Bundler‑based HMR and documents the challenges encountered.

Module Loader : Because esbuild’s scope‑hosting optimization blurs module boundaries, it must be disabled, forcing a custom bundling step. Inspired by Webpack, each module’s code is transformed to CommonJS using esbuild’s transform API, with special handling for dynamic imports, runtime helpers, and macro replacements (e.g., process.env.NODE_ENV ). The transformed code is then wrapped in a custom ModuleLoader runtime that preserves __esModule flags and computed exports.

// a.ts
import { value } from 'b'

moduleLoader.registerLoader('a'/* /path/to/a */, (require, module, exports) => {
  const { value } = require('b');
});

The loader’s exports setter copies enumerable properties and respects the __esModule marker, ensuring compatibility with both ESM‑to‑CJS conversion and circular dependencies.

Module Resolver : After conversion, the system cannot resolve aliases or node_modules paths. Two solutions are discussed: (1) rewriting import URLs (as Webpack/Vite do) and (2) maintaining a runtime mapping table. The article chooses the mapping table approach to avoid the overhead of static analysis.

moduleLoader.registerResolver('a'/* /path/to/a */, {
  'b': '/path/to/b'
});

HMR Mechanism : When a module changes, the HMR API ( module.hot.accept ) marks the module as a boundary. The runtime builds a minimal HMR bundle for the changed module, notifies the browser via WebSocket, and the browser re‑executes the updated module and its callbacks based on the dependency graph.

Bundling Strategies : Two implementations are compared. The first uses magic-string with source‑map remapping, but suffers from performance penalties due to lack of caching. The second leverages webpack-sources ( ConcatSource , CachedSource , SourceMapSource ) which caches sourcemaps and yields a 10‑plus× speedup on incremental builds.

import { ConcatSource, CachedSource, SourceMapSource } from 'webpack-sources';

function bundle() {
  const concat = new ConcatSource();
  concat.add(module1);
  concat.add(module2);
  const { source, map } = concat.sourceAndMap();
  return { code: source, map };
}

The article also notes that esbuild’s production‑mode metafile.inputs may omit pure‑code modules, requiring a full module‑graph traversal to ensure all code is included.

Additional Topics : A brief discussion of lazy compilation (dynamic imports) and an outline of integrating React Refresh, including the Babel plugin transformation, runtime registration, and entry‑point injection, is provided.

var _jsxDevRuntime = require('node_modules/react/jsx-dev-runtime.js');
function FunctionDefault() {
  return _jsxDevRuntime.jsxDEV('h1', { children: 'Default Export Function' }, 0, false, { fileName: '<...>', lineNumber: 2, columnNumber: 10 }, this);
}
frontendesbuildBundlerHMRmodule-loaderReact Refresh
ByteDance Web Infra
Written by

ByteDance Web Infra

ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.