Skip to content

Commit bf76955

Browse files
authored
[DevTools] Extract getDispatcherRef and fork inspectHooks() to avoid pulling React into the build artifact (react#36681)
This is just a refactor to unblock the work on the Facade. This PR contains 2 changes: 1. `getDispatcherRef` was extracted from `fiber/renderer` into its own shared module. 2. `inspectHooksOfFiber` was forked. The new version specifies dispatcher as required and doesn't set it to default as `ReactSharedInternals`, which are pulling the whole `react` module as part of them: https://github.com/facebook/react/blob/63e95c2b51d9e9b8b9e15d656c1e11c393277699/packages/shared/ReactSharedInternals.js#L10-L15
1 parent e730b5e commit bf76955

4 files changed

Lines changed: 107 additions & 46 deletions

File tree

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,17 +1194,13 @@ function handleRenderFunctionError(error: any): void {
11941194
throw wrapperError;
11951195
}
11961196

1197-
export function inspectHooks<Props>(
1197+
// Shared implementation. Requires an explicit dispatcher and never references
1198+
// ReactSharedInternals, so importing it does not pull React into the bundle.
1199+
function inspectHooksImpl<Props>(
11981200
renderFunction: Props => React$Node,
11991201
props: Props,
1200-
currentDispatcher: ?CurrentDispatcherRef,
1202+
currentDispatcher: CurrentDispatcherRef,
12011203
): HooksTree {
1202-
// DevTools will pass the current renderer's injected dispatcher.
1203-
// Other apps might compile debug hooks as part of their app though.
1204-
if (currentDispatcher == null) {
1205-
currentDispatcher = ReactSharedInternals;
1206-
}
1207-
12081204
const previousDispatcher = currentDispatcher.H;
12091205
currentDispatcher.H = DispatcherProxy;
12101206

@@ -1229,6 +1225,31 @@ export function inspectHooks<Props>(
12291225
return buildTree(rootStack, readHookLog);
12301226
}
12311227

1228+
// DevTools will pass the current renderer's injected dispatcher. Other apps
1229+
// might compile debug hooks as part of their app though, so default to the
1230+
// running React's shared internals when no dispatcher is provided.
1231+
export function inspectHooks<Props>(
1232+
renderFunction: Props => React$Node,
1233+
props: Props,
1234+
currentDispatcher: ?CurrentDispatcherRef,
1235+
): HooksTree {
1236+
return inspectHooksImpl(
1237+
renderFunction,
1238+
props,
1239+
currentDispatcher ?? ReactSharedInternals,
1240+
);
1241+
}
1242+
1243+
// Like inspectHooks but requires an explicit dispatcher and never references
1244+
// ReactSharedInternals, so importing it does not pull React into the bundle.
1245+
export function inspectHooksWithoutDefaultDispatcher<Props>(
1246+
renderFunction: Props => React$Node,
1247+
props: Props,
1248+
currentDispatcher: CurrentDispatcherRef,
1249+
): HooksTree {
1250+
return inspectHooksImpl(renderFunction, props, currentDispatcher);
1251+
}
1252+
12321253
function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) {
12331254
let current: null | Fiber = fiber;
12341255
while (current) {
@@ -1295,16 +1316,13 @@ function resolveDefaultProps(Component: any, baseProps: any) {
12951316
return baseProps;
12961317
}
12971318

1298-
export function inspectHooksOfFiber(
1319+
// Shared implementation. Requires an explicit dispatcher and never references
1320+
// ReactSharedInternals (it delegates to inspectHooksImpl), so importing it does
1321+
// not pull React into the bundle.
1322+
function inspectHooksOfFiberImpl(
12991323
fiber: Fiber,
1300-
currentDispatcher: ?CurrentDispatcherRef,
1324+
currentDispatcher: CurrentDispatcherRef,
13011325
): HooksTree {
1302-
// DevTools will pass the current renderer's injected dispatcher.
1303-
// Other apps might compile debug hooks as part of their app though.
1304-
if (currentDispatcher == null) {
1305-
currentDispatcher = ReactSharedInternals;
1306-
}
1307-
13081326
if (
13091327
fiber.tag !== FunctionComponent &&
13101328
fiber.tag !== SimpleMemoComponent &&
@@ -1381,7 +1399,7 @@ export function inspectHooksOfFiber(
13811399
);
13821400
}
13831401

1384-
return inspectHooks(type, props, currentDispatcher);
1402+
return inspectHooksImpl(type, props, currentDispatcher);
13851403
} finally {
13861404
currentFiber = null;
13871405
currentHook = null;
@@ -1392,3 +1410,27 @@ export function inspectHooksOfFiber(
13921410
restoreContexts(contextMap);
13931411
}
13941412
}
1413+
1414+
// DevTools will pass the current renderer's injected dispatcher. Other apps
1415+
// might compile debug hooks as part of their app though, so default to the
1416+
// running React's shared internals when no dispatcher is provided.
1417+
export function inspectHooksOfFiber(
1418+
fiber: Fiber,
1419+
currentDispatcher: ?CurrentDispatcherRef,
1420+
): HooksTree {
1421+
return inspectHooksOfFiberImpl(
1422+
fiber,
1423+
currentDispatcher ?? ReactSharedInternals,
1424+
);
1425+
}
1426+
1427+
// Like inspectHooksOfFiber but requires an explicit dispatcher and never
1428+
// references ReactSharedInternals. Callers that always have the renderer's
1429+
// injected dispatcher (e.g. react-devtools-facade) can use this to avoid
1430+
// pulling React into their bundle.
1431+
export function inspectHooksOfFiberWithoutDefaultDispatcher(
1432+
fiber: Fiber,
1433+
currentDispatcher: CurrentDispatcherRef,
1434+
): HooksTree {
1435+
return inspectHooksOfFiberImpl(fiber, currentDispatcher);
1436+
}

packages/react-debug-tools/src/ReactDebugTools.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
* @flow
88
*/
99

10-
import {inspectHooks, inspectHooksOfFiber} from './ReactDebugHooks';
10+
import {
11+
inspectHooks,
12+
inspectHooksWithoutDefaultDispatcher,
13+
inspectHooksOfFiber,
14+
inspectHooksOfFiberWithoutDefaultDispatcher,
15+
} from './ReactDebugHooks';
1116

12-
export {inspectHooks, inspectHooksOfFiber};
17+
export {
18+
inspectHooks,
19+
inspectHooksWithoutDefaultDispatcher,
20+
inspectHooksOfFiber,
21+
inspectHooksOfFiberWithoutDefaultDispatcher,
22+
};

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,6 @@ import type {
171171
RendererInterface,
172172
SerializedElement,
173173
SerializedAsyncInfo,
174-
CurrentDispatcherRef,
175-
LegacyDispatcherRef,
176174
ProfilingSettings,
177175
} from '../types';
178176
import type {
@@ -194,6 +192,7 @@ import {
194192
VIRTUAL_INSTANCE,
195193
FILTERED_FIBER_INSTANCE,
196194
} from './shared/DevToolsFiberTypes';
195+
import {getDispatcherRef} from '../shared/DevToolsReactDispatcher';
197196
import {getSourceLocationByFiber} from './DevToolsFiberComponentStack';
198197
import {formatOwnerStack} from '../shared/DevToolsOwnerStack';
199198

@@ -275,31 +274,6 @@ function createSuspenseNode(
275274
});
276275
}
277276

278-
export function getDispatcherRef(renderer: {
279-
+currentDispatcherRef?: LegacyDispatcherRef | CurrentDispatcherRef,
280-
...
281-
}): void | CurrentDispatcherRef {
282-
if (renderer.currentDispatcherRef === undefined) {
283-
return undefined;
284-
}
285-
const injectedRef = renderer.currentDispatcherRef;
286-
if (
287-
typeof injectedRef.H === 'undefined' &&
288-
typeof injectedRef.current !== 'undefined'
289-
) {
290-
// We got a legacy dispatcher injected, let's create a wrapper proxy to translate.
291-
return {
292-
get H() {
293-
return (injectedRef as any).current;
294-
},
295-
set H(value) {
296-
(injectedRef as any).current = value;
297-
},
298-
};
299-
}
300-
return injectedRef as any;
301-
}
302-
303277
// All environment names we've seen so far. This lets us create a list of filters to apply.
304278
// This should ideally include env of filtered Components too so that you can add those as
305279
// filters at the same time as removing some other filter.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {CurrentDispatcherRef, LegacyDispatcherRef} from '../types';
11+
12+
export function getDispatcherRef(renderer: {
13+
+currentDispatcherRef?: LegacyDispatcherRef | CurrentDispatcherRef,
14+
...
15+
}): void | CurrentDispatcherRef {
16+
if (renderer.currentDispatcherRef === undefined) {
17+
return undefined;
18+
}
19+
const injectedRef = renderer.currentDispatcherRef;
20+
if (
21+
typeof injectedRef.H === 'undefined' &&
22+
typeof injectedRef.current !== 'undefined'
23+
) {
24+
// We got a legacy dispatcher injected, let's create a wrapper proxy to translate.
25+
return {
26+
get H() {
27+
return (injectedRef as any).current;
28+
},
29+
set H(value) {
30+
(injectedRef as any).current = value;
31+
},
32+
};
33+
}
34+
return injectedRef as any;
35+
}

0 commit comments

Comments
 (0)