1. # `react-devtools-inline`
    
  2. 
    
  3. This package can be used to embed React DevTools into browser-based tools like [CodeSandbox](https://codesandbox.io/), [StackBlitz](https://stackblitz.com/), and [Replay](https://replay.io).
    
  4. 
    
  5. If you're looking for the standalone React DevTools UI, **we suggest using [`react-devtools`](https://github.com/facebook/react/tree/main/packages/react-devtools) instead of using this package directly**.
    
  6. 
    
  7. ---
    
  8. 
    
  9. > **Note** that this package (and the DevTools UI) relies on several _experimental_ APIs that are **only available in the [experimental release channel](https://reactjs.org/docs/release-channels.html#experimental-channel)**. This means that you will need to install `react@experimental` and `react-dom@experimental`.
    
  10. 
    
  11. ---
    
  12. 
    
  13. # Usage
    
  14. 
    
  15. This package exports two entry points: a frontend (to be run in the main `window`) and a backend (to be installed and run within an `iframe`<sup>1</sup>).
    
  16. 
    
  17. The frontend and backend can be initialized in any order, but **the backend must not be activated until the frontend initialization has completed**. Because of this, the simplest sequence is:
    
  18. 
    
  19. 1. Frontend (DevTools interface) initialized in the main `window`.
    
  20. 1. Backend initialized in an `iframe`.
    
  21. 1. Backend activated.
    
  22. 
    
  23. <sup>1</sup> Sandboxed iframes are supported.
    
  24. 
    
  25. # Backend APIs
    
  26. ### `initialize(windowOrGlobal)`
    
  27. 
    
  28. Installs the global hook on the window/global object. This hook is how React and DevTools communicate.
    
  29. 
    
  30. > **This method must be called before React is loaded.** (This includes `import`/`require` statements and `<script>` tags that include React.)
    
  31. 
    
  32. ### `activate(windowOrGlobal)`
    
  33. 
    
  34. Lets the backend know when the frontend is ready. It should not be called until after the frontend has been initialized, else the frontend might miss important tree-initialization events.
    
  35. 
    
  36. ### Example
    
  37. ```js
    
  38. import { activate, initialize } from 'react-devtools-inline/backend';
    
  39. 
    
  40. // This should be the iframe the React application is running in.
    
  41. const iframe = document.getElementById(frameID);
    
  42. const contentWindow = iframe.contentWindow;
    
  43. 
    
  44. // Call this before importing React (or any other packages that might import React).
    
  45. initialize(contentWindow);
    
  46. 
    
  47. // Initialize the frontend...
    
  48. 
    
  49. // Call this only once the frontend has been initialized.
    
  50. activate(contentWindow);
    
  51. ```
    
  52. 
    
  53. # Frontend APIs
    
  54. 
    
  55. ### `initialize(windowOrGlobal)`
    
  56. Configures the DevTools interface to listen to the `window` (or `global` object) the backend was injected into. This method returns a React component that can be rendered directly.
    
  57. 
    
  58. > Because the DevTools interface makes use of several new React concurrent features (like Suspense) **it should be rendered using `ReactDOMClient.createRoot` instead of `ReactDOM.render`.**
    
  59. 
    
  60. ### Example
    
  61. ```js
    
  62. import { initialize } from 'react-devtools-inline/frontend';
    
  63. 
    
  64. // This should be the iframe the backend hook has been installed in.
    
  65. const iframe = document.getElementById(frameID);
    
  66. const contentWindow = iframe.contentWindow;
    
  67. 
    
  68. // This returns a React component that can be rendered into your app.
    
  69. // e.g. render(<DevTools {...props} />);
    
  70. const DevTools = initialize(contentWindow);
    
  71. ```
    
  72. 
    
  73. # Advanced examples
    
  74. 
    
  75. ### Supporting named hooks
    
  76. 
    
  77. DevTools can display hook "names" for an inspected component, although determining the "names" requires loading the source (and source-maps), parsing the code, and inferring the names based on which variables hook values get assigned to. Because the code for this is non-trivial, it's lazy-loaded only if the feature is enabled.
    
  78. 
    
  79. To configure this package to support this functionality, you'll need to provide a prop that dynamically imports the extra functionality:
    
  80. ```js
    
  81. // Follow code examples above to configure the backend and frontend.
    
  82. // When rendering DevTools, the important part is to pass a 'hookNamesModuleLoaderFunction' prop.
    
  83. const hookNamesModuleLoaderFunction = () => import('react-devtools-inline/hookNames');
    
  84. 
    
  85. // Render:
    
  86. <DevTools
    
  87.   hookNamesModuleLoaderFunction={hookNamesModuleLoaderFunction}
    
  88.   {...otherProps}
    
  89. />;
    
  90. ```
    
  91. 
    
  92. ### Configuring a same-origin `iframe`
    
  93. 
    
  94. The simplest way to use this package is to install the hook from the parent `window`. This is possible if the `iframe` is not sandboxed and there are no cross-origin restrictions.
    
  95. 
    
  96. ```js
    
  97. import {
    
  98.   activate as activateBackend,
    
  99.   initialize as initializeBackend
    
  100. } from 'react-devtools-inline/backend';
    
  101. import { initialize as initializeFrontend } from 'react-devtools-inline/frontend';
    
  102. 
    
  103. // The React app you want to inspect with DevTools is running within this iframe:
    
  104. const iframe = document.getElementById('target');
    
  105. const { contentWindow } = iframe;
    
  106. 
    
  107. // Installs the global hook into the iframe.
    
  108. // This must be called before React is loaded into that frame.
    
  109. initializeBackend(contentWindow);
    
  110. 
    
  111. // Initialize DevTools UI to listen to the hook we just installed.
    
  112. // This returns a React component we can render anywhere in the parent window.
    
  113. // This also must be called before React is loaded into the iframe
    
  114. const DevTools = initializeFrontend(contentWindow);
    
  115. 
    
  116. // React application can be injected into <iframe> at any time now...
    
  117. // Note that this would need to be done via <script> tag injection,
    
  118. // as setting the src of the <iframe> would load a new page (without the injected backend).
    
  119. 
    
  120. // <DevTools /> interface can be rendered in the parent window at any time now...
    
  121. // Be sure to use ReactDOMClient.createRoot() to render this component.
    
  122. 
    
  123. // Let the backend know the frontend is ready and listening.
    
  124. activateBackend(contentWindow);
    
  125. ```
    
  126. 
    
  127. ### Configuring a sandboxed `iframe`
    
  128. 
    
  129. Sandboxed `iframe`s are also supported but require more complex initialization.
    
  130. 
    
  131. **`iframe.html`**
    
  132. ```js
    
  133. import { activate, initialize } from "react-devtools-inline/backend";
    
  134. 
    
  135. // The DevTools hook needs to be installed before React is even required!
    
  136. // The safest way to do this is probably to install it in a separate script tag.
    
  137. initialize(window);
    
  138. 
    
  139. // Wait for the frontend to let us know that it's ready.
    
  140. function onMessage({ data }) {
    
  141.   switch (data.type) {
    
  142.     case "activate-backend":
    
  143.       window.removeEventListener("message", onMessage);
    
  144. 
    
  145.       activate(window);
    
  146.       break;
    
  147.     default:
    
  148.       break;
    
  149.   }
    
  150. }
    
  151. 
    
  152. window.addEventListener("message", onMessage);
    
  153. ```
    
  154. 
    
  155. **`main-window.html`**
    
  156. ```js
    
  157. import { initialize } from "react-devtools-inline/frontend";
    
  158. 
    
  159. const iframe = document.getElementById("target");
    
  160. const { contentWindow } = iframe;
    
  161. 
    
  162. // Initialize DevTools UI to listen to the iframe.
    
  163. // This returns a React component we can render anywhere in the main window.
    
  164. // Be sure to use ReactDOMClient.createRoot() to render this component.
    
  165. const DevTools = initialize(contentWindow);
    
  166. 
    
  167. // Let the backend know to initialize itself.
    
  168. // We can't do this directly because the iframe is sandboxed.
    
  169. // Only initialize the backend once the DevTools frontend has been initialized.
    
  170. iframe.onload = () => {
    
  171.   contentWindow.postMessage(
    
  172.     {
    
  173.       type: "activate-backend"
    
  174.     },
    
  175.     "*"
    
  176.   );
    
  177. };
    
  178. ```
    
  179. 
    
  180. ### Advanced: Custom "wall"
    
  181. 
    
  182. Below is an example of an advanced integration with a website like [Replay.io](https://replay.io/) or Code Sandbox's Sandpack (where more than one DevTools instance may be rendered per page).
    
  183. 
    
  184. ```js
    
  185. import {
    
  186.   activate as activateBackend,
    
  187.   createBridge as createBackendBridge,
    
  188.   initialize as initializeBackend,
    
  189. } from 'react-devtools-inline/backend';
    
  190. import {
    
  191.   createBridge as createFrontendBridge,
    
  192.   createStore,
    
  193.   initialize as createDevTools,
    
  194. } from 'react-devtools-inline/frontend';
    
  195. 
    
  196. // DevTools uses "message" events and window.postMessage() by default,
    
  197. // but we can override this behavior by creating a custom "Wall" object.
    
  198. // For example...
    
  199. const wall = {
    
  200.   _listeners: [],
    
  201.   listen(listener) {
    
  202.     wall._listeners.push(listener);
    
  203.   },
    
  204.   send(event, payload) {
    
  205.     wall._listeners.forEach(listener => listener({event, payload}));
    
  206.   },
    
  207. };
    
  208. 
    
  209. // Initialize the DevTools backend before importing React (or any other packages that might import React).
    
  210. initializeBackend(contentWindow);
    
  211. 
    
  212. // Prepare DevTools for rendering.
    
  213. // To use the custom Wall we've created, we need to also create our own "Bridge" and "Store" objects.
    
  214. const bridge = createFrontendBridge(contentWindow, wall);
    
  215. const store = createStore(bridge);
    
  216. const DevTools = createDevTools(contentWindow, { bridge, store });
    
  217. 
    
  218. // You can render DevTools now:
    
  219. const root = createRoot(container);
    
  220. root.render(<DevTools {...otherProps} />);
    
  221. 
    
  222. // Lastly, let the DevTools backend know that the frontend is ready.
    
  223. // To use the custom Wall we've created, we need to also pass in the "Bridge".
    
  224. activateBackend(contentWindow, {
    
  225.   bridge: createBackendBridge(contentWindow, wall),
    
  226. });
    
  227. ```
    
  228. 
    
  229. Alternately, if your code can't share the same `wall` object, you can still provide a custom Wall that connects a specific DevTools frontend to a specific backend like so:
    
  230. ```js
    
  231. const uid = "some-unique-string-shared-between-both-pieces";
    
  232. const wall = {
    
  233.   listen(listener) {
    
  234.     window.addEventListener("message", (event) => {
    
  235.       if (event.data.uid === uid) {
    
  236.         listener(event.data);
    
  237.       }
    
  238.     });
    
  239.   },
    
  240.   send(event, payload) {
    
  241.     window.postMessage({ event, payload, uid }, "*");
    
  242.   },
    
  243. };
    
  244. ```
    
  245. 
    
  246. ### Advanced: Node + browser
    
  247. 
    
  248. Below is an example of an advanced integration that could be used to connect React running in a Node process to React DevTools running in a browser.
    
  249. 
    
  250. ##### Sample Node backend
    
  251. ```js
    
  252. const {
    
  253.   activate,
    
  254.   createBridge,
    
  255.   initialize,
    
  256. } = require('react-devtools-inline/backend');
    
  257. const { createServer } = require('http');
    
  258. const SocketIO = require('socket.io');
    
  259. 
    
  260. const server = createServer();
    
  261. const socket = SocketIO(server, {
    
  262.   cors: {
    
  263.     origin: "*",
    
  264.     methods: ["GET", "POST"],
    
  265.     allowedHeaders: [],
    
  266.     credentials: true
    
  267.   }
    
  268. });
    
  269. socket.on('connection', client => {
    
  270.   const wall = {
    
  271.     listen(listener) {
    
  272.       client.on('message', data => {
    
  273.         if (data.uid === UID) {
    
  274.           listener(data);
    
  275.         }
    
  276.       });
    
  277.     },
    
  278.     send(event, payload) {
    
  279.       const data = {event, payload, uid: UID};
    
  280.       client.emit('message', data);
    
  281.     },
    
  282.   };
    
  283. 
    
  284.   const bridge = createBridge(global, wall);
    
  285. 
    
  286.   client.on('disconnect', () => {
    
  287.     bridge.shutdown();
    
  288.   });
    
  289. 
    
  290.   activate(global, { bridge });
    
  291. });
    
  292. socket.listen(PORT);
    
  293. ```
    
  294. 
    
  295. ##### Sample Web frontend
    
  296. ```js
    
  297. import { createElement } from 'react';
    
  298. import { createRoot } from 'react-dom/client';
    
  299. import {
    
  300.   createBridge,
    
  301.   createStore,
    
  302.   initialize as createDevTools,
    
  303. } from 'react-devtools-inline/frontend';
    
  304. import { io } from "socket.io-client";
    
  305. 
    
  306. let root = null;
    
  307. 
    
  308. const socket = io(`http://${HOST}:${PORT}`);
    
  309. socket.on("connect", () => {
    
  310.   const wall = {
    
  311.     listen(listener) {
    
  312.       socket.on("message", (data) => {
    
  313.         if (data.uid === UID) {
    
  314.           listener(data);
    
  315.         }
    
  316.       });
    
  317.     },
    
  318.     send(event, payload) {
    
  319.       const data = { event, payload, uid: UID };
    
  320.       socket.emit('message', data);
    
  321.     },
    
  322.   };
    
  323. 
    
  324.   const bridge = createBridge(window, wall);
    
  325.   const store = createStore(bridge);
    
  326.   const DevTools = createDevTools(window, { bridge, store });
    
  327. 
    
  328.   root = createRoot(document.getElementById('root'));
    
  329.   root.render(createElement(DevTools));
    
  330. });
    
  331. socket.on("disconnect", () => {
    
  332.   root.unmount();
    
  333.   root = null;
    
  334. });
    
  335. ```
    
  336. 
    
  337. # Local development
    
  338. You can also build and test this package from source.
    
  339. 
    
  340. ## Prerequisite steps
    
  341. DevTools depends on local versions of several NPM packages<sup>1</sup> also in this workspace. You'll need to either build or download those packages first.
    
  342. 
    
  343. <sup>1</sup> Note that at this time, an _experimental_ build is required because DevTools depends on the `createRoot` API.
    
  344. 
    
  345. ### Build from source
    
  346. To build dependencies from source, run the following command from the root of the repository:
    
  347. ```sh
    
  348. yarn build-for-devtools
    
  349. ```
    
  350. ### Download from CI
    
  351. To use the latest build from CI, go to `scripts/release/` and run the following commands:
    
  352. ```sh
    
  353. yarn
    
  354. ./download-experimental-build.js --commit=main
    
  355. ```
    
  356. 
    
  357. ## Build steps
    
  358. Once the above packages have been built or downloaded, you can watch for changes made to the source code and automatically rebuild by running:
    
  359. ```sh
    
  360. yarn start
    
  361. ```
    
  362. 
    
  363. To test package changes, refer to the [`react-devtools-shell` README](https://github.com/facebook/react/blob/main/packages/react-devtools-shell/README.md).