bippy 0.6.1-dev.d7876ea → 0.6.1-dev.f9e6c65
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 +159 -487
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +24 -70
- package/dist/core.d.ts +24 -70
- package/dist/core.js +1 -1
- package/dist/core2.cjs +1 -1
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +1 -1
- package/dist/errors.d.cts +33 -59
- package/dist/errors.d.ts +33 -59
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +8 -0
- package/dist/install-hook-only.d.ts +8 -0
- 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 +12 -13
- package/dist/source.d.cts +85 -77
- package/dist/source.d.ts +85 -77
- package/dist/source.js +12 -13
- package/package.json +6 -3
- package/src/core.ts +141 -564
- package/src/errors.ts +0 -65
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +39 -45
- package/src/{generated/react-work-tags.js → react-internals/generated/react-work-tags.ts} +85 -7
- package/src/{react-internals.ts → react-internals/index.ts} +6 -4
- package/src/{semver.ts → react-internals/semver.ts} +2 -0
- package/src/{types.ts → react-internals/types.ts} +1 -84
- package/src/react.ts +76 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +42 -26
- package/src/source/index.ts +2 -0
- package/src/source/inspect-hooks.ts +74 -96
- package/src/source/owner-stack.ts +66 -91
- 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 +421 -108
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- package/src/generated/react-work-tags.d.ts +0 -261
- package/src/unsubscribe.ts +0 -17
package/src/errors.ts
CHANGED
|
@@ -5,55 +5,6 @@ export class BippyError extends Error {
|
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export class BippyInstrumentationError extends BippyError {
|
|
9
|
-
readonly callbackName: string;
|
|
10
|
-
readonly instrumentationName: string;
|
|
11
|
-
|
|
12
|
-
constructor(instrumentationName: string, callbackName: string, cause: unknown) {
|
|
13
|
-
super(
|
|
14
|
-
`Bippy instrumentation "${instrumentationName}" callback "${callbackName}" failed`,
|
|
15
|
-
cause,
|
|
16
|
-
);
|
|
17
|
-
this.name = "BippyInstrumentationError";
|
|
18
|
-
this.callbackName = callbackName;
|
|
19
|
-
this.instrumentationName = instrumentationName;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class BippyReactDevToolsError extends BippyError {
|
|
24
|
-
readonly callbackName: string;
|
|
25
|
-
|
|
26
|
-
constructor(callbackName: string, cause: unknown) {
|
|
27
|
-
super(`React DevTools callback "${callbackName}" failed`, cause);
|
|
28
|
-
this.name = "BippyReactDevToolsError";
|
|
29
|
-
this.callbackName = callbackName;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class BippyHookListenerError extends BippyError {
|
|
34
|
-
readonly listenerName: string;
|
|
35
|
-
|
|
36
|
-
constructor(listenerName: string, cause: unknown) {
|
|
37
|
-
super(`Bippy hook listener "${listenerName}" failed`, cause);
|
|
38
|
-
this.name = "BippyHookListenerError";
|
|
39
|
-
this.listenerName = listenerName;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class BippyHookInstallationError extends BippyError {
|
|
44
|
-
constructor(cause: unknown) {
|
|
45
|
-
super("Bippy could not install the React DevTools hook", cause);
|
|
46
|
-
this.name = "BippyHookInstallationError";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class BippyReactBuildError extends BippyError {
|
|
51
|
-
constructor(message: string, cause?: unknown) {
|
|
52
|
-
super(message, cause);
|
|
53
|
-
this.name = "BippyReactBuildError";
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
8
|
export class BippyHookInspectionError extends BippyError {
|
|
58
9
|
constructor(message: string, cause?: unknown) {
|
|
59
10
|
super(message, cause);
|
|
@@ -81,19 +32,3 @@ export class BippySourceMapError extends BippyError {
|
|
|
81
32
|
this.name = "BippySourceMapError";
|
|
82
33
|
}
|
|
83
34
|
}
|
|
84
|
-
|
|
85
|
-
interface BippyErrorFactory {
|
|
86
|
-
(cause: unknown): BippyError;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export const runWithBippyError = <Result>(
|
|
90
|
-
callback: () => Result,
|
|
91
|
-
createError: BippyErrorFactory,
|
|
92
|
-
): Result => {
|
|
93
|
-
try {
|
|
94
|
-
return callback();
|
|
95
|
-
} catch (cause) {
|
|
96
|
-
if (cause instanceof BippyError) throw cause;
|
|
97
|
-
throw createError(cause);
|
|
98
|
-
}
|
|
99
|
-
};
|
package/src/index.ts
CHANGED
package/src/install-hook-only.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getRDTHook } from "./rdt-hook.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
getRDTHook();
|
package/src/rdt-hook.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
// This module must load before React so renderers can inject into the hook.
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "./errors.js";
|
|
9
|
-
import type { ReactDevToolsGlobalHook, ReactRenderer } from "./types.js";
|
|
10
|
-
import { toUnsubscribe, type Unsubscribe } from "./unsubscribe.js";
|
|
3
|
+
import type { ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
|
|
4
|
+
|
|
5
|
+
export interface Unsubscribe extends Disposable {
|
|
6
|
+
(): void;
|
|
7
|
+
}
|
|
11
8
|
|
|
12
9
|
interface ActiveListener {
|
|
13
10
|
(): unknown;
|
|
@@ -28,20 +25,19 @@ const objectDefineProperty = Object.defineProperty;
|
|
|
28
25
|
|
|
29
26
|
const noOp = (): void => {};
|
|
30
27
|
|
|
31
|
-
const
|
|
32
|
-
|
|
28
|
+
export const createUnsubscribe = (unsubscribe: () => void): Unsubscribe =>
|
|
29
|
+
Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
|
|
33
30
|
|
|
34
31
|
const checkDCE = (functionToCheck: unknown): void => {
|
|
35
32
|
try {
|
|
36
33
|
const code = Function.prototype.toString.call(functionToCheck);
|
|
37
|
-
if (code.
|
|
38
|
-
// HACK: React DevTools reports failed dead-code elimination asynchronously.
|
|
34
|
+
if (code.indexOf("^_^") > -1) {
|
|
39
35
|
setTimeout(() => {
|
|
40
|
-
throw new
|
|
36
|
+
throw new Error(
|
|
41
37
|
"React is running in production mode, but dead code " +
|
|
42
38
|
"elimination has not been applied. Read how to correctly " +
|
|
43
39
|
"configure React for production: " +
|
|
44
|
-
"https://
|
|
40
|
+
"https://react.dev/link/perf-use-production-build",
|
|
45
41
|
);
|
|
46
42
|
});
|
|
47
43
|
}
|
|
@@ -69,19 +65,19 @@ const notifyingInjectByHook = new WeakMap<
|
|
|
69
65
|
|
|
70
66
|
const notifyActiveListeners = (): void => {
|
|
71
67
|
for (const listener of _onActiveListeners) {
|
|
72
|
-
|
|
68
|
+
listener();
|
|
73
69
|
}
|
|
74
70
|
};
|
|
75
71
|
|
|
76
72
|
const notifyRendererInjectListeners = (renderer: ReactRenderer): void => {
|
|
77
73
|
for (const listener of rendererInjectListeners) {
|
|
78
|
-
|
|
74
|
+
listener(renderer);
|
|
79
75
|
}
|
|
80
76
|
};
|
|
81
77
|
|
|
82
78
|
const notifyRDTHookReplaceListeners = (rdtHook: ReactDevToolsGlobalHook): void => {
|
|
83
79
|
for (const listener of rdtHookReplaceListeners) {
|
|
84
|
-
|
|
80
|
+
listener(rdtHook);
|
|
85
81
|
}
|
|
86
82
|
};
|
|
87
83
|
|
|
@@ -103,14 +99,14 @@ const setRendererInjectDispatcher = (rdtHook: ReactDevToolsGlobalHook): void =>
|
|
|
103
99
|
export const onRendererInject = (listener: RendererInjectListener): Unsubscribe => {
|
|
104
100
|
setRendererInjectDispatcher(getRDTHook());
|
|
105
101
|
rendererInjectListeners.add(listener);
|
|
106
|
-
return
|
|
102
|
+
return createUnsubscribe(() => {
|
|
107
103
|
rendererInjectListeners.delete(listener);
|
|
108
104
|
});
|
|
109
105
|
};
|
|
110
106
|
|
|
111
107
|
export const onRDTHookReplace = (listener: RDTHookReplaceListener): Unsubscribe => {
|
|
112
108
|
rdtHookReplaceListeners.add(listener);
|
|
113
|
-
return
|
|
109
|
+
return createUnsubscribe(() => {
|
|
114
110
|
rdtHookReplaceListeners.delete(listener);
|
|
115
111
|
});
|
|
116
112
|
};
|
|
@@ -122,6 +118,20 @@ const getRendererMap = (rdtHook: ReactDevToolsGlobalHook): Map<number, ReactRend
|
|
|
122
118
|
return rdtHook.renderers;
|
|
123
119
|
};
|
|
124
120
|
|
|
121
|
+
const trackInjectedRenderer = (
|
|
122
|
+
rdtHook: ReactDevToolsGlobalHook,
|
|
123
|
+
renderers: Map<number, ReactRenderer>,
|
|
124
|
+
rendererId: number,
|
|
125
|
+
renderer: ReactRenderer,
|
|
126
|
+
): void => {
|
|
127
|
+
renderers.set(rendererId, renderer);
|
|
128
|
+
_renderers.add(renderer);
|
|
129
|
+
if (!rdtHook._instrumentationIsActive) {
|
|
130
|
+
rdtHook._instrumentationIsActive = true;
|
|
131
|
+
notifyActiveListeners();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
125
135
|
export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook => {
|
|
126
136
|
if (onActive) {
|
|
127
137
|
_onActiveListeners.add(onActive);
|
|
@@ -135,12 +145,7 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
135
145
|
hasUnsupportedRendererAttached: false,
|
|
136
146
|
inject: (renderer) => {
|
|
137
147
|
const nextRendererId = ++rendererIdCounter;
|
|
138
|
-
renderers
|
|
139
|
-
_renderers.add(renderer);
|
|
140
|
-
if (!rdtHook._instrumentationIsActive) {
|
|
141
|
-
rdtHook._instrumentationIsActive = true;
|
|
142
|
-
notifyActiveListeners();
|
|
143
|
-
}
|
|
148
|
+
trackInjectedRenderer(rdtHook, renderers, nextRendererId, renderer);
|
|
144
149
|
return nextRendererId;
|
|
145
150
|
},
|
|
146
151
|
on: noOp,
|
|
@@ -163,12 +168,10 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
163
168
|
const ourRenderers = rdtHook.renderers;
|
|
164
169
|
rdtHook = newHook;
|
|
165
170
|
const nextRenderers = getRendererMap(rdtHook);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
}
|
|
171
|
+
ourRenderers.forEach((renderer, rendererId) => {
|
|
172
|
+
_renderers.add(renderer);
|
|
173
|
+
nextRenderers.set(rendererId, renderer);
|
|
174
|
+
});
|
|
172
175
|
if (ourRenderers.size > 0 || rdtHookReplaceListeners.size > 0) {
|
|
173
176
|
patchRDTHook(onActive);
|
|
174
177
|
}
|
|
@@ -193,14 +196,14 @@ export const installRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHo
|
|
|
193
196
|
};
|
|
194
197
|
objectDefineProperty(window, "hasOwnProperty", {
|
|
195
198
|
configurable: true,
|
|
196
|
-
value: (
|
|
197
|
-
if (!didRunHasOwnPropertyHack &&
|
|
199
|
+
value: (propertyKey: PropertyKey) => {
|
|
200
|
+
if (!didRunHasOwnPropertyHack && propertyKey === "__REACT_DEVTOOLS_GLOBAL_HOOK__") {
|
|
198
201
|
didRunHasOwnPropertyHack = true;
|
|
199
202
|
restoreWindowHasOwnProperty();
|
|
200
203
|
globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ = undefined;
|
|
201
204
|
return false;
|
|
202
205
|
}
|
|
203
|
-
return originalWindowHasOwnProperty(
|
|
206
|
+
return originalWindowHasOwnProperty(propertyKey);
|
|
204
207
|
},
|
|
205
208
|
writable: true,
|
|
206
209
|
});
|
|
@@ -240,17 +243,12 @@ export const patchRDTHook = (onActive?: ActiveListener): void => {
|
|
|
240
243
|
const previousInject = rdtHook.inject;
|
|
241
244
|
rdtHook.inject = (renderer) => {
|
|
242
245
|
const rendererId = previousInject.call(rdtHook, renderer);
|
|
243
|
-
|
|
244
|
-
renderers.set(rendererId, renderer);
|
|
245
|
-
if (!rdtHook._instrumentationIsActive) {
|
|
246
|
-
rdtHook._instrumentationIsActive = true;
|
|
247
|
-
notifyActiveListeners();
|
|
248
|
-
}
|
|
246
|
+
trackInjectedRenderer(rdtHook, renderers, rendererId, renderer);
|
|
249
247
|
return rendererId;
|
|
250
248
|
};
|
|
251
249
|
}
|
|
252
250
|
if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
|
|
253
|
-
|
|
251
|
+
onActive?.();
|
|
254
252
|
}
|
|
255
253
|
};
|
|
256
254
|
|
|
@@ -269,7 +267,3 @@ export const getRDTHook = (onActive?: ActiveListener): ReactDevToolsGlobalHook =
|
|
|
269
267
|
patchRDTHook(onActive);
|
|
270
268
|
return globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? installRDTHook(onActive);
|
|
271
269
|
};
|
|
272
|
-
|
|
273
|
-
export const safelyInstallRDTHook = (): void => {
|
|
274
|
-
runWithBippyError(getRDTHook, (cause) => new BippyHookInstallationError(cause));
|
|
275
|
-
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { compareSemver } from "../semver.js";
|
|
2
|
+
|
|
2
3
|
export const ReactBuildType = {
|
|
3
4
|
Development: 1,
|
|
4
5
|
Production: 0,
|
|
5
|
-
};
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
6
8
|
export const ReactFiberFlags = {
|
|
7
9
|
ChildDeletion: 16,
|
|
8
10
|
Cloned: 8,
|
|
@@ -13,7 +15,8 @@ export const ReactFiberFlags = {
|
|
|
13
15
|
Snapshot: 1024,
|
|
14
16
|
Update: 4,
|
|
15
17
|
Visibility: 8192,
|
|
16
|
-
};
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
17
20
|
export const ReactSymbols = {
|
|
18
21
|
CONCURRENT_MODE_NUMBER: 60111,
|
|
19
22
|
CONCURRENT_MODE_SYMBOL_DESCRIPTION: "react.concurrent_mode",
|
|
@@ -22,8 +25,49 @@ export const ReactSymbols = {
|
|
|
22
25
|
DEPRECATED_ASYNC_MODE_SYMBOL_STRING: "Symbol(react.async_mode)",
|
|
23
26
|
ELEMENT_SYMBOL_STRING: "Symbol(react.transitional.element)",
|
|
24
27
|
LEGACY_ELEMENT_SYMBOL_STRING: "Symbol(react.element)",
|
|
25
|
-
};
|
|
26
|
-
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export interface ReactWorkTagMap {
|
|
31
|
+
ActivityComponent: number;
|
|
32
|
+
CacheComponent: number;
|
|
33
|
+
ClassComponent: number;
|
|
34
|
+
ContextConsumer: number;
|
|
35
|
+
ContextProvider: number;
|
|
36
|
+
CoroutineComponent: number;
|
|
37
|
+
CoroutineHandlerPhase: number;
|
|
38
|
+
DehydratedSuspenseComponent: number;
|
|
39
|
+
ForwardRef: number;
|
|
40
|
+
Fragment: number;
|
|
41
|
+
FunctionComponent: number;
|
|
42
|
+
HostComponent: number;
|
|
43
|
+
HostHoistable: number;
|
|
44
|
+
HostPortal: number;
|
|
45
|
+
HostRoot: number;
|
|
46
|
+
HostSingleton: number;
|
|
47
|
+
HostText: number;
|
|
48
|
+
IncompleteClassComponent: number;
|
|
49
|
+
IncompleteFunctionComponent: number;
|
|
50
|
+
IndeterminateComponent: number;
|
|
51
|
+
LazyComponent: number;
|
|
52
|
+
LegacyHiddenComponent: number;
|
|
53
|
+
MemoComponent: number;
|
|
54
|
+
Mode: number;
|
|
55
|
+
OffscreenComponent: number;
|
|
56
|
+
Profiler: number;
|
|
57
|
+
ScopeComponent: number;
|
|
58
|
+
SimpleMemoComponent: number;
|
|
59
|
+
SuspenseComponent: number;
|
|
60
|
+
SuspenseListComponent: number;
|
|
61
|
+
Throw: number;
|
|
62
|
+
TracingMarkerComponent: number;
|
|
63
|
+
ViewTransitionComponent: number;
|
|
64
|
+
YieldComponent: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const defineReactWorkTags = <const VersionedWorkTags extends Record<string, ReactWorkTagMap>>(
|
|
68
|
+
workTags: VersionedWorkTags,
|
|
69
|
+
): VersionedWorkTags => workTags;
|
|
70
|
+
|
|
27
71
|
const reactWorkTagsByVersion = defineReactWorkTags({
|
|
28
72
|
"16.0.0": {
|
|
29
73
|
ActivityComponent: -1,
|
|
@@ -206,7 +250,37 @@ const reactWorkTagsByVersion = defineReactWorkTags({
|
|
|
206
250
|
YieldComponent: -1,
|
|
207
251
|
},
|
|
208
252
|
});
|
|
209
|
-
|
|
253
|
+
|
|
254
|
+
export type ReactWorkTagVersion = keyof typeof reactWorkTagsByVersion;
|
|
255
|
+
|
|
256
|
+
export interface GetReactWorkTags {
|
|
257
|
+
(): Readonly<ReactWorkTagMap>;
|
|
258
|
+
<Version extends ReactWorkTagVersion>(
|
|
259
|
+
reactVersion: Version,
|
|
260
|
+
): Readonly<(typeof reactWorkTagsByVersion)[Version]>;
|
|
261
|
+
(reactVersion: string): Readonly<ReactWorkTagMap>;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export type ReactWorkTag = Exclude<
|
|
265
|
+
(typeof reactWorkTagsByVersion)[ReactWorkTagVersion][keyof ReactWorkTagMap],
|
|
266
|
+
-1
|
|
267
|
+
>;
|
|
268
|
+
|
|
269
|
+
export type HostWorkTag = Exclude<
|
|
270
|
+
| (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostComponent"]
|
|
271
|
+
| (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostHoistable"]
|
|
272
|
+
| (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostSingleton"]
|
|
273
|
+
| (typeof reactWorkTagsByVersion)[ReactWorkTagVersion]["HostText"],
|
|
274
|
+
-1
|
|
275
|
+
>;
|
|
276
|
+
|
|
277
|
+
interface ReactWorkTagRange {
|
|
278
|
+
isMinimumExcluded: boolean;
|
|
279
|
+
minimumVersion: string;
|
|
280
|
+
workTags: Readonly<ReactWorkTagMap>;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const reactWorkTagRanges: ReactWorkTagRange[] = [
|
|
210
284
|
{
|
|
211
285
|
isMinimumExcluded: true,
|
|
212
286
|
minimumVersion: "17.0.1",
|
|
@@ -228,10 +302,14 @@ const reactWorkTagRanges = [
|
|
|
228
302
|
workTags: reactWorkTagsByVersion["16.4.3-alpha"],
|
|
229
303
|
},
|
|
230
304
|
];
|
|
231
|
-
|
|
305
|
+
|
|
306
|
+
const defaultReactWorkTags = reactWorkTagsByVersion["17.0.2"];
|
|
307
|
+
|
|
308
|
+
export const getReactWorkTags: GetReactWorkTags = (reactVersion?: string) => {
|
|
309
|
+
if (reactVersion === undefined) return defaultReactWorkTags;
|
|
232
310
|
for (const range of reactWorkTagRanges) {
|
|
233
311
|
const comparison = compareSemver(reactVersion, range.minimumVersion);
|
|
234
|
-
if (comparison === null) return
|
|
312
|
+
if (comparison === null) return defaultReactWorkTags;
|
|
235
313
|
if (comparison === 1 || (comparison === 0 && !range.isMinimumExcluded)) {
|
|
236
314
|
return range.workTags;
|
|
237
315
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getReactWorkTags, ReactFiberFlags } from "./generated/react-work-tags.js";
|
|
2
2
|
import type { ReactWorkTagMap } from "./generated/react-work-tags.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isSemver } from "./semver.js";
|
|
4
4
|
import type { Fiber, ReactRenderer } from "./types.js";
|
|
5
5
|
|
|
6
|
+
export { compareSemver } from "./semver.js";
|
|
6
7
|
export {
|
|
7
8
|
getReactWorkTags,
|
|
8
9
|
ReactBuildType,
|
|
@@ -15,18 +16,19 @@ export type {
|
|
|
15
16
|
ReactWorkTagMap,
|
|
16
17
|
ReactWorkTagVersion,
|
|
17
18
|
} from "./generated/react-work-tags.js";
|
|
19
|
+
export * from "./types.js";
|
|
18
20
|
|
|
19
|
-
const defaultReactWorkTags = getReactWorkTags(
|
|
21
|
+
const defaultReactWorkTags = getReactWorkTags();
|
|
20
22
|
const fiberReactWorkTags = new WeakMap<Fiber, Readonly<ReactWorkTagMap>>();
|
|
21
23
|
|
|
22
24
|
export const getReactWorkTagsForRenderer = (
|
|
23
25
|
renderer?: ReactRenderer | null,
|
|
24
26
|
): Readonly<ReactWorkTagMap> => {
|
|
25
27
|
const reconcilerVersion = renderer?.reconcilerVersion;
|
|
26
|
-
if (reconcilerVersion &&
|
|
28
|
+
if (reconcilerVersion && isSemver(reconcilerVersion)) {
|
|
27
29
|
return getReactWorkTags(reconcilerVersion);
|
|
28
30
|
}
|
|
29
|
-
return getReactWorkTags(renderer
|
|
31
|
+
return renderer?.version ? getReactWorkTags(renderer.version) : defaultReactWorkTags;
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
export const setReactWorkTagsForFiber = (fiber: Fiber, renderer?: ReactRenderer): void => {
|
|
@@ -9,6 +9,8 @@ const SEMVER_PATTERN =
|
|
|
9
9
|
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
10
10
|
const NUMERIC_IDENTIFIER_PATTERN = /^\d+$/;
|
|
11
11
|
|
|
12
|
+
export const isSemver = (version: string): boolean => parseSemver(version) !== null;
|
|
13
|
+
|
|
12
14
|
const parseSemver = (version: string): SemanticVersion | null => {
|
|
13
15
|
const match = SEMVER_PATTERN.exec(version);
|
|
14
16
|
if (!match) return null;
|
|
@@ -1,85 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type ReactReconciler from "react-reconciler";
|
|
3
|
-
import type { HostWorkTag, ReactWorkTag } from "./react-
|
|
3
|
+
import type { HostWorkTag, ReactWorkTag } from "./generated/react-work-tags.js";
|
|
4
4
|
|
|
5
|
-
export type BundleType = ReactReconciler.BundleType;
|
|
6
|
-
export type Flags = ReactReconciler.Flags;
|
|
7
|
-
export type HookType = ReactReconciler.HookType;
|
|
8
|
-
export type LanePriority = ReactReconciler.LanePriority;
|
|
9
|
-
export type Lanes = ReactReconciler.Lanes;
|
|
10
|
-
export type MutableSource = ReactReconciler.MutableSource;
|
|
11
|
-
export type OpaqueHandle = ReactReconciler.OpaqueHandle;
|
|
12
|
-
export type OpaqueRoot = ReactReconciler.OpaqueRoot;
|
|
13
|
-
export type React$AbstractComponent<
|
|
14
|
-
Config,
|
|
15
|
-
Instance = unknown,
|
|
16
|
-
> = ReactReconciler.React$AbstractComponent<Config, Instance>;
|
|
17
|
-
export type RootTag = ReactReconciler.RootTag;
|
|
18
|
-
export type TypeOfMode = ReactReconciler.TypeOfMode;
|
|
19
5
|
export type WorkTag = ReactReconciler.WorkTag | ReactWorkTag;
|
|
20
6
|
|
|
21
|
-
export interface HostConfig<
|
|
22
|
-
Type = unknown,
|
|
23
|
-
HostProps = unknown,
|
|
24
|
-
Container = unknown,
|
|
25
|
-
Instance = unknown,
|
|
26
|
-
TextInstance = unknown,
|
|
27
|
-
SuspenseInstance = unknown,
|
|
28
|
-
HydratableInstance = unknown,
|
|
29
|
-
FormInstance = unknown,
|
|
30
|
-
PublicInstance = unknown,
|
|
31
|
-
HostContext = unknown,
|
|
32
|
-
ChildSet = unknown,
|
|
33
|
-
TimeoutHandle = unknown,
|
|
34
|
-
NoTimeout = unknown,
|
|
35
|
-
TransitionStatus = unknown,
|
|
36
|
-
> extends ReactReconciler.HostConfig<
|
|
37
|
-
Type,
|
|
38
|
-
HostProps,
|
|
39
|
-
Container,
|
|
40
|
-
Instance,
|
|
41
|
-
TextInstance,
|
|
42
|
-
SuspenseInstance,
|
|
43
|
-
HydratableInstance,
|
|
44
|
-
FormInstance,
|
|
45
|
-
PublicInstance,
|
|
46
|
-
HostContext,
|
|
47
|
-
ChildSet,
|
|
48
|
-
TimeoutHandle,
|
|
49
|
-
NoTimeout,
|
|
50
|
-
TransitionStatus
|
|
51
|
-
> {}
|
|
52
|
-
|
|
53
7
|
export interface Source extends ReactReconciler.Source {}
|
|
54
|
-
export interface RefObject extends ReactReconciler.RefObject {}
|
|
55
|
-
export interface Thenable<T> extends ReactReconciler.Thenable<T> {}
|
|
56
8
|
|
|
57
9
|
export interface ReactContext<T> extends ReactReconciler.ReactContext<T> {}
|
|
58
10
|
|
|
59
|
-
export interface ReactProviderType<T> extends ReactReconciler.ReactProviderType<T> {}
|
|
60
|
-
export interface ReactProvider<T> extends ReactReconciler.ReactProvider<T> {}
|
|
61
|
-
export interface ReactConsumer<T> extends ReactReconciler.ReactConsumer<T> {}
|
|
62
|
-
export interface ReactPortal extends ReactReconciler.ReactPortal {}
|
|
63
|
-
|
|
64
|
-
export interface ComponentSelector extends ReactReconciler.ComponentSelector {}
|
|
65
|
-
export interface HasPseudoClassSelector extends ReactReconciler.HasPseudoClassSelector {}
|
|
66
|
-
export interface RoleSelector extends ReactReconciler.RoleSelector {}
|
|
67
|
-
export interface TextSelector extends ReactReconciler.TextSelector {}
|
|
68
|
-
export interface TestNameSelector extends ReactReconciler.TestNameSelector {}
|
|
69
|
-
export type Selector = ReactReconciler.Selector;
|
|
70
|
-
|
|
71
|
-
export interface DevToolsConfig<
|
|
72
|
-
Instance = unknown,
|
|
73
|
-
TextInstance = unknown,
|
|
74
|
-
RendererInspectionConfig = unknown,
|
|
75
|
-
> extends ReactReconciler.DevToolsConfig<Instance, TextInstance, RendererInspectionConfig> {}
|
|
76
|
-
|
|
77
|
-
export interface SuspenseHydrationCallbacks<
|
|
78
|
-
SuspenseInstance = unknown,
|
|
79
|
-
> extends ReactReconciler.SuspenseHydrationCallbacks<SuspenseInstance> {}
|
|
80
|
-
|
|
81
|
-
export interface TransitionTracingCallbacks extends ReactReconciler.TransitionTracingCallbacks {}
|
|
82
|
-
|
|
83
11
|
// HACK: React 17 exposes observedBits while React 18+ exposes memoizedValue on context dependencies.
|
|
84
12
|
export interface ContextDependency<T> extends Omit<
|
|
85
13
|
ReactReconciler.ContextDependency<T>,
|
|
@@ -98,15 +26,6 @@ export interface Dependencies extends Omit<ReactReconciler.Dependencies, "firstC
|
|
|
98
26
|
firstContext: ContextDependency<unknown> | null;
|
|
99
27
|
}
|
|
100
28
|
|
|
101
|
-
export interface Effect {
|
|
102
|
-
[key: string]: unknown;
|
|
103
|
-
create: (...args: unknown[]) => unknown;
|
|
104
|
-
deps: null | unknown[];
|
|
105
|
-
destroy: ((...args: unknown[]) => unknown) | null;
|
|
106
|
-
next: Effect | null;
|
|
107
|
-
tag: number;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
29
|
export interface ServerComponentInfo {
|
|
111
30
|
name?: string;
|
|
112
31
|
env?: string;
|
|
@@ -131,8 +50,6 @@ export interface FiberDebugSource extends Source {
|
|
|
131
50
|
|
|
132
51
|
export interface FiberUpdateQueue {
|
|
133
52
|
[key: string]: unknown;
|
|
134
|
-
dispatch?: unknown;
|
|
135
|
-
lastEffect: Effect | null;
|
|
136
53
|
memoCache?: ReactMemoCache;
|
|
137
54
|
}
|
|
138
55
|
|
package/src/react.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import "./install-hook-only.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { isValidFiber } from "./core.js";
|
|
4
|
+
import type { Fiber } from "./react-internals/index.js";
|
|
5
|
+
|
|
6
|
+
export type { Fiber } from "./react-internals/index.js";
|
|
7
|
+
|
|
8
|
+
const preserveState = (state: undefined): undefined => state;
|
|
9
|
+
const readEmptySnapshot = (): undefined => undefined;
|
|
10
|
+
const unsubscribeFromEmptyStore = (): void => {};
|
|
11
|
+
const subscribeToEmptyStore = (): (() => void) => unsubscribeFromEmptyStore;
|
|
12
|
+
const useSyncExternalStore: unknown = Reflect.get(React, "useSyncExternalStore");
|
|
13
|
+
|
|
14
|
+
const captureFiberFromHook = (useCaptureHook: () => void): Fiber | null => {
|
|
15
|
+
const originalBind = Function.prototype.bind;
|
|
16
|
+
let capturedFiber: Fiber | null = null;
|
|
17
|
+
// HACK: React binds hook callbacks to the rendering Fiber in production but exposes no public API for it.
|
|
18
|
+
const bindProxy = new Proxy(originalBind, {
|
|
19
|
+
apply: (bind, functionToBind, boundArguments) => {
|
|
20
|
+
const fiber = boundArguments[1];
|
|
21
|
+
if (!capturedFiber && isValidFiber(fiber)) {
|
|
22
|
+
capturedFiber = fiber;
|
|
23
|
+
}
|
|
24
|
+
return Reflect.apply(bind, functionToBind, boundArguments);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
useCaptureHook();
|
|
31
|
+
} finally {
|
|
32
|
+
if (Function.prototype.bind === bindProxy) {
|
|
33
|
+
Reflect.set(Function.prototype, "bind", originalBind);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return capturedFiber;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const useExternalStoreCapture = (): void => {
|
|
41
|
+
Reflect.apply(useSyncExternalStore, React, [
|
|
42
|
+
subscribeToEmptyStore,
|
|
43
|
+
readEmptySnapshot,
|
|
44
|
+
readEmptySnapshot,
|
|
45
|
+
]);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const useReducerCapture = (): void => {
|
|
49
|
+
React.useReducer(preserveState, undefined);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const useFiberWithExternalStore = (): Fiber | undefined =>
|
|
53
|
+
captureFiberFromHook(useExternalStoreCapture) ?? undefined;
|
|
54
|
+
|
|
55
|
+
const useFiberWithReducer = (): Fiber | undefined => {
|
|
56
|
+
const committedFiberRef = React.useRef<Fiber | null>(null);
|
|
57
|
+
const renderedFiberRef = React.useRef<Fiber | null>(null);
|
|
58
|
+
const hookFiber = captureFiberFromHook(useReducerCapture);
|
|
59
|
+
const fiber =
|
|
60
|
+
hookFiber ??
|
|
61
|
+
(renderedFiberRef.current !== committedFiberRef.current
|
|
62
|
+
? renderedFiberRef.current
|
|
63
|
+
: committedFiberRef.current?.alternate) ??
|
|
64
|
+
committedFiberRef.current;
|
|
65
|
+
|
|
66
|
+
renderedFiberRef.current = fiber;
|
|
67
|
+
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
committedFiberRef.current = fiber;
|
|
70
|
+
}, [fiber]);
|
|
71
|
+
|
|
72
|
+
return fiber ?? undefined;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const useFiber =
|
|
76
|
+
typeof useSyncExternalStore === "function" ? useFiberWithExternalStore : useFiberWithReducer;
|