bippy 0.6.1-dev.fe75557 → 0.7.0-dev.07e6032
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.
- package/LICENSE +1 -1
- package/README.md +223 -461
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +410 -0
- package/dist/errors.d.ts +410 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +154 -3
- package/dist/index.d.ts +154 -3
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +9 -1
- package/dist/install-hook-only.d.ts +9 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +14 -5
- package/dist/source.d.cts +86 -69
- package/dist/source.d.ts +86 -69
- package/dist/source.js +14 -5
- package/package.json +26 -27
- package/src/core.ts +367 -685
- package/src/errors.ts +34 -0
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +6 -2
- package/src/rdt-hook.ts +177 -156
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +73 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/react-internals/types.ts +186 -0
- package/src/react.ts +76 -0
- package/src/source/constants.ts +1 -1
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +43 -26
- package/src/source/index.ts +11 -1
- package/src/source/inspect-hooks.ts +220 -207
- package/src/source/owner-stack.ts +121 -174
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +14 -53
- package/src/source/parse-stack.ts +14 -31
- package/src/source/renderer-dispatchers.ts +30 -0
- package/src/source/symbolication.ts +709 -120
- package/dist/core.d.cts +0 -214
- package/dist/core.d.ts +0 -214
- package/dist/core2.d.cts +0 -3
- package/dist/core2.d.ts +0 -3
- package/dist/get-source.cjs +0 -19
- package/dist/get-source.js +0 -19
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- package/dist/react-refresh.cjs +0 -9
- package/dist/react-refresh.d.cts +0 -66
- package/dist/react-refresh.d.ts +0 -66
- package/dist/react-refresh.js +0 -9
- package/dist/unsubscribe.d.cts +0 -298
- package/dist/unsubscribe.d.ts +0 -298
- package/src/react-refresh/constants.ts +0 -9
- package/src/react-refresh/detect-hmr-transport.ts +0 -33
- package/src/react-refresh/index.ts +0 -173
- package/src/react-refresh/metro-hmr-transport.ts +0 -188
- package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
- package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
- package/src/react-refresh/types.ts +0 -7
- package/src/react-refresh/vite-hmr-transport.ts +0 -116
- package/src/types.ts +0 -438
- package/src/unsubscribe.ts +0 -17
package/src/errors.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class BippyError extends Error {
|
|
2
|
+
constructor(message: string, cause?: unknown) {
|
|
3
|
+
super(message, { cause });
|
|
4
|
+
this.name = "BippyError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class BippyHookInspectionError extends BippyError {
|
|
9
|
+
constructor(message: string, cause?: unknown) {
|
|
10
|
+
super(message, cause);
|
|
11
|
+
this.name = "BippyHookInspectionError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class BippyUnsupportedHookError extends BippyHookInspectionError {
|
|
16
|
+
constructor(message: string, cause?: unknown) {
|
|
17
|
+
super(message, cause);
|
|
18
|
+
this.name = "BippyUnsupportedHookError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class BippyHookRenderError extends BippyHookInspectionError {
|
|
23
|
+
constructor(message: string, cause?: unknown) {
|
|
24
|
+
super(message, cause);
|
|
25
|
+
this.name = "BippyHookRenderError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class BippySourceMapError extends BippyError {
|
|
30
|
+
constructor(message: string, cause?: unknown) {
|
|
31
|
+
super(message, cause);
|
|
32
|
+
this.name = "BippySourceMapError";
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.ts
CHANGED
package/src/install-hook-only.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getRDTHook } from "./rdt-hook.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Importing bippy must never crash module evaluation, even when a foreign
|
|
4
|
+
// hook is frozen or otherwise rejects patching.
|
|
5
|
+
try {
|
|
6
|
+
getRDTHook();
|
|
7
|
+
} catch {}
|
package/src/rdt-hook.ts
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
|
-
//
|
|
2
|
-
// this file is super important to load the __REACT_DEVTOOLS_GLOBAL_HOOK__ object
|
|
3
|
-
// without this, we can't stub the React DevTools global hook, we don't have a way to instrument the application
|
|
4
|
-
// make sure you import this file first before anything else (particularly React)
|
|
1
|
+
// This module must load before React so renderers can inject into the hook.
|
|
5
2
|
|
|
6
|
-
import type { ReactDevToolsGlobalHook, ReactRenderer } from "./
|
|
7
|
-
|
|
3
|
+
import type { ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
|
|
4
|
+
|
|
5
|
+
export interface Unsubscribe extends Disposable {
|
|
6
|
+
(): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ActiveListener {
|
|
10
|
+
(): unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface RendererInjectListener {
|
|
14
|
+
(renderer: ReactRenderer): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface RDTHookReplaceListener {
|
|
18
|
+
(rdtHook: ReactDevToolsGlobalHook): void;
|
|
19
|
+
}
|
|
8
20
|
|
|
9
21
|
export const version = process.env.VERSION;
|
|
10
22
|
export const BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`;
|
|
11
23
|
|
|
12
24
|
const objectDefineProperty = Object.defineProperty;
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
14
|
-
const objectHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
15
25
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
const noOp = (): void => {};
|
|
27
|
+
|
|
28
|
+
export const createUnsubscribe = (unsubscribe: () => void): Unsubscribe =>
|
|
29
|
+
Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
|
|
19
30
|
|
|
20
31
|
const checkDCE = (functionToCheck: unknown): void => {
|
|
21
32
|
try {
|
|
@@ -26,7 +37,7 @@ const checkDCE = (functionToCheck: unknown): void => {
|
|
|
26
37
|
"React is running in production mode, but dead code " +
|
|
27
38
|
"elimination has not been applied. Read how to correctly " +
|
|
28
39
|
"configure React for production: " +
|
|
29
|
-
"https://
|
|
40
|
+
"https://react.dev/link/perf-use-production-build",
|
|
30
41
|
);
|
|
31
42
|
});
|
|
32
43
|
}
|
|
@@ -39,62 +50,104 @@ export const isRealReactDevtools = (
|
|
|
39
50
|
return Boolean(rdtHook && "getFiberRoots" in rdtHook);
|
|
40
51
|
};
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
// HACK: react-refresh's inject wrapper is the only stock hook whose parameter is named
|
|
54
|
+
// "injected", so its string form identifies hooks installed by react-refresh:
|
|
55
|
+
// https://github.com/facebook/react/blob/main/packages/react-refresh/src/ReactFreshRuntime.js (injectIntoGlobalHook)
|
|
45
56
|
export const isReactRefresh = (
|
|
46
57
|
rdtHook: ReactDevToolsGlobalHook | undefined | null = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__,
|
|
47
58
|
): boolean => {
|
|
48
|
-
if (
|
|
49
|
-
if (rdtHook
|
|
50
|
-
|
|
59
|
+
if (!rdtHook || typeof rdtHook.inject !== "function") return false;
|
|
60
|
+
if (isRealReactDevtools(rdtHook)) return false;
|
|
61
|
+
try {
|
|
62
|
+
return Function.prototype.toString.call(rdtHook.inject).includes("(injected)");
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
51
65
|
}
|
|
52
|
-
// https://github.com/facebook/react/blob/8f8b336734d7c807f5aa11b0f31540e63302d789/packages/react-refresh/src/ReactFreshRuntime.js#L459
|
|
53
|
-
return Boolean(injectFnStr?.includes("(injected)"));
|
|
54
66
|
};
|
|
55
67
|
|
|
56
|
-
export const _onActiveListeners = new Set<
|
|
68
|
+
export const _onActiveListeners = new Set<ActiveListener>();
|
|
57
69
|
|
|
58
70
|
export const _renderers = new Set<ReactRenderer>();
|
|
59
71
|
|
|
60
|
-
const rendererInjectListeners = new Set<
|
|
61
|
-
|
|
62
|
-
// wrapper in the call chain, so notifications
|
|
72
|
+
const rendererInjectListeners = new Set<RendererInjectListener>();
|
|
73
|
+
const rdtHookReplaceListeners = new Set<RDTHookReplaceListener>();
|
|
74
|
+
// HACK: Hook replacement can leave an old inject wrapper in the call chain, so notifications must be deduplicated.
|
|
63
75
|
const notifiedRenderers = new WeakSet<ReactRenderer>();
|
|
64
|
-
|
|
76
|
+
const notifyingInjectByHook = new WeakMap<
|
|
77
|
+
ReactDevToolsGlobalHook,
|
|
78
|
+
ReactDevToolsGlobalHook["inject"]
|
|
79
|
+
>();
|
|
80
|
+
|
|
81
|
+
const notifyActiveListeners = (): void => {
|
|
82
|
+
for (const listener of _onActiveListeners) {
|
|
83
|
+
listener();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const notifyRendererInjectListeners = (renderer: ReactRenderer): void => {
|
|
88
|
+
for (const listener of rendererInjectListeners) {
|
|
89
|
+
listener(renderer);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
65
92
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
93
|
+
const notifyRDTHookReplaceListeners = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
94
|
+
for (const listener of rdtHookReplaceListeners) {
|
|
95
|
+
listener(rdtHook);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const setRendererInjectDispatcher = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
100
|
+
if (rdtHook.inject === notifyingInjectByHook.get(rdtHook)) return;
|
|
101
|
+
const previousInject = rdtHook.inject;
|
|
69
102
|
const nextInject = (renderer: ReactRenderer) => {
|
|
70
|
-
const rendererId =
|
|
103
|
+
const rendererId = previousInject.call(rdtHook, renderer);
|
|
71
104
|
if (!notifiedRenderers.has(renderer)) {
|
|
72
105
|
notifiedRenderers.add(renderer);
|
|
73
|
-
|
|
74
|
-
listener(renderer);
|
|
75
|
-
}
|
|
106
|
+
notifyRendererInjectListeners(renderer);
|
|
76
107
|
}
|
|
77
108
|
return rendererId;
|
|
78
109
|
};
|
|
79
110
|
rdtHook.inject = nextInject;
|
|
80
|
-
|
|
111
|
+
notifyingInjectByHook.set(rdtHook, nextInject);
|
|
81
112
|
};
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* single shared inject wrapper lets multiple consumers (override methods,
|
|
86
|
-
* react-refresh, user code) observe renderers without stacking patches
|
|
87
|
-
* whose restore order matters. Returns an unsubscribe function.
|
|
88
|
-
*/
|
|
89
|
-
export const onRendererInject = (listener: (renderer: ReactRenderer) => void): Unsubscribe => {
|
|
90
|
-
ensureInjectNotifiesListeners(getRDTHook());
|
|
114
|
+
export const onRendererInject = (listener: RendererInjectListener): Unsubscribe => {
|
|
115
|
+
setRendererInjectDispatcher(getRDTHook());
|
|
91
116
|
rendererInjectListeners.add(listener);
|
|
92
|
-
return
|
|
117
|
+
return createUnsubscribe(() => {
|
|
93
118
|
rendererInjectListeners.delete(listener);
|
|
94
119
|
});
|
|
95
120
|
};
|
|
96
121
|
|
|
97
|
-
export const
|
|
122
|
+
export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe => {
|
|
123
|
+
rdtHookReplaceListeners.add(listener);
|
|
124
|
+
return createUnsubscribe(() => {
|
|
125
|
+
rdtHookReplaceListeners.delete(listener);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRenderer> => {
|
|
130
|
+
if (!(rdtHook.renderers instanceof Map)) {
|
|
131
|
+
rdtHook.renderers = new Map();
|
|
132
|
+
}
|
|
133
|
+
return rdtHook.renderers;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const trackInjectedRenderer = (
|
|
137
|
+
rdtHook: ReactDevToolsGlobalHook,
|
|
138
|
+
renderers: Map<number, ReactRenderer>,
|
|
139
|
+
rendererId: number,
|
|
140
|
+
renderer: ReactRenderer,
|
|
141
|
+
): void => {
|
|
142
|
+
renderers.set(rendererId, renderer);
|
|
143
|
+
_renderers.add(renderer);
|
|
144
|
+
if (!rdtHook._instrumentationIsActive) {
|
|
145
|
+
rdtHook._instrumentationIsActive = true;
|
|
146
|
+
notifyActiveListeners();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook => {
|
|
98
151
|
if (onActive) {
|
|
99
152
|
_onActiveListeners.add(onActive);
|
|
100
153
|
}
|
|
@@ -105,20 +158,15 @@ export const installRDTHook = (onActive?: () => unknown): ReactDevToolsGlobalHoo
|
|
|
105
158
|
_instrumentationSource: BIPPY_INSTRUMENTATION_STRING,
|
|
106
159
|
checkDCE,
|
|
107
160
|
hasUnsupportedRendererAttached: false,
|
|
108
|
-
inject(renderer) {
|
|
161
|
+
inject: (renderer) => {
|
|
109
162
|
const nextRendererId = ++rendererIdCounter;
|
|
110
|
-
renderers
|
|
111
|
-
_renderers.add(renderer);
|
|
112
|
-
if (!rdtHook._instrumentationIsActive) {
|
|
113
|
-
rdtHook._instrumentationIsActive = true;
|
|
114
|
-
_onActiveListeners.forEach((listener) => listener());
|
|
115
|
-
}
|
|
163
|
+
trackInjectedRenderer(rdtHook, renderers, nextRendererId, renderer);
|
|
116
164
|
return nextRendererId;
|
|
117
165
|
},
|
|
118
|
-
on:
|
|
119
|
-
onCommitFiberRoot:
|
|
120
|
-
onCommitFiberUnmount:
|
|
121
|
-
onPostCommitFiberRoot:
|
|
166
|
+
on: noOp,
|
|
167
|
+
onCommitFiberRoot: noOp,
|
|
168
|
+
onCommitFiberUnmount: noOp,
|
|
169
|
+
onPostCommitFiberRoot: noOp,
|
|
122
170
|
renderers,
|
|
123
171
|
supportsFiber: true,
|
|
124
172
|
supportsFlight: true,
|
|
@@ -134,137 +182,110 @@ export const installRDTHook = (onActive?: () => unknown): ReactDevToolsGlobalHoo
|
|
|
134
182
|
if (newHook && typeof newHook === "object") {
|
|
135
183
|
const ourRenderers = rdtHook.renderers;
|
|
136
184
|
rdtHook = newHook;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
185
|
+
const nextRenderers = getRendererMap(rdtHook);
|
|
186
|
+
ourRenderers.forEach((renderer, rendererId) => {
|
|
187
|
+
_renderers.add(renderer);
|
|
188
|
+
nextRenderers.set(rendererId, renderer);
|
|
189
|
+
});
|
|
190
|
+
if (ourRenderers.size > 0 || rdtHookReplaceListeners.size > 0) {
|
|
143
191
|
patchRDTHook(onActive);
|
|
144
192
|
}
|
|
193
|
+
notifyRDTHookReplaceListeners(rdtHook);
|
|
145
194
|
}
|
|
146
195
|
},
|
|
147
196
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
if (typeof window !== "undefined") {
|
|
198
|
+
// HACK: The DevTools extension uses hasOwnProperty to decide whether it may replace an earlier hook.
|
|
199
|
+
const originalWindowHasOwnProperty = window.hasOwnProperty.bind(window);
|
|
200
|
+
const originalHasOwnPropertyDescriptor = Object.getOwnPropertyDescriptor(
|
|
201
|
+
window,
|
|
202
|
+
"hasOwnProperty",
|
|
203
|
+
);
|
|
204
|
+
let didRunHasOwnPropertyHack = false;
|
|
205
|
+
const restoreWindowHasOwnProperty = (): void => {
|
|
206
|
+
if (originalHasOwnPropertyDescriptor) {
|
|
207
|
+
objectDefineProperty(window, "hasOwnProperty", originalHasOwnPropertyDescriptor);
|
|
208
|
+
} else {
|
|
209
|
+
Reflect.deleteProperty(window, "hasOwnProperty");
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
objectDefineProperty(window, "hasOwnProperty", {
|
|
213
|
+
configurable: true,
|
|
214
|
+
value: (propertyKey: PropertyKey) => {
|
|
215
|
+
if (!didRunHasOwnPropertyHack && propertyKey === "__REACT_DEVTOOLS_GLOBAL_HOOK__") {
|
|
216
|
+
didRunHasOwnPropertyHack = true;
|
|
217
|
+
restoreWindowHasOwnProperty();
|
|
158
218
|
globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ = undefined;
|
|
159
|
-
|
|
160
|
-
hasRanHack = true;
|
|
161
|
-
return -0;
|
|
219
|
+
return false;
|
|
162
220
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
221
|
+
return originalWindowHasOwnProperty(propertyKey);
|
|
222
|
+
},
|
|
223
|
+
writable: true,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
168
226
|
} catch {
|
|
169
227
|
patchRDTHook(onActive);
|
|
170
228
|
}
|
|
171
229
|
return rdtHook;
|
|
172
230
|
};
|
|
173
231
|
|
|
174
|
-
export const patchRDTHook = (onActive?:
|
|
232
|
+
export const patchRDTHook = (onActive?: ActiveListener): void => {
|
|
175
233
|
if (onActive) {
|
|
176
234
|
_onActiveListeners.add(onActive);
|
|
177
235
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (rdtHook.renderers.size) {
|
|
194
|
-
rdtHook._instrumentationIsActive = true;
|
|
195
|
-
_onActiveListeners.forEach((listener) => listener());
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const prevInject = rdtHook.inject;
|
|
199
|
-
const isRefresh = isReactRefresh(rdtHook);
|
|
200
|
-
if (isRefresh && !isReactDevtools) {
|
|
201
|
-
isReactRefreshOverride = true;
|
|
202
|
-
// but since the underlying implementation doens't care,
|
|
203
|
-
// it's ok: https://github.com/facebook/react/blob/18eaf51bd51fed8dfed661d64c306759101d0bfd/packages/react-refresh/src/ReactFreshRuntime.js#L430
|
|
204
|
-
const injectedRendererId = rdtHook.inject({
|
|
205
|
-
scheduleRefresh() {},
|
|
206
|
-
} as unknown as ReactRenderer);
|
|
207
|
-
if (injectedRendererId) {
|
|
208
|
-
rdtHook._instrumentationIsActive = true;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
rdtHook.inject = (renderer) => {
|
|
212
|
-
const rendererId = prevInject(renderer);
|
|
213
|
-
_renderers.add(renderer);
|
|
214
|
-
if (isRefresh) {
|
|
215
|
-
// react refresh doesn't inject this properly
|
|
216
|
-
// https://github.com/facebook/react/blob/18eaf51bd51fed8dfed661d64c306759101d0bfd/packages/react-refresh/src/ReactFreshRuntime.js#L430
|
|
217
|
-
rdtHook.renderers.set(rendererId, renderer);
|
|
218
|
-
}
|
|
219
|
-
rdtHook._instrumentationIsActive = true;
|
|
220
|
-
_onActiveListeners.forEach((listener) => listener());
|
|
221
|
-
return rendererId;
|
|
222
|
-
};
|
|
236
|
+
let didNotifyActiveListeners = false;
|
|
237
|
+
const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
238
|
+
if (!rdtHook) return;
|
|
239
|
+
const renderers = getRendererMap(rdtHook);
|
|
240
|
+
if (!rdtHook._instrumentationSource) {
|
|
241
|
+
rdtHook.checkDCE = checkDCE;
|
|
242
|
+
rdtHook.supportsFiber = true;
|
|
243
|
+
rdtHook.supportsFlight = true;
|
|
244
|
+
rdtHook.hasUnsupportedRendererAttached = false;
|
|
245
|
+
rdtHook._instrumentationSource = BIPPY_INSTRUMENTATION_STRING;
|
|
246
|
+
rdtHook._instrumentationIsActive = false;
|
|
247
|
+
// HACK: DevTools detection must happen after setting the source to avoid recursive patching.
|
|
248
|
+
const isReactDevtools = isRealReactDevtools(rdtHook);
|
|
249
|
+
if (!isReactDevtools) {
|
|
250
|
+
rdtHook.on = noOp;
|
|
223
251
|
}
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
rdtHook._instrumentationIsActive
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
) {
|
|
230
|
-
|
|
252
|
+
if (renderers.size) {
|
|
253
|
+
renderers.forEach((renderer) => _renderers.add(renderer));
|
|
254
|
+
rdtHook._instrumentationIsActive = true;
|
|
255
|
+
notifyActiveListeners();
|
|
256
|
+
didNotifyActiveListeners = true;
|
|
257
|
+
} else if (!isReactDevtools && isReactRefresh(rdtHook)) {
|
|
258
|
+
// HACK: react-refresh's stub inject never records renderers, so a React app
|
|
259
|
+
// that injected before bippy loaded is undetectable through the renderers map.
|
|
260
|
+
// A react-refresh hook implies a dev renderer, so activate immediately.
|
|
261
|
+
rdtHook._instrumentationIsActive = true;
|
|
262
|
+
notifyActiveListeners();
|
|
263
|
+
didNotifyActiveListeners = true;
|
|
231
264
|
}
|
|
232
|
-
|
|
265
|
+
const previousInject = rdtHook.inject;
|
|
266
|
+
rdtHook.inject = (renderer) => {
|
|
267
|
+
const rendererId = previousInject.call(rdtHook, renderer);
|
|
268
|
+
trackInjectedRenderer(rdtHook, renderers, rendererId, renderer);
|
|
269
|
+
return rendererId;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
|
|
273
|
+
onActive?.();
|
|
274
|
+
}
|
|
233
275
|
};
|
|
234
276
|
|
|
235
277
|
export const hasRDTHook = (): boolean => {
|
|
236
|
-
return
|
|
278
|
+
return Object.hasOwn(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__");
|
|
237
279
|
};
|
|
238
280
|
|
|
239
281
|
/**
|
|
240
282
|
* Returns the current React DevTools global hook.
|
|
241
283
|
*/
|
|
242
|
-
export const getRDTHook = (onActive?:
|
|
284
|
+
export const getRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook => {
|
|
243
285
|
if (!hasRDTHook()) {
|
|
244
286
|
return installRDTHook(onActive);
|
|
245
287
|
}
|
|
246
288
|
|
|
247
289
|
patchRDTHook(onActive);
|
|
248
|
-
|
|
249
|
-
return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ as ReactDevToolsGlobalHook;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
export const isClientEnvironment = (): boolean => {
|
|
253
|
-
return Boolean(
|
|
254
|
-
typeof window !== "undefined" &&
|
|
255
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
256
|
-
(window.document?.createElement || window.navigator?.product === "ReactNative"),
|
|
257
|
-
);
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Usually used purely for side effect
|
|
262
|
-
*/
|
|
263
|
-
export const safelyInstallRDTHook = () => {
|
|
264
|
-
try {
|
|
265
|
-
// __REACT_DEVTOOLS_GLOBAL_HOOK__ must exist before React is ever executed
|
|
266
|
-
if (isClientEnvironment()) {
|
|
267
|
-
getRDTHook();
|
|
268
|
-
}
|
|
269
|
-
} catch {}
|
|
290
|
+
return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? installRDTHook(onActive);
|
|
270
291
|
};
|