2019-08-15 10:06:16 -07:00
# `react-devtools-inline`
2022-03-16 08:37:10 -07:00
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 ).
2019-08-15 10:06:16 -07:00
2022-03-16 08:37:10 -07:00
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 ** .
2019-08-15 10:06:16 -07:00
2022-03-16 08:37:10 -07:00
---
2022-07-30 16:57:13 +08:00
> **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`.
2022-03-16 08:37:10 -07:00
---
# Usage
2019-08-15 10:06:16 -07:00
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>).
2020-09-28 16:25:18 +03:00
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:
2019-08-15 10:06:16 -07:00
1. Frontend (DevTools interface) initialized in the main `window` .
1. Backend initialized in an `iframe` .
1. Backend activated.
<sup>1</sup> Sandboxed iframes are supported.
2022-03-16 08:37:10 -07:00
# Backend APIs
### `initialize(windowOrGlobal)`
Installs the global hook on the window/global object. This hook is how React and DevTools communicate.
2019-08-15 10:06:16 -07:00
2022-03-16 08:37:10 -07:00
> **This method must be called before React is loaded.** (This includes `import`/`require` statements and `<script>` tags that include React.)
### `activate(windowOrGlobal)`
2019-08-15 10:06:16 -07:00
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.
2022-03-16 08:37:10 -07:00
### Example
2019-08-15 10:06:16 -07:00
``` js
import { activate , initialize } from 'react-devtools-inline/backend' ;
2019-08-27 15:20:34 -07:00
// This should be the iframe the React application is running in.
const iframe = document . getElementById ( frameID ) ;
const contentWindow = iframe . contentWindow ;
2019-08-15 10:06:16 -07:00
// Call this before importing React (or any other packages that might import React).
2019-08-27 15:20:34 -07:00
initialize ( contentWindow ) ;
// Initialize the frontend...
2019-08-15 10:06:16 -07:00
// Call this only once the frontend has been initialized.
2019-08-27 15:20:34 -07:00
activate ( contentWindow ) ;
2019-08-15 10:06:16 -07:00
```
2022-03-16 08:37:10 -07:00
# Frontend APIs
2019-08-27 15:20:34 -07:00
2022-03-16 08:37:10 -07:00
### `initialize(windowOrGlobal)`
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.
2019-08-15 10:06:16 -07:00
2022-03-16 08:37:10 -07:00
> 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`.**
2019-08-15 10:06:16 -07:00
2022-03-16 08:37:10 -07:00
### Example
2019-08-15 10:06:16 -07:00
``` js
import { initialize } from 'react-devtools-inline/frontend' ;
// This should be the iframe the backend hook has been installed in.
const iframe = document . getElementById ( frameID ) ;
const contentWindow = iframe . contentWindow ;
// This returns a React component that can be rendered into your app.
2021-12-14 12:16:16 -05:00
// e.g. render(<DevTools {...props} />);
2019-08-15 10:06:16 -07:00
const DevTools = initialize ( contentWindow ) ;
```
2022-03-16 08:37:10 -07:00
# Advanced examples
2019-08-15 10:06:16 -07:00
2021-09-09 15:25:26 -04:00
### Supporting named hooks
2021-11-15 16:59:35 +01:00
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.
2021-09-09 15:25:26 -04:00
To configure this package to support this functionality, you'll need to provide a prop that dynamically imports the extra functionality:
``` js
// Follow code examples above to configure the backend and frontend.
// When rendering DevTools, the important part is to pass a 'hookNamesModuleLoaderFunction' prop.
const hookNamesModuleLoaderFunction = ( ) => import ( 'react-devtools-inline/hookNames' ) ;
// Render:
< DevTools
hookNamesModuleLoaderFunction = { hookNamesModuleLoaderFunction }
{ ... otherProps }
/ > ;
```
2019-08-15 10:06:16 -07:00
### Configuring a same-origin `iframe`
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.
``` js
import {
activate as activateBackend ,
initialize as initializeBackend
} from 'react-devtools-inline/backend' ;
import { initialize as initializeFrontend } from 'react-devtools-inline/frontend' ;
// The React app you want to inspect with DevTools is running within this iframe:
const iframe = document . getElementById ( 'target' ) ;
const { contentWindow } = iframe ;
// Installs the global hook into the iframe.
// This must be called before React is loaded into that frame.
initializeBackend ( contentWindow ) ;
// Initialize DevTools UI to listen to the hook we just installed.
// This returns a React component we can render anywhere in the parent window.
2021-08-25 15:35:38 -07:00
// This also must be called before React is loaded into the iframe
2019-08-15 10:06:16 -07:00
const DevTools = initializeFrontend ( contentWindow ) ;
2021-08-25 15:35:38 -07:00
// React application can be injected into <iframe> at any time now...
// Note that this would need to be done via <script> tag injection,
// as setting the src of the <iframe> would load a new page (without the injected backend).
2019-08-15 10:06:16 -07:00
// <DevTools /> interface can be rendered in the parent window at any time now...
2022-03-01 00:13:28 -05:00
// Be sure to use ReactDOMClient.createRoot() to render this component.
2019-08-15 10:06:16 -07:00
// Let the backend know the frontend is ready and listening.
activateBackend ( contentWindow ) ;
```
### Configuring a sandboxed `iframe`
Sandboxed `iframe` s are also supported but require more complex initialization.
* * `iframe.html` **
``` js
import { activate , initialize } from "react-devtools-inline/backend" ;
2020-06-15 19:59:44 -04:00
// The DevTools hook needs to be installed before React is even required!
2019-08-15 10:06:16 -07:00
// The safest way to do this is probably to install it in a separate script tag.
initialize ( window ) ;
// Wait for the frontend to let us know that it's ready.
function onMessage ( { data } ) {
switch ( data . type ) {
case "activate-backend" :
window . removeEventListener ( "message" , onMessage ) ;
activate ( window ) ;
break ;
default :
break ;
}
}
window . addEventListener ( "message" , onMessage ) ;
```
* * `main-window.html` **
``` js
import { initialize } from "react-devtools-inline/frontend" ;
const iframe = document . getElementById ( "target" ) ;
const { contentWindow } = iframe ;
// Initialize DevTools UI to listen to the iframe.
// This returns a React component we can render anywhere in the main window.
2022-03-01 00:13:28 -05:00
// Be sure to use ReactDOMClient.createRoot() to render this component.
2019-08-15 10:06:16 -07:00
const DevTools = initialize ( contentWindow ) ;
// Let the backend know to initialize itself.
// We can't do this directly because the iframe is sandboxed.
// Only initialize the backend once the DevTools frontend has been initialized.
iframe . onload = ( ) => {
contentWindow . postMessage (
{
type : "activate-backend"
} ,
"*"
) ;
} ;
2019-08-27 15:20:34 -07:00
```
2022-03-16 08:37:10 -07:00
### Advanced: Custom "wall"
2021-04-12 17:07:14 -04:00
2021-12-15 10:44:14 -05:00
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).
2021-04-12 17:07:14 -04:00
``` js
import {
2021-12-14 12:16:16 -05:00
activate as activateBackend ,
createBridge as createBackendBridge ,
initialize as initializeBackend ,
} from 'react-devtools-inline/backend' ;
import {
createBridge as createFrontendBridge ,
2021-04-12 17:07:14 -04:00
createStore ,
initialize as createDevTools ,
2021-12-14 12:16:16 -05:00
} from 'react-devtools-inline/frontend' ;
2021-04-12 17:07:14 -04:00
2021-12-14 12:16:16 -05:00
// DevTools uses "message" events and window.postMessage() by default,
// but we can override this behavior by creating a custom "Wall" object.
2021-04-12 17:07:14 -04:00
// For example...
const wall = {
2021-12-14 12:16:16 -05:00
_listeners : [ ] ,
2021-04-12 17:07:14 -04:00
listen ( listener ) {
2021-12-14 12:16:16 -05:00
wall . _listeners . push ( listener ) ;
2021-04-12 17:07:14 -04:00
} ,
2021-12-14 12:16:16 -05:00
send ( event , payload ) {
wall . _listeners . forEach ( listener => listener ( { event , payload } ) ) ;
2021-04-12 17:07:14 -04:00
} ,
} ;
2021-12-14 12:16:16 -05:00
// Initialize the DevTools backend before importing React (or any other packages that might import React).
initializeBackend ( contentWindow ) ;
// Prepare DevTools for rendering.
// To use the custom Wall we've created, we need to also create our own "Bridge" and "Store" objects.
2021-12-15 10:44:14 -05:00
const bridge = createFrontendBridge ( contentWindow , wall ) ;
2021-04-12 17:07:14 -04:00
const store = createStore ( bridge ) ;
2021-12-15 10:44:14 -05:00
const DevTools = createDevTools ( contentWindow , { bridge , store } ) ;
2021-04-12 17:07:14 -04:00
2021-12-14 12:16:16 -05:00
// You can render DevTools now:
const root = createRoot ( container ) ;
root . render ( < DevTools { ... otherProps } / > ) ;
// Lastly, let the DevTools backend know that the frontend is ready.
// To use the custom Wall we've created, we need to also pass in the "Bridge".
activateBackend ( contentWindow , {
bridge : createBackendBridge ( contentWindow , wall ) ,
} ) ;
2021-04-12 17:07:14 -04:00
```
2021-12-15 10:44:14 -05:00
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:
``` js
const uid = "some-unique-string-shared-between-both-pieces" ;
const wall = {
listen ( listener ) {
window . addEventListener ( "message" , ( event ) => {
if ( event . data . uid === uid ) {
listener ( event . data ) ;
}
} ) ;
} ,
send ( event , payload ) {
window . postMessage ( { event , payload , uid } , "*" ) ;
} ,
} ;
```
2022-03-16 08:37:10 -07:00
### Advanced: Node + browser
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.
##### Sample Node backend
``` js
const {
activate ,
createBridge ,
initialize ,
} = require ( 'react-devtools-inline/backend' ) ;
const { createServer } = require ( 'http' ) ;
const SocketIO = require ( 'socket.io' ) ;
const server = createServer ( ) ;
const socket = SocketIO ( server , {
cors : {
origin : "*" ,
methods : [ "GET" , "POST" ] ,
allowedHeaders : [ ] ,
credentials : true
}
} ) ;
socket . on ( 'connection' , client => {
const wall = {
listen ( listener ) {
client . on ( 'message' , data => {
if ( data . uid === UID ) {
listener ( data ) ;
}
} ) ;
} ,
send ( event , payload ) {
const data = { event , payload , uid : UID } ;
client . emit ( 'message' , data ) ;
} ,
} ;
const bridge = createBridge ( global , wall ) ;
client . on ( 'disconnect' , ( ) => {
bridge . shutdown ( ) ;
} ) ;
activate ( global , { bridge } ) ;
} ) ;
socket . listen ( PORT ) ;
```
##### Sample Web frontend
``` js
import { createElement } from 'react' ;
import { createRoot } from 'react-dom/client' ;
import {
createBridge ,
createStore ,
initialize as createDevTools ,
} from 'react-devtools-inline/frontend' ;
import { io } from "socket.io-client" ;
let root = null ;
const socket = io ( ` http:// ${ HOST } : ${ PORT } ` ) ;
socket . on ( "connect" , ( ) => {
const wall = {
listen ( listener ) {
socket . on ( "message" , ( data ) => {
if ( data . uid === UID ) {
listener ( data ) ;
}
} ) ;
} ,
send ( event , payload ) {
const data = { event , payload , uid : UID } ;
socket . emit ( 'message' , data ) ;
} ,
} ;
const bridge = createBridge ( window , wall ) ;
const store = createStore ( bridge ) ;
const DevTools = createDevTools ( window , { bridge , store } ) ;
root = createRoot ( document . getElementById ( 'root' ) ) ;
root . render ( createElement ( DevTools ) ) ;
} ) ;
socket . on ( "disconnect" , ( ) => {
root . unmount ( ) ;
root = null ;
} ) ;
```
# Local development
2019-12-18 14:34:40 -08:00
You can also build and test this package from source.
2019-08-27 15:20:34 -07:00
2022-03-16 08:37:10 -07:00
## Prerequisite steps
2019-12-18 14:34:40 -08:00
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.
<sup>1</sup> Note that at this time, an _ experimental _ build is required because DevTools depends on the `createRoot` API.
2022-03-16 08:37:10 -07:00
### Build from source
2019-12-18 14:34:40 -08:00
To build dependencies from source, run the following command from the root of the repository:
``` sh
yarn build-for-devtools
```
2022-03-16 08:37:10 -07:00
### Download from CI
2022-03-24 10:04:12 -04:00
To use the latest build from CI, go to `scripts/release/` and run the following commands:
2019-12-18 14:34:40 -08:00
``` sh
2022-03-24 10:04:12 -04:00
yarn
./download-experimental-build.js --commit= main
2019-12-18 14:34:40 -08:00
```
2022-03-24 10:04:12 -04:00
2022-03-16 08:37:10 -07:00
## Build steps
2019-12-18 14:34:40 -08:00
Once the above packages have been built or downloaded, you can watch for changes made to the source code and automatically rebuild by running:
2019-08-27 15:20:34 -07:00
``` sh
yarn start
2019-12-18 14:34:40 -08:00
```
2021-06-29 14:26:24 -04:00
To test package changes, refer to the [`react-devtools-shell` README ](https://github.com/facebook/react/blob/main/packages/react-devtools-shell/README.md ).