Skip to content

Commit 5de4c98

Browse files
committed
feat(hot): include the changed file in the building event (#2352)
The compiler's invalid hook reports which file invalidated the compilation. Forward it as an optional `file` field on the `building` payload and log it in the client, so users can see what triggered a rebuild. Ref webpack/webpack-hot-middleware#173
1 parent e78dbe4 commit 5de4c98

6 files changed

Lines changed: 70 additions & 6 deletions

File tree

client-src/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export function disconnect() {
205205
// eslint-disable-next-line jsdoc/reject-any-type
206206
/** @typedef {any} EXPECTED_ANY */
207207

208-
/** @typedef {{ name?: string, errors: string[], warnings: string[], hash: string, time?: number, action?: string }} HMRPayload */
208+
/** @typedef {{ name?: string, errors: string[], warnings: string[], hash: string, time?: number, action?: string, file?: string }} HMRPayload */
209209

210210
/**
211211
* @returns {{
@@ -291,7 +291,11 @@ let subscribeAllHandler;
291291
function processMessage(obj) {
292292
switch (obj.action) {
293293
case "building": {
294-
log.info(`bundle ${obj.name ? `'${obj.name}' ` : ""}rebuilding`);
294+
log.info(
295+
`bundle ${obj.name ? `'${obj.name}' ` : ""}rebuilding${
296+
obj.file ? ` (${obj.file} changed)` : ""
297+
}`,
298+
);
295299
break;
296300
}
297301
case "built":

src/hot.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
/**
2121
* @typedef {object} Payload
2222
* @property {string} action action
23+
* @property {string=} file file that invalidated the compilation
2324
* @property {string=} name name
2425
* @property {number=} time time
2526
* @property {string=} hash hash
@@ -262,12 +263,22 @@ function createHot(compiler, userOptions) {
262263
let latestStats = null;
263264
let closed = false;
264265

265-
const onInvalid = () => {
266+
/** @param {string | null=} fileName file that triggered the rebuild */
267+
const onInvalid = (fileName) => {
266268
if (closed) return;
267269

268270
latestStats = null;
269271

270-
eventStream.publish({ action: "building" });
272+
/** @type {{ action: string, file?: string }} */
273+
const payload = { action: "building" };
274+
275+
// The invalid hook reports which file changed — forward it so clients
276+
// can show what triggered the rebuild.
277+
if (typeof fileName === "string" && fileName) {
278+
payload.file = fileName;
279+
}
280+
281+
eventStream.publish(payload);
271282
};
272283

273284
/** @param {Stats | MultiStats} statsResult stats result */

test/client.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ describe("client", () => {
159159
expect(processUpdate).toHaveBeenCalledTimes(1);
160160
});
161161

162+
it("logs the changed file on building messages", () => {
163+
EventSourceStub.lastInstance().onmessage(
164+
makeMessage({ action: "building", file: "/src/index.js" }),
165+
);
166+
expect(
167+
console.info.mock.calls.some(([msg]) =>
168+
msg.includes("rebuilding (/src/index.js changed)"),
169+
),
170+
).toBe(true);
171+
});
172+
162173
it("calls subscribeAll handler on default messages", () => {
163174
const spy = jest.fn();
164175
client.subscribeAll(spy);

test/helpers/sse.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import http from "node:http";
33
/**
44
* @typedef {object} SseEvent
55
* @property {string=} action event action (building/built/sync/custom)
6+
* @property {string=} file file that invalidated the compilation
67
* @property {string=} name compilation name
78
* @property {string=} hash compilation hash
89
* @property {number=} time build time in ms

test/hot.test.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ function makeFakeCompiler(logger = noopLogger) {
2727
done: { tap: (_name, fn) => doneTaps.push(fn) },
2828
},
2929
getInfrastructureLogger: () => logger,
30-
emitInvalid() {
31-
for (const fn of invalidTaps) fn();
30+
emitInvalid(fileName) {
31+
for (const fn of invalidTaps) fn(fileName);
3232
},
3333
emitDone(stats) {
3434
for (const fn of doneTaps) fn(stats);
@@ -315,6 +315,38 @@ describe("createHot", () => {
315315
hot.close();
316316
});
317317

318+
it("includes the changed file in the building payload", () => {
319+
const compiler = makeFakeCompiler();
320+
const hot = createHot(compiler, {});
321+
const { writes } = attachClient({ handler: hot.handle });
322+
323+
compiler.emitInvalid("/src/index.js");
324+
325+
expect(
326+
writes.some(
327+
(w) =>
328+
w.includes('"action":"building"') &&
329+
w.includes('"file":"/src/index.js"'),
330+
),
331+
).toBe(true);
332+
333+
hot.close();
334+
});
335+
336+
it("omits the file field when the invalid hook reports none", () => {
337+
const compiler = makeFakeCompiler();
338+
const hot = createHot(compiler, {});
339+
const { writes } = attachClient({ handler: hot.handle });
340+
341+
compiler.emitInvalid();
342+
343+
const building = writes.find((w) => w.includes('"action":"building"'));
344+
expect(building).toBeDefined();
345+
expect(building).not.toContain('"file"');
346+
347+
hot.close();
348+
});
349+
318350
it("sends a sync payload to a client that connects after a build", () => {
319351
const compiler = makeFakeCompiler();
320352
const hot = createHot(compiler, {});

types/hot.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ declare const HOT_DEFAULT_HEARTBEAT: number;
6060
/**
6161
* @typedef {object} Payload
6262
* @property {string} action action
63+
* @property {string=} file file that invalidated the compilation
6364
* @property {string=} name name
6465
* @property {number=} time time
6566
* @property {string=} hash hash
@@ -158,6 +159,10 @@ type Payload = {
158159
* action
159160
*/
160161
action: string;
162+
/**
163+
* file that invalidated the compilation
164+
*/
165+
file?: string | undefined;
161166
/**
162167
* name
163168
*/

0 commit comments

Comments
 (0)