@samitouri / QOS-React-1 / commits / a86afe8e56

feat: expose installHook with settings argument from react-devtools-core/backend (#30987)

Stacked on https://github.com/facebook/react/pull/30986. Previously, we would call `installHook` at a top level of the JavaScript module. Because of this, having `require` statement for `react-devtools-core` package was enough to initialize the React DevTools global hook on the `window`. Now, the Hook can actually receive an argument - initial user settings for console patching. We expose this as a function `initialize`, which can be used by third parties (including React Native) to provide the persisted settings. The README was also updated to reflect the changes.

Ruslan Lesiutin committed Sep 19, 2024 at 13:55 UTC a86afe8e560f452a9df5ceb4893d9423e5840800
2 files changed +62 -23
packages/react-devtools-core/README.md
+29 -18
@@ -14,35 +14,46 @@ If you are building a non-browser-based React renderer, you can use the backend
14
15 ```js
16 if (process.env.NODE_ENV !== 'production') {
17 - const { connectToDevTools } = require("react-devtools-core");
17 + const { initialize, connectToDevTools } = require("react-devtools-core");
18
19 + initialize(settings);
20 // Must be called before packages like react or react-native are imported
20 - connectToDevTools({
21 - ...config
22 - });
21 + connectToDevTools({...config});
22 }
23 ```
24
25 > **NOTE** that this API (`connectToDevTools`) must be (1) run in the same context as React and (2) must be called before React packages are imported (e.g. `react`, `react-dom`, `react-native`).
26
27 +### `initialize` arguments
28 +| Argument | Description |
29 +|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
30 +| `settings` | Optional. If not specified, or received as null, then default settings are used. Can be plain object or a Promise that resolves with the [plain settings object](#Settings). If Promise rejects, the console will not be patched and some console features from React DevTools will not work. |
31 +
32 +#### `Settings`
33 +| Spec | Default value |
34 +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
35 +| <pre>{<br> appendComponentStack: boolean,<br> breakOnConsoleErrors: boolean,<br> showInlineWarningsAndErrors: boolean,<br> hideConsoleLogsInStrictMode: boolean<br>}</pre> | <pre>{<br> appendComponentStack: true,<br> breakOnConsoleErrors: false,<br> showInlineWarningsAndErrors: true,<br> hideConsoleLogsInStrictMode: false<br>}</pre> |
36 +
37 ### `connectToDevTools` options
29 -| Prop | Default | Description |
30 -|---|---|---|
31 -| `host` | `"localhost"` | Socket connection to frontend should use this host. |
32 -| `isAppActive` | | (Optional) function that returns true/false, telling DevTools when it's ready to connect to React. |
33 -| `port` | `8097` | Socket connection to frontend should use this port. |
34 -| `resolveRNStyle` | | (Optional) function that accepts a key (number) and returns a style (object); used by React Native. |
35 -| `retryConnectionDelay` | `200` | Delay (ms) to wait between retrying a failed Websocket connection |
36 -| `useHttps` | `false` | Socket connection to frontend should use secure protocol (wss). |
37 -| `websocket` | | Custom `WebSocket` connection to frontend; overrides `host` and `port` settings. |
38 +| Prop | Default | Description |
39 +|------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------|
40 +| `host` | `"localhost"` | Socket connection to frontend should use this host. |
41 +| `isAppActive` | | (Optional) function that returns true/false, telling DevTools when it's ready to connect to React. |
42 +| `port` | `8097` | Socket connection to frontend should use this port. |
43 +| `resolveRNStyle` | | (Optional) function that accepts a key (number) and returns a style (object); used by React Native. |
44 +| `retryConnectionDelay` | `200` | Delay (ms) to wait between retrying a failed Websocket connection |
45 +| `useHttps` | `false` | Socket connection to frontend should use secure protocol (wss). |
46 +| `websocket` | | Custom `WebSocket` connection to frontend; overrides `host` and `port` settings. |
47 +| `onSettingsUpdated` | | A callback that will be called when the user updates the settings in the UI. You can use it for persisting user settings. | |
48
49
50 ### `connectWithCustomMessagingProtocol` options
41 -| Prop | Description |
42 -|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
43 -| `onSubscribe` | Function, which receives listener (function, with a single argument) as an argument. Called when backend subscribes to messages from the other end (frontend). |
44 -| `onUnsubscribe` | Function, which receives listener (function) as an argument. Called when backend unsubscribes to messages from the other end (frontend). |
45 -| `onMessage` | Function, which receives 2 arguments: event (string) and payload (any). Called when backend emits a message, which should be sent to the frontend. |
51 +| Prop | Description |
52 +|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
53 +| `onSubscribe` | Function, which receives listener (function, with a single argument) as an argument. Called when backend subscribes to messages from the other end (frontend). |
54 +| `onUnsubscribe` | Function, which receives listener (function) as an argument. Called when backend unsubscribes to messages from the other end (frontend). |
55 +| `onMessage` | Function, which receives 2 arguments: event (string) and payload (any). Called when backend emits a message, which should be sent to the frontend. |
56 +| `onSettingsUpdated` | A callback that will be called when the user updates the settings in the UI. You can use it for persisting user settings. |
57
58 Unlike `connectToDevTools`, `connectWithCustomMessagingProtocol` returns a callback, which can be used for unsubscribing the backend from the global DevTools hook.
59
packages/react-devtools-core/src/backend.js
+33 -5
@@ -20,7 +20,10 @@ import type {
20 ComponentFilter,
21 Wall,
22 } from 'react-devtools-shared/src/frontend/types';
23 -import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
23 +import type {
24 + DevToolsHook,
25 + DevToolsHookSettings,
26 +} from 'react-devtools-shared/src/backend/types';
27 import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
28
29 type ConnectOptions = {
@@ -32,12 +35,9 @@ type ConnectOptions = {
35 retryConnectionDelay?: number,
36 isAppActive?: () => boolean,
37 websocket?: ?WebSocket,
38 + onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
39 };
40
37 -installHook(window);
38 -
39 -const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
40 -
41 let savedComponentFilters: Array<ComponentFilter> =
42 getDefaultComponentFilters();
43
@@ -52,11 +52,21 @@ function debug(methodName: string, ...args: Array<mixed>) {
52 }
53 }
54
55 +export function initialize(
56 + maybeSettingsOrSettingsPromise?:
57 + | DevToolsHookSettings
58 + | Promise<DevToolsHookSettings>,
59 +) {
60 + installHook(window, maybeSettingsOrSettingsPromise);
61 +}
62 +
63 export function connectToDevTools(options: ?ConnectOptions) {
64 + const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
65 if (hook == null) {
66 // DevTools didn't get injected into this page (maybe b'c of the contentType).
67 return;
68 }
69 +
70 const {
71 host = 'localhost',
72 nativeStyleEditorValidAttributes,
@@ -66,6 +76,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
76 resolveRNStyle = (null: $FlowFixMe),
77 retryConnectionDelay = 2000,
78 isAppActive = () => true,
79 + onSettingsUpdated,
80 } = options || {};
81
82 const protocol = useHttps ? 'wss' : 'ws';
@@ -160,7 +171,14 @@ export function connectToDevTools(options: ?ConnectOptions) {
171 // TODO (npm-packages) Warn if "isBackendStorageAPISupported"
172 // $FlowFixMe[incompatible-call] found when upgrading Flow
173 const agent = new Agent(bridge);
174 + if (onSettingsUpdated != null) {
175 + agent.addListener('updateHookSettings', onSettingsUpdated);
176 + }
177 agent.addListener('shutdown', () => {
178 + if (onSettingsUpdated != null) {
179 + agent.removeListener('updateHookSettings', onSettingsUpdated);
180 + }
181 +
182 // If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
183 // and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
184 hook.emit('shutdown');
@@ -290,6 +308,7 @@ type ConnectWithCustomMessagingOptions = {
308 onMessage: (event: string, payload: any) => void,
309 nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
310 resolveRNStyle?: ResolveNativeStyle,
311 + onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
312 };
313
314 export function connectWithCustomMessagingProtocol({
@@ -298,7 +317,9 @@ export function connectWithCustomMessagingProtocol({
317 onMessage,
318 nativeStyleEditorValidAttributes,
319 resolveRNStyle,
320 + onSettingsUpdated,
321 }: ConnectWithCustomMessagingOptions): Function {
322 + const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
323 if (hook == null) {
324 // DevTools didn't get injected into this page (maybe b'c of the contentType).
325 return;
@@ -334,7 +355,14 @@ export function connectWithCustomMessagingProtocol({
355 }
356
357 const agent = new Agent(bridge);
358 + if (onSettingsUpdated != null) {
359 + agent.addListener('updateHookSettings', onSettingsUpdated);
360 + }
361 agent.addListener('shutdown', () => {
362 + if (onSettingsUpdated != null) {
363 + agent.removeListener('updateHookSettings', onSettingsUpdated);
364 + }
365 +
366 // If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
367 // and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
368 hook.emit('shutdown');