In‑Depth Source Code Analysis of webpack‑dev‑middleware
This article provides a detailed walkthrough of webpack-dev-middleware version 3.7.2, explaining its purpose, key features, and the core implementation of its index.js and middleware.js files, including code snippets and how it integrates with Express to serve compiled assets from memory during development.
Webpack has become an essential tool for front‑end developers. While a simple devServer configuration starts webpack-dev-server, the underlying webpack-dev-middleware package provides the core functionality that serves compiled assets directly from memory.
The article focuses on [email protected], presenting the source code of its most important modules: index.js and middleware.js. It first shows how to use the package with an Express server:
const wdm = require('webpack-dev-middleware');
const express = require('express');
const webpack = require('webpack');
const webpackConf = require('./webapck.conf.js');
const compiler = webpack(webpackConf);
const app = express();
app.use(wdm(compiler));
app.listen(8080);The call wdm(compiler) returns an Express middleware that wraps the compiler and handles requests for compiled files.
Why use webpack‑dev‑middleware? Unlike the default watch mode, which writes bundles to disk after each compilation, the middleware keeps bundles in memory, eliminating costly I/O and allowing the server to serve the latest assets instantly. Its three main characteristics are:
Runs webpack in watch mode, automatically recompiling on file changes.
Delays requests until the new compilation finishes, preventing stale bundles.
Stores compiled files in an in‑memory filesystem, reducing disk access latency.
The source tree of the package looks like this:
...<br/>├── lib<br/>│ ├── DevMiddlewareError.js<br/>│ ├── index.js<br/>│ ├── middleware.js<br/>│ └── utils<br/>│ ├── getFilenameFromUrl.js<br/>│ ├── handleRangeHeaders.js<br/>│ ├── index.js<br/>│ ├── ready.js<br/>│ ├── reporter.js<br/>│ ├── setupHooks.js<br/>│ ├── setupLogger.js<br/>│ ├── setupOutputFileSystem.js<br/>│ ├── setupRebuild.js<br/>│ └── setupWriteToDisk.js<br/>├── package.json<br/>...The lib directory contains the core logic, but the most critical files are index.js and middleware.js.
index.js
This file creates the middleware container. It receives the webpack compiler and an optional configuration object, sets up hooks, starts the compiler in watch mode, and replaces the compiler’s output file system with an in‑memory MemoryFileSystem. The three essential steps are:
// start watching
context.watching = compiler.watch(options.watchOptions, (err) => {
if (err) {
context.log.error(err.stack || err);
if (err.details) {
context.log.error(err.details);
}
}
});
setupHooks(context);
setupOutputFileSystem(compiler, context); setupHooksregisters callbacks for the compiler’s lifecycle events ( invalid, run, done, watchRun) to report status and manage pending requests. setupOutputFileSystem swaps the default file system with a memory‑based one, ensuring that bundles are written to RAM instead of disk.
middleware.js
This file returns the actual Express middleware function. It first checks whether the request method is allowed (default GET and HEAD) and then resolves the requested URL to a filename in the in‑memory file system using getFilenameFromUrl. If the file exists, it reads the content and sends the response; otherwise it calls next() to pass control to later middleware.
function goNext() {
if (!context.options.serverSideRender) {
return next();
}
return new Promise((resolve) => {
ready(context, () => {
res.locals.webpackStats = context.webpackStats;
res.locals.fs = context.fs;
resolve(next());
}, req);
});
}
const acceptedMethods = context.options.methods || ['GET', 'HEAD'];
if (acceptedMethods.indexOf(req.method) === -1) {
return goNext();
}
let filename = getFilenameFromUrl(context.options.publicPath, context.compiler, req.url);
if (filename === false) {
return goNext();
}The ready helper decides whether to execute the request handler immediately (if the compilation is finished) or to queue it in context.callbacks until the next done hook fires.
if (context.state) {
return fn(context.webpackStats);
}
context.log.info(`wait until bundle finished: ${req.url || fn.name}`);
context.callbacks.push(fn);Through these mechanisms, webpack-dev-middleware provides fast, memory‑based serving of assets during development, automatically recompiling on changes and ensuring that only the latest bundle is delivered.
Conclusion
Reading the source code of webpack-dev-middleware reveals how it achieves its three main features—watch‑mode compilation, in‑memory asset storage, and request postponement until a fresh bundle is ready—by wiring webpack’s compiler hooks into an Express middleware pipeline.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
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.
