Deep Dive into Webpack Runtime: Chunk, Runtime Modules, and Build Process
This article provides an in‑depth technical analysis of Webpack's runtime, explaining how bundles are composed of business code and runtime, the generation of __webpack_require__ helpers, the mechanisms behind asynchronous chunk loading, hot module replacement, and the internal dependency‑collection process that drives the final asset emission.
Background
In the previous article "A Bit Hard Webpack Knowledge Point: Chunk Splitting Rules Explained" we covered Webpack's default chunking rules and part of the seal phase. This article continues the deep dive by following Webpack's execution flow to analyse the implementation principles, focusing on the build products, runtime, asynchronous loading, and HMR.
What does a Webpack build product contain? How does it support modularisation, async loading, and HMR?
What is the runtime? How does Webpack collect runtime dependencies and merge them into the final bundle ?
Although the series may not solve immediate business problems, it cultivates the ability to analyse complex open‑source code, understand Webpack's architecture, and create custom plugins or loaders.
Compilation Product Analysis
Webpack bundles both the developer's business code and the supporting "runtime" into the final bundle. The business code is like bricks, while the runtime is the hidden steel framework that enables features such as async loading, HMR, WASM, and Module Federation.
Basic Structure
Consider the following minimal example:
// a.js
export default 'a module';
// index.js
import name from './a';
console.log(name);Webpack configuration:
module.exports = {
entry: "./src/index",
mode: "development",
devtool: false,
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist")
}
};The generated bundle is wrapped in an IIFE and contains several key objects and functions:
__webpack_modules__ – stores all modules except the entry (e.g., a.js ).
__webpack_module_cache__ – caches already executed modules.
__webpack_require__ – implements the module loading logic.
__webpack_require__.d , __webpack_require__.o , __webpack_require__.r – utility helpers for exporting, property checking, and ESM flagging.
The final IIFE executes the entry module ( index.js ).
The core of the runtime is the __webpack_require__ function, whose implementation looks like this:
function __webpack_require__(moduleId) {
// If the module is cached, return its exports
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module and put it into the cache
var module = (__webpack_module_cache__[moduleId] = {
exports: {}
});
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}This function resolves a module by its moduleId , executes it once, caches the result, and returns the exported value.
Asynchronous Module Loading
Example:
// ./src/a.js
export default "module-a";
// ./src/index.js
import('./a').then(console.log);Webpack adds the following runtime helpers for async loading:
__webpack_require__.e – wraps a Promise‑based middleware to load multiple chunks.
__webpack_require__.f – middleware container used by e .
__webpack_require__.u – constructs the async chunk filename.
__webpack_require__.l – JSONP‑based script loader.
__webpack_require__.p – base URL for calculating the final chunk URL.
The core async loader is implemented as:
__webpack_require__.e = (chunkId) => {
return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
__webpack_require__.f[key](chunkId, promises);
return promises;
}, []));
};And the script loader:
var inProgress = {};
__webpack_require__.l = (url, done, key, chunkId) => {
if (inProgress[url]) { inProgress[url].push(done); return; }
var script = document.createElement('script');
// ... set attributes, error/timeout handling ...
inProgress[url] = [done];
script.onload = onScriptComplete.bind(null, script.onload);
script.onerror = onScriptComplete.bind(null, script.onerror);
document.head.appendChild(script);
};Calling __webpack_require__.e('src_a_js') triggers the async loading of the chunk containing a.js .
Module Hot Replacement (HMR)
HMR enables developers to replace changed modules in the browser without a full page reload. Enabling HMR requires a development server configuration:
module.exports = {
entry: "./src/index",
mode: "development",
devtool: false,
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist")
},
plugins: [new HtmlWebpackPlugin({ title: "Hot Module Replacement" })],
devServer: {
contentBase: "./dist",
hot: true,
writeToDisk: true
}
};Running webpack serve --hot-only produces a bundle whose runtime exceeds 15 k lines, adding helpers such as webpack-dev-server , webpack/hot/… , and additional __webpack_require__ functions for HMR handling.
Implementation Principle
Two main steps drive runtime generation during the compilation.seal phase:
Dependency Collection : Traverse all modules, analyse their AST, and record required runtime features.
Generation : Merge the collected runtime requirements into RuntimeModule instances and embed them into the final chunks.
The collection happens in three loops:
First loop – gather runtime requirements from each module.
Second loop – aggregate module requirements into their owning chunks.
Third loop – transform requirement identifiers into concrete RuntimeModule objects via the runtimeRequirementInTree hook.
Dependency Collection Details
During the seal phase, after ChunkGraph is built, Webpack calls module.codeGeneration for each module, producing sources (the transformed code) and runtimeRequirements (the set of needed runtime helpers). These requirements are stored back into ChunkGraph .
First Loop – Module Requirements
Each module's runtimeRequirements are recorded in the graph.
Second Loop – Chunk Aggregation
All modules belonging to a chunk contribute their runtime requirements, resulting in a unified set per chunk.
Third Loop – RuntimeModule Creation
For each runtime chunk, Webpack publishes runtimeRequirementInTree events. RuntimePlugin listens and creates concrete RuntimeModule subclasses such as MakeNamespaceObjectRuntimeModule (for __webpack_require__.r ), HasOwnPropertyRuntimeModule (for __webpack_require__.o ), and EnsureChunkRuntimeModule (for __webpack_require__.e ).
Asset Emission
After runtime collection, Webpack emits assets:
compilation.createChunkAssets merges business modules and runtime modules into a single Source per chunk.
compilation.emitAsset registers the source in compilation.assets .
compiler.emitAssets writes the assets to the file system.
compiler.hooks.done signals the end of the build.
Open Issues (Digging Deeper)
What roles do the other Module subclasses (besides NormalModule and RuntimeModule ) play?
How exactly is a single module transformed and how are its runtime dependencies computed?
Beyond storing runtimeRequirements , what additional responsibilities does ChunkGraph have?
These questions illustrate the depth of Webpack's architecture and provide directions for future exploration.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.