From 0e1013406c679adca47f63330bf058f67a3670b3 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Mon, 13 Jul 2026 22:36:39 -0500 Subject: [PATCH] refactor(hot): drop the module map from the SSE payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit webpack-dev-server does not send module names over the wire: the HMR runtime logs module ids on apply. Align the SSE payload with that (name, action, time, hash, errors, warnings) instead of serializing a module id → name map that was only used for log cosmetics and was empty with the default stats options anyway. Ref webpack/webpack-hot-middleware#452 Ref webpack/webpack-hot-middleware#306 --- README.md | 2 +- client-src/index.js | 4 ++-- client-src/process-update.js | 7 +++--- src/hot.js | 21 ------------------ test/client.test.js | 2 -- test/helpers/sse.js | 1 - test/hot.test.js | 41 +++++++++++++++++++++++++----------- types/hot.d.ts | 14 ------------ 8 files changed, 35 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 676495526..78df93da3 100644 --- a/README.md +++ b/README.md @@ -349,7 +349,7 @@ Heartbeat interval (in milliseconds) used to keep the SSE connection alive when Type: `Boolean | Object` Default: `undefined` -Webpack stats options used when serializing compilation results for the SSE payload. Forwarded to `stats.toJson(...)`. By default only the minimal stats needed by the client are requested (`hash`, `timings`, `errors`, `warnings`) to avoid slowing down rebuilds. Pass `statsOptions: { modules: true }` if you want the module id → name map used for nicer client logging. +Webpack stats options used when serializing compilation results for the SSE payload. Forwarded to `stats.toJson(...)`. By default only the minimal stats needed by the client are requested (`hash`, `timings`, `errors`, `warnings`) to avoid slowing down rebuilds. ## Hot Module Replacement client diff --git a/client-src/index.js b/client-src/index.js index 106ab1f8e..96204eec4 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -183,7 +183,7 @@ export function setOptionsAndConnect(overrides) { // eslint-disable-next-line jsdoc/reject-any-type /** @typedef {any} EXPECTED_ANY */ -/** @typedef {{ name?: string, errors: string[], warnings: string[], hash: string, time?: number, modules?: Record, action?: string }} HMRPayload */ +/** @typedef {{ name?: string, errors: string[], warnings: string[], hash: string, time?: number, action?: string }} HMRPayload */ /** * @returns {{ @@ -295,7 +295,7 @@ function processMessage(obj) { reporter.success(); } if (shouldApply) { - applyUpdate(obj.hash, obj.modules, options); + applyUpdate(obj.hash, options); } break; } diff --git a/client-src/process-update.js b/client-src/process-update.js index 1d00d7c97..5f2108572 100644 --- a/client-src/process-update.js +++ b/client-src/process-update.js @@ -49,10 +49,9 @@ function upToDate(hash) { /** * @param {string} hash latest hash from the SSE payload - * @param {Record | undefined} moduleMap module id → name map * @param {{ reload?: boolean }} options client options */ -export default function applyUpdate(hash, moduleMap, options) { +export default function applyUpdate(hash, options) { const { reload } = options; /** @@ -96,7 +95,7 @@ export default function applyUpdate(hash, moduleMap, options) { `See ${HMR_DOCS_URL} for more details.`, ); for (const moduleId of unacceptedModules) { - log.warn(` - ${(moduleMap && moduleMap[moduleId]) || moduleId}`); + log.warn(` - ${moduleId}`); } performReload(); return; @@ -107,7 +106,7 @@ export default function applyUpdate(hash, moduleMap, options) { } else { log.info("Updated modules:"); for (const moduleId of renewedModules) { - log.info(` - ${(moduleMap && moduleMap[moduleId]) || moduleId}`); + log.info(` - ${moduleId}`); } } diff --git a/src/hot.js b/src/hot.js index dd3f4090b..3946795b3 100644 --- a/src/hot.js +++ b/src/hot.js @@ -5,7 +5,6 @@ /** @typedef {import("webpack").MultiStats} MultiStats */ /** @typedef {import("webpack").StatsCompilation} StatsCompilation */ /** @typedef {import("webpack").StatsError} StatsError */ -/** @typedef {import("webpack").StatsModule} StatsModule */ /** @typedef {import("./index.js").IncomingMessage} IncomingMessage */ /** @typedef {import("./index.js").ServerResponse} ServerResponse */ @@ -26,7 +25,6 @@ * @property {string=} hash hash * @property {string[]=} warnings warnings * @property {string[]=} errors errors - * @property {Record=} modules modules */ /** @@ -191,23 +189,6 @@ function extractBundles(stats) { return [stats]; } -/** - * @param {StatsModule[]} modules modules - * @returns {Record} module id to name map - */ -function buildModuleMap(modules) { - /** @type {Record} */ - const map = {}; - - for (const item of modules) { - map[/** @type {string | number} */ (item.id)] = /** @type {string} */ ( - item.name - ); - } - - return map; -} - /** * @param {string} action action * @param {Stats | MultiStats} statsResult stats result @@ -251,7 +232,6 @@ function publishStats(action, statsResult, eventStream, statsOptions) { hash: stats.hash, warnings: formatErrors(stats.warnings || []), errors: formatErrors(stats.errors || []), - modules: buildModuleMap(stats.modules || []), }); } } @@ -332,7 +312,6 @@ function createHot(compiler, userOptions) { module.exports = createHot; module.exports.HOT_DEFAULT_HEARTBEAT = HOT_DEFAULT_HEARTBEAT; module.exports.HOT_DEFAULT_PATH = HOT_DEFAULT_PATH; -module.exports.buildModuleMap = buildModuleMap; module.exports.createEventStream = createEventStream; module.exports.createHot = createHot; module.exports.formatErrors = formatErrors; diff --git a/test/client.test.js b/test/client.test.js index e310b30b0..37cd09e26 100644 --- a/test/client.test.js +++ b/test/client.test.js @@ -141,7 +141,6 @@ describe("client", () => { ); expect(processUpdate).toHaveBeenCalledWith( "1234567890abcdef", - expect.anything(), expect.objectContaining({ reload: true }), ); }); @@ -530,7 +529,6 @@ describe("client", () => { ); expect(processUpdate).toHaveBeenCalledWith( "1234567890abcdef", - expect.anything(), expect.objectContaining({ reload: false }), ); }); diff --git a/test/helpers/sse.js b/test/helpers/sse.js index 23c98883c..71d7a8410 100644 --- a/test/helpers/sse.js +++ b/test/helpers/sse.js @@ -8,7 +8,6 @@ import http from "node:http"; * @property {number=} time build time in ms * @property {string[]=} errors errors * @property {string[]=} warnings warnings - * @property {Record=} modules module id → name map */ /** diff --git a/test/hot.test.js b/test/hot.test.js index 9fb4f01bd..90820948a 100644 --- a/test/hot.test.js +++ b/test/hot.test.js @@ -1,5 +1,4 @@ import createHot, { - buildModuleMap, createEventStream, formatErrors, pathMatch, @@ -138,17 +137,6 @@ describe("hot middleware (unit)", () => { }); }); - describe("buildModuleMap", () => { - it("maps id to name", () => { - expect( - buildModuleMap([ - { id: 1, name: "./a.js" }, - { id: 2, name: "./b.js" }, - ]), - ).toEqual({ 1: "./a.js", 2: "./b.js" }); - }); - }); - describe("createEventStream", () => { beforeEach(() => { jest.useFakeTimers(); @@ -370,6 +358,35 @@ describe("createHot", () => { hot.close(); }); + it("forwards custom statsOptions to stats.toJson", () => { + const compiler = makeFakeCompiler(); + const hot = createHot(compiler, { + statsOptions: { modules: true, ids: true }, + }); + attachClient({ handler: hot.handle }); + + /** @type {EXPECTED_OBJECT} */ + let receivedOptions; + + compiler.emitDone({ + toJson(statsOptions) { + receivedOptions = statsOptions; + return { + time: 1, + hash: "h", + warnings: [], + errors: [], + modules: [], + }; + }, + compilation: undefined, + }); + + expect(receivedOptions).toMatchObject({ modules: true, ids: true }); + + hot.close(); + }); + it("stops publishing after close() even if the compiler still emits", () => { const compiler = makeFakeCompiler(); const hot = createHot(compiler, {}); diff --git a/types/hot.d.ts b/types/hot.d.ts index cac94957c..5f9c2f1ac 100644 --- a/types/hot.d.ts +++ b/types/hot.d.ts @@ -19,7 +19,6 @@ declare namespace createHot { export { HOT_DEFAULT_HEARTBEAT, HOT_DEFAULT_PATH, - buildModuleMap, createEventStream, createHot, formatErrors, @@ -33,7 +32,6 @@ declare namespace createHot { MultiStats, StatsCompilation, StatsError, - StatsModule, IncomingMessage, ServerResponse, StatsOptions, @@ -50,7 +48,6 @@ declare const HOT_DEFAULT_HEARTBEAT: number; /** @typedef {import("webpack").MultiStats} MultiStats */ /** @typedef {import("webpack").StatsCompilation} StatsCompilation */ /** @typedef {import("webpack").StatsError} StatsError */ -/** @typedef {import("webpack").StatsModule} StatsModule */ /** @typedef {import("./index.js").IncomingMessage} IncomingMessage */ /** @typedef {import("./index.js").ServerResponse} ServerResponse */ /** @typedef {NonNullable} StatsOptions */ @@ -68,7 +65,6 @@ declare const HOT_DEFAULT_HEARTBEAT: number; * @property {string=} hash hash * @property {string[]=} warnings warnings * @property {string[]=} errors errors - * @property {Record=} modules modules */ /** * @typedef {object} EventStream @@ -77,11 +73,6 @@ declare const HOT_DEFAULT_HEARTBEAT: number; * @property {() => void} close end every client and stop the heartbeat */ declare const HOT_DEFAULT_PATH: "/__webpack_hmr"; -/** - * @param {StatsModule[]} modules modules - * @returns {Record} module id to name map - */ -declare function buildModuleMap(modules: StatsModule[]): Record; /** * @param {number} heartbeat heartbeat interval in milliseconds * @param {Logger} logger logger @@ -145,7 +136,6 @@ type Stats = import("webpack").Stats; type MultiStats = import("webpack").MultiStats; type StatsCompilation = import("webpack").StatsCompilation; type StatsError = import("webpack").StatsError; -type StatsModule = import("webpack").StatsModule; type IncomingMessage = import("./index.js").IncomingMessage; type ServerResponse = import("./index.js").ServerResponse; type StatsOptions = NonNullable; @@ -188,10 +178,6 @@ type Payload = { * errors */ errors?: string[] | undefined; - /** - * modules - */ - modules?: Record | undefined; }; type EventStream = { /**