Skip to content

Commit 49a16c5

Browse files
committed
feat(vite): onHmrUpdate for custom app hmr update handling
1 parent 414ce43 commit 49a16c5

File tree

1 file changed

+84
-0
lines changed
  • packages/vite/hmr/shared/runtime

1 file changed

+84
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
export type NsHmrUpdatePayload = {
2+
type: 'full-graph' | 'delta';
3+
version: number;
4+
changedIds: string[];
5+
// Raw message payload from the HMR WebSocket
6+
raw: any;
7+
};
8+
9+
export type NsHmrUpdateHandler = (payload: NsHmrUpdatePayload) => void;
10+
11+
type NsHmrGlobalState = {
12+
__NS_HMR_ON_UPDATE__?: unknown;
13+
__NS_HMR_ON_UPDATE_DISPATCHER__?: NsHmrUpdateHandler;
14+
__NS_HMR_ON_UPDATE_REGISTRY__?: Map<string, NsHmrUpdateHandler>;
15+
__NS_HMR_ON_UPDATE_BASE__?: unknown;
16+
};
17+
18+
function getNsHmrGlobal(): NsHmrGlobalState {
19+
return globalThis as any;
20+
}
21+
22+
function ensureDispatcherInstalled(): {
23+
registry: Map<string, NsHmrUpdateHandler>;
24+
dispatcher: NsHmrUpdateHandler;
25+
base: unknown;
26+
} {
27+
const g = getNsHmrGlobal();
28+
if (!g.__NS_HMR_ON_UPDATE_REGISTRY__) g.__NS_HMR_ON_UPDATE_REGISTRY__ = new Map();
29+
const registry = g.__NS_HMR_ON_UPDATE_REGISTRY__;
30+
31+
if (!g.__NS_HMR_ON_UPDATE_DISPATCHER__) {
32+
const base = g.__NS_HMR_ON_UPDATE__;
33+
// If something already owns the hook and it's not our dispatcher, preserve it.
34+
g.__NS_HMR_ON_UPDATE_BASE__ = base;
35+
g.__NS_HMR_ON_UPDATE_DISPATCHER__ = (payload: NsHmrUpdatePayload) => {
36+
// Call registered handlers first (app-level consumers).
37+
try {
38+
for (const handler of registry.values()) {
39+
try {
40+
handler(payload);
41+
} catch {}
42+
}
43+
} catch {}
44+
// Then call any preserved base hook.
45+
try {
46+
const b = (getNsHmrGlobal() as any).__NS_HMR_ON_UPDATE_BASE__;
47+
if (typeof b === 'function') (b as NsHmrUpdateHandler)(payload);
48+
} catch {}
49+
};
50+
g.__NS_HMR_ON_UPDATE__ = g.__NS_HMR_ON_UPDATE_DISPATCHER__;
51+
}
52+
53+
return {
54+
registry,
55+
dispatcher: g.__NS_HMR_ON_UPDATE_DISPATCHER__!,
56+
base: g.__NS_HMR_ON_UPDATE_BASE__,
57+
};
58+
}
59+
60+
/**
61+
* Register a callback that will be invoked after each HMR batch
62+
* (full graph or delta) is applied on device.
63+
*
64+
* It is safe to call multiple times with the same `id`; the handler
65+
* will be replaced instead of stacking duplicates across module reloads.
66+
*/
67+
68+
export function onHmrUpdate(handler: NsHmrUpdateHandler, id: string): void {
69+
if (typeof handler !== 'function') return;
70+
if (typeof id !== 'string' || !id) return;
71+
try {
72+
const { registry } = ensureDispatcherInstalled();
73+
registry.set(id, handler);
74+
} catch {}
75+
}
76+
77+
/** Remove a previously registered handler (use the same `id` you registered with). */
78+
export function offHmrUpdate(id: string): void {
79+
if (typeof id !== 'string' || !id) return;
80+
try {
81+
const g = getNsHmrGlobal();
82+
g.__NS_HMR_ON_UPDATE_REGISTRY__?.delete(id);
83+
} catch {}
84+
}

0 commit comments

Comments
 (0)