Skip to content

Commit dc8580e

Browse files
author
Brian Vaughn
authored
New NPM package react-devtools-inline (#363)
1 parent 6f1e283 commit dc8580e

File tree

21 files changed

+440
-116
lines changed

21 files changed

+440
-116
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ shells/browser/firefox/build
55
shells/browser/shared/build
66
shells/dev/dist
77
packages/react-devtools-core/dist
8+
packages/react-devtools-inline/dist
89
vendor
910
*.js.snap
1011

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
/shells/browser/firefox/*.pem
55
/shells/browser/shared/build
66
/packages/react-devtools-core/dist
7+
/packages/react-devtools-inline/dist
78
/shells/dev/dist
89
build
910
/node_modules
10-
/packages/react-devtools-core/node_modules/
11-
/packages/react-devtools/node_modules/
11+
/packages/react-devtools-core/node_modules
12+
/packages/react-devtools-inline/node_modules
13+
/packages/react-devtools/node_modules
1214
npm-debug.log
1315
yarn-error.log
1416
.DS_Store

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
"scripts": {
3535
"build:core:backend": "cd ./packages/react-devtools-core && yarn build:backend",
3636
"build:core:standalone": "cd ./packages/react-devtools-core && yarn build:standalone",
37+
"build:core": "cd ./packages/react-devtools-core && yarn build",
38+
"build:inline": "cd ./packages/react-devtools-inline && yarn build",
3739
"build:demo": "cd ./shells/dev && cross-env NODE_ENV=development cross-env TARGET=remote webpack --config webpack.config.js",
3840
"build:extension": "cross-env NODE_ENV=production yarn run build:extension:chrome && yarn run build:extension:firefox",
3941
"build:extension:dev": "cross-env NODE_ENV=development yarn run build:extension:chrome && yarn run build:extension:firefox",

packages/react-devtools-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-devtools-core",
3-
"version": "4.0.0-alpha.6",
3+
"version": "4.0.0-alpha.7",
44
"description": "Use react-devtools outside of the browser",
55
"license": "MIT",
66
"main": "./dist/backend.js",
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# `react-devtools-inline`
2+
3+
React DevTools implementation for embedding within a browser-based IDE (e.g. [CodeSandbox](https://codesandbox.io/), [StackBlitz](https://stackblitz.com/)).
4+
5+
This is a low-level package. If you're looking for the standalone DevTools app, **use the `react-devtools` package instead.**
6+
7+
## Usage
8+
9+
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>).
10+
11+
The frontend and backend can be initialized in any order, but **the backend must not be activated until after the frontend has been initialized**. Because of this, the simplest sequence is:
12+
13+
1. Frontend (DevTools interface) initialized in the main `window`.
14+
1. Backend initialized in an `iframe`.
15+
1. Backend activated.
16+
17+
<sup>1</sup> Sandboxed iframes are supported.
18+
19+
## API
20+
21+
### `react-devtools-inline/backend`
22+
23+
* **`initialize(contentWindow)`** -
24+
Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.** (This means before any `import` or `require` statements!)
25+
* **`activate(contentWindow)`** -
26+
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.
27+
28+
```js
29+
import { activate, initialize } from 'react-devtools-inline/backend';
30+
31+
// Call this before importing React (or any other packages that might import React).
32+
initialize();
33+
34+
// Call this only once the frontend has been initialized.
35+
activate();
36+
```
37+
38+
### `react-devtools-inline/frontend`
39+
40+
* **`initialize(contentWindow)`** -
41+
Configures the DevTools interface to listen to the `window` the backend was injected into. This method returns a React component that can be rendered directly.
42+
43+
```js
44+
import { initialize } from 'react-devtools-inline/frontend';
45+
46+
// This should be the iframe the backend hook has been installed in.
47+
const iframe = document.getElementById(frameID);
48+
const contentWindow = iframe.contentWindow;
49+
50+
// This returns a React component that can be rendered into your app.
51+
// <DevTools {...props} />
52+
const DevTools = initialize(contentWindow);
53+
```
54+
55+
## Examples
56+
57+
### Configuring a same-origin `iframe`
58+
59+
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.
60+
61+
```js
62+
import {
63+
activate as activateBackend,
64+
initialize as initializeBackend
65+
} from 'react-devtools-inline/backend';
66+
import { initialize as initializeFrontend } from 'react-devtools-inline/frontend';
67+
68+
// The React app you want to inspect with DevTools is running within this iframe:
69+
const iframe = document.getElementById('target');
70+
const { contentWindow } = iframe;
71+
72+
// Installs the global hook into the iframe.
73+
// This be called before React is loaded into that frame.
74+
initializeBackend(contentWindow);
75+
76+
// React application can be injected into <iframe> at any time now...
77+
78+
// Initialize DevTools UI to listen to the hook we just installed.
79+
// This returns a React component we can render anywhere in the parent window.
80+
const DevTools = initializeFrontend(contentWindow);
81+
82+
// <DevTools /> interface can be rendered in the parent window at any time now...
83+
84+
// Let the backend know the frontend is ready and listening.
85+
activateBackend(contentWindow);
86+
```
87+
88+
### Configuring a sandboxed `iframe`
89+
90+
Sandboxed `iframe`s are also supported but require more complex initialization.
91+
92+
**`iframe.html`**
93+
```js
94+
import { activate, initialize } from 'react-devtools-inline/backend';
95+
96+
// The DevTooks hook needs to be installed before React is even required!
97+
// The safest way to do this is probably to install it in a separate script tag.
98+
initialize(window);
99+
100+
// Wait for the frontend to let us know that it's ready.
101+
window.addEventListener('message', ({ data }) => {
102+
switch (data.type) {
103+
case 'activate':
104+
activate(window);
105+
break;
106+
default:
107+
break;
108+
}
109+
});
110+
```
111+
112+
**`main-window.html`**
113+
```js
114+
import { initialize } from 'react-devtools-inline/frontend';
115+
116+
const iframe = document.getElementById('target');
117+
const { contentWindow } = iframe;
118+
119+
// Initialize DevTools UI to listen to the iframe.
120+
// This returns a React component we can render anywhere in the main window.
121+
const DevTools = initialize(contentWindow);
122+
123+
// Let the backend know to initialize itself.
124+
// We can't do this directly because the iframe is sandboxed.
125+
// Only initialize the backend once the DevTools frontend has been initialized.
126+
iframe.onload = () => {
127+
contentWindow.postMessage(
128+
{
129+
type: 'activate',
130+
},
131+
'*'
132+
);
133+
};
134+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./dist/backend');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./dist/frontend');
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "react-devtools-inline",
3+
"version": "4.0.0-alpha.7",
4+
"description": "Embed react-devtools within a website",
5+
"license": "MIT",
6+
"main": "./dist/backend.js",
7+
"repository": {
8+
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
9+
"type": "git"
10+
},
11+
"files": [
12+
"dist",
13+
"backend.js",
14+
"frontend.js"
15+
],
16+
"scripts": {
17+
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
18+
"prepublish": "yarn run build",
19+
"start": "cross-env NODE_ENV=development webpack --config webpack.config.js --watch"
20+
},
21+
"dependencies": {},
22+
"devDependencies": {
23+
"cross-env": "^3.1.4"
24+
}
25+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/** @flow */
2+
3+
import Agent from 'src/backend/agent';
4+
import Bridge from 'src/bridge';
5+
import { initBackend } from 'src/backend';
6+
import { installHook } from 'src/hook';
7+
import setupNativeStyleEditor from 'src/backend/NativeStyleEditor/setupNativeStyleEditor';
8+
import {
9+
MESSAGE_TYPE_GET_SAVED_PREFERENCES,
10+
MESSAGE_TYPE_SAVED_PREFERENCES,
11+
} from './constants';
12+
13+
function startActivation(contentWindow: window) {
14+
const { parent } = contentWindow;
15+
16+
const onMessage = ({ data }) => {
17+
switch (data.type) {
18+
case MESSAGE_TYPE_SAVED_PREFERENCES:
19+
// This is the only message we're listening for,
20+
// so it's safe to cleanup after we've received it.
21+
contentWindow.removeEventListener('message', onMessage);
22+
23+
const { appendComponentStack, componentFilters } = data;
24+
25+
contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
26+
contentWindow.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
27+
28+
// TRICKY
29+
// The backend entry point may be required in the context of an iframe or the parent window.
30+
// If it's required within the parent window, store the saved values on it as well,
31+
// since the injected renderer interface will read from window.
32+
// Technically we don't need to store them on the contentWindow in this case,
33+
// but it doesn't really hurt anything to store them there too.
34+
if (contentWindow !== window) {
35+
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
36+
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
37+
}
38+
39+
finishActivation(contentWindow);
40+
break;
41+
default:
42+
break;
43+
}
44+
};
45+
46+
contentWindow.addEventListener('message', onMessage);
47+
48+
// The backend may be unable to read saved preferences directly,
49+
// because they are stored in localStorage within the context of the extension (on the frontend).
50+
// Instead it relies on the extension to pass preferences through.
51+
// Because we might be in a sandboxed iframe, we have to ask for them by way of postMessage().
52+
parent.postMessage({ type: MESSAGE_TYPE_GET_SAVED_PREFERENCES }, '*');
53+
}
54+
55+
function finishActivation(contentWindow: window) {
56+
const { parent } = contentWindow;
57+
58+
const bridge = new Bridge({
59+
listen(fn) {
60+
const onMessage = event => {
61+
fn(event.data);
62+
};
63+
contentWindow.addEventListener('message', onMessage);
64+
return () => {
65+
contentWindow.removeEventListener('message', onMessage);
66+
};
67+
},
68+
send(event: string, payload: any, transferable?: Array<any>) {
69+
parent.postMessage({ event, payload }, '*', transferable);
70+
},
71+
});
72+
73+
const agent = new Agent(bridge);
74+
75+
const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__;
76+
77+
initBackend(hook, agent, contentWindow);
78+
79+
// Setup React Native style editor if a renderer like react-native-web has injected it.
80+
if (!!hook.resolveRNStyle) {
81+
setupNativeStyleEditor(
82+
bridge,
83+
agent,
84+
hook.resolveRNStyle,
85+
hook.nativeStyleEditorValidAttributes
86+
);
87+
}
88+
}
89+
90+
export function activate(contentWindow: window): void {
91+
startActivation(contentWindow);
92+
}
93+
94+
export function initialize(contentWindow: window): void {
95+
installHook(contentWindow);
96+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @flow */
2+
3+
export const MESSAGE_TYPE_GET_SAVED_PREFERENCES =
4+
'React::DevTools::getSavedPreferences';
5+
export const MESSAGE_TYPE_SAVED_PREFERENCES =
6+
'React::DevTools::savedPreferences';

0 commit comments

Comments
 (0)