bippy 0.6.1-dev.fe75557 → 0.7.0-dev.697d535

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +223 -461
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/errors.d.cts +410 -0
  6. package/dist/errors.d.ts +410 -0
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +153 -3
  9. package/dist/index.d.ts +153 -3
  10. package/dist/index.js +1 -1
  11. package/dist/install-hook-only.cjs +1 -1
  12. package/dist/install-hook-only.d.cts +9 -1
  13. package/dist/install-hook-only.d.ts +9 -1
  14. package/dist/install-hook-only.js +1 -1
  15. package/dist/rdt-hook.cjs +1 -1
  16. package/dist/rdt-hook.js +1 -1
  17. package/dist/source.cjs +14 -5
  18. package/dist/source.d.cts +86 -69
  19. package/dist/source.d.ts +86 -69
  20. package/dist/source.js +14 -5
  21. package/package.json +26 -27
  22. package/src/core.ts +353 -685
  23. package/src/errors.ts +34 -0
  24. package/src/index.ts +1 -0
  25. package/src/install-hook-only.ts +2 -2
  26. package/src/rdt-hook.ts +160 -161
  27. package/src/react-internals/generated/react-work-tags.ts +318 -0
  28. package/src/react-internals/index.ts +67 -0
  29. package/src/react-internals/semver.ts +80 -0
  30. package/src/react-internals/types.ts +186 -0
  31. package/src/react.ts +76 -0
  32. package/src/source/constants.ts +1 -1
  33. package/src/source/error-stack.ts +11 -0
  34. package/src/source/get-display-name-from-source.ts +44 -40
  35. package/src/source/get-source.ts +43 -26
  36. package/src/source/index.ts +11 -1
  37. package/src/source/inspect-hooks.ts +220 -207
  38. package/src/source/owner-stack.ts +119 -174
  39. package/src/source/parse-debug-stack.ts +4 -4
  40. package/src/source/parse-hook-names.ts +14 -53
  41. package/src/source/parse-stack.ts +14 -31
  42. package/src/source/renderer-dispatchers.ts +30 -0
  43. package/src/source/symbolication.ts +709 -120
  44. package/dist/core.d.cts +0 -214
  45. package/dist/core.d.ts +0 -214
  46. package/dist/core2.d.cts +0 -3
  47. package/dist/core2.d.ts +0 -3
  48. package/dist/get-source.cjs +0 -19
  49. package/dist/get-source.js +0 -19
  50. package/dist/index.iife.js +0 -9
  51. package/dist/install-hook-only.iife.js +0 -9
  52. package/dist/react-refresh.cjs +0 -9
  53. package/dist/react-refresh.d.cts +0 -66
  54. package/dist/react-refresh.d.ts +0 -66
  55. package/dist/react-refresh.js +0 -9
  56. package/dist/unsubscribe.d.cts +0 -298
  57. package/dist/unsubscribe.d.ts +0 -298
  58. package/src/react-refresh/constants.ts +0 -9
  59. package/src/react-refresh/detect-hmr-transport.ts +0 -33
  60. package/src/react-refresh/index.ts +0 -173
  61. package/src/react-refresh/metro-hmr-transport.ts +0 -188
  62. package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
  63. package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
  64. package/src/react-refresh/types.ts +0 -7
  65. package/src/react-refresh/vite-hmr-transport.ts +0 -116
  66. package/src/types.ts +0 -438
  67. package/src/unsubscribe.ts +0 -17
@@ -1,298 +0,0 @@
1
- import { ReactNode } from "react";
2
-
3
- //#region src/types.d.ts
4
- type BundleType = 0 | 1;
5
- type Flags = number;
6
- type Lanes = number;
7
- type TypeOfMode = number;
8
- type RootTag = 0 | 1 | 2;
9
- type LanePriority = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17;
10
- type WorkTag = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31;
11
- type HookType = "useState" | "useReducer" | "useContext" | "useRef" | "useEffect" | "useLayoutEffect" | "useCallback" | "useMemo" | "useImperativeHandle" | "useDebugValue" | "useDeferredValue" | "useTransition" | "useMutableSource" | "useOpaqueIdentifier" | "useCacheRefresh";
12
- type FiberRoot = any;
13
- type MutableSource = any;
14
- type OpaqueHandle = any;
15
- type OpaqueRoot = any;
16
- type React$AbstractComponent<_Config, _Instance = unknown> = any;
17
- type HostConfig = Record<string, any>;
18
- interface Source {
19
- fileName: string;
20
- lineNumber: number;
21
- }
22
- interface RefObject {
23
- current: any;
24
- }
25
- interface Thenable<T> {
26
- then(resolve: () => T, reject?: () => T): T;
27
- }
28
- interface ReactContext<T> {
29
- $$typeof: symbol | number;
30
- Consumer: ReactContext<T>;
31
- Provider: ReactProviderType<T>;
32
- _calculateChangedBits: ((a: T, b: T) => number) | null;
33
- _currentValue: T;
34
- _currentValue2: T;
35
- _threadCount: number;
36
- _currentRenderer?: Record<string, any> | null;
37
- _currentRenderer2?: Record<string, any> | null;
38
- displayName?: string;
39
- }
40
- interface ReactProviderType<T> {
41
- $$typeof: symbol | number;
42
- _context: ReactContext<T>;
43
- }
44
- interface ReactProvider<T> {
45
- $$typeof: symbol | number;
46
- type: ReactProviderType<T>;
47
- key: null | string;
48
- ref: null;
49
- props: {
50
- value: T;
51
- children?: ReactNode;
52
- };
53
- }
54
- interface ReactConsumer<T> {
55
- $$typeof: symbol | number;
56
- type: ReactContext<T>;
57
- key: null | string;
58
- ref: null;
59
- props: {
60
- children: (value: T) => ReactNode;
61
- unstable_observedBits?: number;
62
- };
63
- }
64
- interface ReactPortal {
65
- $$typeof: symbol | number;
66
- key: null | string;
67
- containerInfo: any;
68
- children: ReactNode;
69
- implementation: any;
70
- }
71
- interface ComponentSelector {
72
- $$typeof: symbol | number;
73
- value: React$AbstractComponent<never, unknown>;
74
- }
75
- interface HasPseudoClassSelector {
76
- $$typeof: symbol | number;
77
- value: Selector[];
78
- }
79
- interface RoleSelector {
80
- $$typeof: symbol | number;
81
- value: string;
82
- }
83
- interface TextSelector {
84
- $$typeof: symbol | number;
85
- value: string;
86
- }
87
- interface TestNameSelector {
88
- $$typeof: symbol | number;
89
- value: string;
90
- }
91
- type Selector = ComponentSelector | HasPseudoClassSelector | RoleSelector | TextSelector | TestNameSelector;
92
- interface DevToolsConfig<Instance = any, TextInstance = any, RendererInspectionConfig = any> {
93
- bundleType: BundleType;
94
- version: string;
95
- rendererPackageName: string;
96
- findFiberByHostInstance?: (instance: Instance | TextInstance) => ReactFiber | null;
97
- rendererConfig?: RendererInspectionConfig;
98
- }
99
- interface SuspenseHydrationCallbacks<SuspenseInstance = unknown> {
100
- onHydrated?: (suspenseInstance: SuspenseInstance) => void;
101
- onDeleted?: (suspenseInstance: SuspenseInstance) => void;
102
- }
103
- interface TransitionTracingCallbacks {
104
- onTransitionStart?: (transitionName: string, startTime: number) => void;
105
- onTransitionProgress?: (transitionName: string, startTime: number, currentTime: number, pending: Array<{
106
- name: null | string;
107
- }>) => void;
108
- onTransitionIncomplete?: (transitionName: string, startTime: number, deletions: Array<{
109
- type: string;
110
- name?: string;
111
- newName?: string;
112
- endTime: number;
113
- }>) => void;
114
- onTransitionComplete?: (transitionName: string, startTime: number, endTime: number) => void;
115
- onMarkerProgress?: (transitionName: string, marker: string, startTime: number, currentTime: number, pending: Array<{
116
- name: null | string;
117
- }>) => void;
118
- onMarkerIncomplete?: (transitionName: string, marker: string, startTime: number, deletions: Array<{
119
- type: string;
120
- name?: string;
121
- newName?: string;
122
- endTime: number;
123
- }>) => void;
124
- onMarkerComplete?: (transitionName: string, marker: string, startTime: number, endTime: number) => void;
125
- }
126
- interface ReactFiber {
127
- tag: WorkTag;
128
- key: null | string;
129
- elementType: any;
130
- type: any;
131
- stateNode: any;
132
- return: ReactFiber | null;
133
- child: ReactFiber | null;
134
- sibling: ReactFiber | null;
135
- index: number;
136
- ref: null | (((handle: unknown) => void) & {
137
- _stringRef?: string | null;
138
- }) | RefObject;
139
- pendingProps: any;
140
- memoizedProps: any;
141
- updateQueue: unknown;
142
- memoizedState: any;
143
- dependencies: Dependencies | null;
144
- mode: TypeOfMode;
145
- flags: Flags;
146
- subtreeFlags: Flags;
147
- deletions: ReactFiber[] | null;
148
- nextEffect: ReactFiber | null;
149
- firstEffect: ReactFiber | null;
150
- lastEffect: ReactFiber | null;
151
- lanes: Lanes;
152
- childLanes: Lanes;
153
- alternate: ReactFiber | null;
154
- actualDuration?: number;
155
- actualStartTime?: number;
156
- selfBaseDuration?: number;
157
- treeBaseDuration?: number;
158
- _debugID?: number;
159
- _debugSource?: Source | null;
160
- _debugOwner?: ReactFiber | null;
161
- _debugIsCurrentlyTiming?: boolean;
162
- _debugNeedsRemount?: boolean;
163
- _debugHookTypes?: HookType[] | null;
164
- }
165
- interface ContextDependency<T> {
166
- context: ReactContext<T>;
167
- memoizedValue: T;
168
- next: ContextDependency<unknown> | null;
169
- observedBits: number;
170
- }
171
- interface Dependencies {
172
- firstContext: ContextDependency<unknown> | null;
173
- lanes: Lanes;
174
- }
175
- interface Effect {
176
- [key: string]: unknown;
177
- create: (...args: unknown[]) => unknown;
178
- deps: null | unknown[];
179
- destroy: ((...args: unknown[]) => unknown) | null;
180
- next: Effect | null;
181
- tag: number;
182
- }
183
- interface Family {
184
- current: unknown;
185
- }
186
- /**
187
- * React 19 flight metadata for a server component owner (ReactComponentInfo).
188
- * Unlike client owners it has no `tag`; the owner chain continues via `owner`.
189
- */
190
- interface ServerComponentInfo {
191
- name?: string;
192
- env?: string;
193
- owner?: Fiber | ServerComponentInfo | null;
194
- debugStack?: Error | null;
195
- }
196
- interface RendererRefreshUpdate {
197
- staleFamilies: Set<Family>;
198
- updatedFamilies: Set<Family>;
199
- }
200
- /**
201
- * Represents a react-internal Fiber node.
202
- */
203
- type Fiber<T = any> = Omit<ReactFiber, "alternate" | "child" | "dependencies" | "memoizedProps" | "memoizedState" | "pendingProps" | "return" | "sibling" | "stateNode" | "updateQueue"> & {
204
- _debugInfo?: Array<{
205
- debugLocation?: unknown;
206
- env?: string;
207
- name?: string;
208
- }>;
209
- _debugOwner?: Fiber;
210
- _debugSource?: {
211
- columnNumber?: number;
212
- fileName: string;
213
- lineNumber: number;
214
- };
215
- _debugStack?: Error & {
216
- stack: string;
217
- };
218
- alternate: Fiber | null;
219
- child: Fiber | null;
220
- dependencies: Dependencies | null;
221
- memoizedProps: Props;
222
- memoizedState: MemoizedState;
223
- pendingProps: Props;
224
- return: Fiber | null;
225
- sibling: Fiber | null;
226
- stateNode: T;
227
- updateQueue: {
228
- [key: string]: unknown;
229
- lastEffect: Effect | null;
230
- };
231
- };
232
- interface MemoizedState {
233
- [key: string]: unknown;
234
- memoizedState: unknown;
235
- next: MemoizedState | null;
236
- }
237
- interface Props {
238
- [key: string]: unknown;
239
- }
240
- interface ReactDevToolsGlobalHook {
241
- _instrumentationIsActive?: boolean;
242
- _instrumentationSource?: string;
243
- checkDCE: (fn: unknown) => void;
244
- hasUnsupportedRendererAttached: boolean;
245
- inject: (renderer: ReactRenderer) => number;
246
- on: () => void;
247
- onCommitFiberRoot: (rendererID: number, root: FiberRoot, priority: number | void) => void;
248
- onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;
249
- onPostCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;
250
- onScheduleFiberRoot?: (rendererID: number, root: FiberRoot, children: ReactNode) => void;
251
- renderers: Map<number, ReactRenderer>;
252
- supportsFiber: boolean;
253
- supportsFlight: boolean;
254
- }
255
- interface ReactRenderer {
256
- bundleType: 0 | 1;
257
- currentDispatcherRef: any;
258
- findFiberByHostInstance?: (hostInstance: unknown) => Fiber | null;
259
- getCurrentFiber?: (fiber: Fiber) => Fiber | null;
260
- overrideContext?: (fiber: Fiber, contextType: unknown, path: string[], value: unknown) => void;
261
- overrideHookState?: (fiber: Fiber, id: string, path: string[], value: unknown) => void;
262
- overrideHookStateDeletePath?: (fiber: Fiber, id: number, path: Array<number | string>) => void;
263
- overrideHookStateRenamePath?: (fiber: Fiber, id: number, oldPath: Array<number | string>, newPath: Array<number | string>) => void;
264
- overrideProps?: (fiber: Fiber, path: string[], value: unknown) => void;
265
- overridePropsDeletePath?: (fiber: Fiber, path: Array<number | string>) => void;
266
- overridePropsRenamePath?: (fiber: Fiber, oldPath: Array<number | string>, newPath: Array<number | string>) => void;
267
- reconcilerVersion: string;
268
- rendererPackageName: string;
269
- scheduleRefresh?: (root: FiberRoot, update: RendererRefreshUpdate) => void;
270
- scheduleRoot?: (root: FiberRoot, element: React.ReactNode) => void;
271
- scheduleUpdate?: (fiber: Fiber) => void;
272
- setErrorHandler?: (newShouldErrorImpl: (fiber: Fiber) => boolean) => void;
273
- setRefreshHandler?: (handler: ((fiber: Fiber) => Family | null) | null) => void;
274
- setSuspenseHandler?: (newShouldSuspendImpl: (suspenseInstance: unknown) => void) => void;
275
- version: string;
276
- }
277
- declare global {
278
- var __REACT_DEVTOOLS_GLOBAL_HOOK__: ReactDevToolsGlobalHook | undefined;
279
- }
280
- //#endregion
281
- //#region src/unsubscribe.d.ts
282
- interface Unsubscribe extends Disposable {
283
- (): void;
284
- }
285
- /**
286
- * Wraps a teardown callback so it is both callable and a `Disposable`,
287
- * letting subscriptions compose through explicit resource management:
288
- *
289
- * @example
290
- * ```ts
291
- * using instrumentation = instrument({ onCommitFiberRoot });
292
- * using refresh = instrumentReactRefresh({ onRefresh: handleRefresh });
293
- * // both torn down automatically at scope exit
294
- * ```
295
- */
296
- declare const toUnsubscribe: (dispose: () => void) => Unsubscribe;
297
- //#endregion
298
- export { ReactRenderer as A, TextSelector as B, React$AbstractComponent as C, ReactPortal as D, ReactDevToolsGlobalHook as E, Selector as F, TransitionTracingCallbacks as H, ServerComponentInfo as I, Source as L, RendererRefreshUpdate as M, RoleSelector as N, ReactProvider as O, RootTag as P, SuspenseHydrationCallbacks as R, Props as S, ReactContext as T, TypeOfMode as U, Thenable as V, WorkTag as W, Lanes as _, ContextDependency as a, OpaqueHandle as b, Effect as c, FiberRoot as d, Flags as f, LanePriority as g, HostConfig as h, ComponentSelector as i, RefObject as j, ReactProviderType as k, Family as l, HookType as m, toUnsubscribe as n, Dependencies as o, HasPseudoClassSelector as p, BundleType as r, DevToolsConfig as s, Unsubscribe as t, Fiber as u, MemoizedState as v, ReactConsumer as w, OpaqueRoot as x, MutableSource as y, TestNameSelector as z };
@@ -1,9 +0,0 @@
1
- export const HMR_RECONNECT_DELAY_MS = 1000;
2
-
3
- export const PENDING_HOT_UPDATE_MAX_AGE_MS = 10_000;
4
-
5
- export const VITE_WS_TOKEN_REGEX = /wsToken = "([^"]+)"/;
6
-
7
- export const HMR_SOURCE_FILE_EXTENSION_REGEX = /\.(?:tsx|ts|jsx|js|mjs|cjs|css)$/;
8
-
9
- export const BUNDLER_LAYER_PREFIX_REGEX = /^(?:\.\/)?\/?\([a-z][a-z0-9-]*\)\//;
@@ -1,33 +0,0 @@
1
- import { isClientEnvironment } from "../rdt-hook.js";
2
-
3
- import { createMetroHmrTransport } from "./metro-hmr-transport.js";
4
- import { createNextWebpackHmrTransport } from "./next-webpack-hmr-transport.js";
5
- import { HmrTransport, HmrUpdateHandler } from "./types.js";
6
- import { createViteHmrTransport } from "./vite-hmr-transport.js";
7
-
8
- /**
9
- * Detects the dev server's HMR transport (Next.js webpack, then Metro for
10
- * React Native, then Vite) and subscribes `onHmrUpdate` to hot updates.
11
- * Resolves `null` on the server (SSR) and when no known transport is
12
- * available (production builds, unsupported bundlers — Turbopack exposes
13
- * `window.TURBOPACK_CHUNK_UPDATE_LISTENERS` but its update payload shape
14
- * has not been validated, so it is not wired up yet).
15
- *
16
- * @example
17
- * ```ts
18
- * const transport = await detectHmrTransport((filePaths) => {
19
- * console.log("hot updated:", filePaths);
20
- * });
21
- * transport?.dispose();
22
- * ```
23
- */
24
- export const detectHmrTransport = async (
25
- onHmrUpdate: HmrUpdateHandler,
26
- ): Promise<HmrTransport | null> => {
27
- if (!isClientEnvironment()) return null;
28
- const webpackTransport = createNextWebpackHmrTransport(onHmrUpdate);
29
- if (webpackTransport) return webpackTransport;
30
- const metroTransport = createMetroHmrTransport(onHmrUpdate);
31
- if (metroTransport) return metroTransport;
32
- return createViteHmrTransport(onHmrUpdate);
33
- };
@@ -1,173 +0,0 @@
1
- import { getType, traverseFiber } from "../core.js";
2
- import { getRDTHook, isClientEnvironment, onRendererInject } from "../rdt-hook.js";
3
- import type { Fiber, FiberRoot, ReactRenderer } from "../types.js";
4
- import { toUnsubscribe, type Unsubscribe } from "../unsubscribe.js";
5
- import { PENDING_HOT_UPDATE_MAX_AGE_MS } from "./constants.js";
6
- import { detectHmrTransport } from "./detect-hmr-transport.js";
7
- import { HmrTransport } from "./types.js";
8
-
9
- export interface ReactRefreshUpdate {
10
- /**
11
- * hot-updated source file paths reported by the bundler's HMR transport
12
- * (auto-detected: Next.js webpack, Metro, Vite). Best-effort: empty when
13
- * the bundler does not expose a transport (e.g. Turbopack) or when the
14
- * transport message has not arrived yet (e.g. Metro delivers updates on
15
- * an independent socket).
16
- */
17
- filePaths: string[];
18
- root: FiberRoot;
19
- /** new component types that were remounted, losing state */
20
- staleComponents: unknown[];
21
- /** mounted fibers whose component types were remounted */
22
- staleFibers: Fiber[];
23
- /** new component types that re-rendered preserving state */
24
- updatedComponents: unknown[];
25
- /** mounted fibers whose component types re-rendered preserving state */
26
- updatedFibers: Fiber[];
27
- }
28
-
29
- export interface ReactRefreshHandler {
30
- (update: ReactRefreshUpdate): void;
31
- }
32
-
33
- export interface ReactRefreshInstrumentationOptions {
34
- onRefresh?: ReactRefreshHandler;
35
- }
36
-
37
- const collectFibersByComponentType = (root: FiberRoot, componentTypes: Set<unknown>): Fiber[] => {
38
- if (componentTypes.size === 0 || !root.current) return [];
39
- const matchedFibers: Fiber[] = [];
40
- traverseFiber(root.current, (fiber) => {
41
- // memo/forwardRef fibers carry the wrapper as fiber.type, while the
42
- // refresh families can register either the wrapper or the inner type
43
- if (componentTypes.has(fiber.type) || componentTypes.has(getType(fiber.type))) {
44
- matchedFibers.push(fiber);
45
- }
46
- });
47
- return matchedFibers;
48
- };
49
-
50
- const refreshHandlers = new Set<ReactRefreshHandler>();
51
- const refreshWrappedRenderers = new WeakSet<ReactRenderer>();
52
-
53
- let pendingFilePaths: string[] = [];
54
- let pendingFilePathsReceivedAtMs = 0;
55
-
56
- const bufferHotUpdateFilePaths = (filePaths: string[]): void => {
57
- const nowMs = Date.now();
58
- if (nowMs - pendingFilePathsReceivedAtMs > PENDING_HOT_UPDATE_MAX_AGE_MS) {
59
- pendingFilePaths = [];
60
- }
61
- pendingFilePaths.push(...filePaths);
62
- pendingFilePathsReceivedAtMs = nowMs;
63
- };
64
-
65
- const takeFreshFilePaths = (): string[] => {
66
- if (Date.now() - pendingFilePathsReceivedAtMs > PENDING_HOT_UPDATE_MAX_AGE_MS) return [];
67
- const freshFilePaths = [...pendingFilePaths];
68
- // performReactRefresh calls scheduleRefresh synchronously once per
69
- // mounted root, so clear the pending paths only after the whole
70
- // refresh pass instead of on the first root
71
- queueMicrotask(() => {
72
- pendingFilePaths = [];
73
- });
74
- return freshFilePaths;
75
- };
76
-
77
- const wrapRendererScheduleRefresh = (renderer: ReactRenderer): void => {
78
- if (refreshWrappedRenderers.has(renderer)) return;
79
- const originalScheduleRefresh = renderer.scheduleRefresh;
80
- if (typeof originalScheduleRefresh !== "function") return;
81
- refreshWrappedRenderers.add(renderer);
82
- renderer.scheduleRefresh = (root, update) => {
83
- originalScheduleRefresh.call(renderer, root, update);
84
- if (refreshHandlers.size === 0) return;
85
- const staleComponents = Array.from(update.staleFamilies, (family) => family.current);
86
- const updatedComponents = Array.from(update.updatedFamilies, (family) => family.current);
87
- const refreshUpdate: ReactRefreshUpdate = {
88
- filePaths: takeFreshFilePaths(),
89
- root,
90
- staleComponents,
91
- staleFibers: collectFibersByComponentType(root, new Set(staleComponents)),
92
- updatedComponents,
93
- updatedFibers: collectFibersByComponentType(root, new Set(updatedComponents)),
94
- };
95
- for (const handler of refreshHandlers) {
96
- handler(refreshUpdate);
97
- }
98
- };
99
- };
100
-
101
- let isRefreshWired = false;
102
-
103
- const ensureRefreshWired = (): void => {
104
- if (isRefreshWired) return;
105
- isRefreshWired = true;
106
- const rdtHook = getRDTHook();
107
- for (const renderer of rdtHook.renderers.values()) {
108
- wrapRendererScheduleRefresh(renderer);
109
- }
110
- onRendererInject(wrapRendererScheduleRefresh);
111
- };
112
-
113
- let activeTransport: HmrTransport | null = null;
114
-
115
- // the bundler's HMR global may not exist yet when an early subscriber
116
- // arrives, so detection is retried whenever the first subscriber (re)appears
117
- const detectTransportForNewSubscriber = (): void => {
118
- void detectHmrTransport(bufferHotUpdateFilePaths).then((detectedTransport) => {
119
- if (!detectedTransport) return;
120
- activeTransport?.dispose();
121
- activeTransport = detectedTransport;
122
- });
123
- };
124
-
125
- /**
126
- * Subscribes to react-refresh (fast refresh) updates by wrapping
127
- * `scheduleRefresh` on every renderer injected into the React DevTools
128
- * global hook. The react-refresh runtime calls `scheduleRefresh` after each
129
- * hot update, so this works with any bundler that uses react-refresh (Vite,
130
- * Next.js webpack, Next.js Turbopack, Metro) without bundler-specific code.
131
- * Returns an unsubscribe function (a no-op in non-client environments like
132
- * SSR, so callers never need an environment check). The returned function
133
- * is also a `Disposable`, so it composes with other bippy subscriptions
134
- * through `using`.
135
- *
136
- * The bundler's HMR transport is auto-detected and each refresh update is
137
- * augmented with the hot-updated source file paths it reported.
138
- *
139
- * The handler runs after React has re-rendered with the new component
140
- * types, so the refreshed root's fiber tree already carries them;
141
- * `updatedFibers`/`staleFibers` are the mounted fibers matching the
142
- * hot-swapped component types.
143
- *
144
- * @example
145
- * ```ts
146
- * const unsubscribe = instrumentReactRefresh({
147
- * onRefresh(update) {
148
- * for (const fiber of update.updatedFibers) {
149
- * console.log("hot updated:", getDisplayName(fiber.type));
150
- * }
151
- * console.log("changed files:", update.filePaths);
152
- * },
153
- * });
154
- * unsubscribe();
155
- * ```
156
- *
157
- * Pair with `getSource(fiber)` from `bippy/source` to symbolicate the
158
- * source locations of `updatedFibers` when needed.
159
- */
160
- export const instrumentReactRefresh = (
161
- options: ReactRefreshInstrumentationOptions,
162
- ): Unsubscribe => {
163
- const { onRefresh } = options;
164
- if (!onRefresh || !isClientEnvironment()) return toUnsubscribe(() => {});
165
- ensureRefreshWired();
166
- if (refreshHandlers.size === 0) {
167
- detectTransportForNewSubscriber();
168
- }
169
- refreshHandlers.add(onRefresh);
170
- return toUnsubscribe(() => {
171
- refreshHandlers.delete(onRefresh);
172
- });
173
- };