Allow using global devTools
Summary:
Currently Flipper embeds the React devTools, and as a result the version of the React devTools is strictly coupled to the Flipper version. This is troublesome when connecting to (slightly) older React Native versions, that use a different version of the tools. That results in errors like this one:
{F615263497}
This diff introduces a feature to use globally installed devTools instead of the embedded ones, giving users the flexibility to pick their own version.
{F615263580}
This addresses
https://fb.workplace.com/groups/flippersupport/permalink/1125669971246993/
https://github.com/facebook/flipper/issues/2250
https://github.com/facebook/flipper/issues/2224
Changelog: [React DevTools] It is now possible to switch between the embedded and globally installed version of the React DevTools. This will enable the React DevTools to connect to older RN versions. Fixes #2250, #2224
Reviewed By: passy
Differential Revision: D28382586
fbshipit-source-id: a5386a5043933acda5aab2db74078bf7ceb105ca
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4062950fbe
commit
ab17bbd555
@@ -16,10 +16,14 @@ import {
|
|||||||
useValue,
|
useValue,
|
||||||
theme,
|
theme,
|
||||||
sleep,
|
sleep,
|
||||||
|
Toolbar,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import React, {createRef, useEffect} from 'react';
|
import React, {createRef, useEffect} from 'react';
|
||||||
import getPort from 'get-port';
|
import getPort from 'get-port';
|
||||||
import {Alert, Button} from 'antd';
|
import {Alert, Button, Switch} from 'antd';
|
||||||
|
import child_process from 'child_process';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
const DEV_TOOLS_NODE_ID = 'reactdevtools-out-of-react-node';
|
const DEV_TOOLS_NODE_ID = 'reactdevtools-out-of-react-node';
|
||||||
|
|
||||||
@@ -28,6 +32,29 @@ interface MetroDevice {
|
|||||||
sendCommand(command: string, params?: any): void;
|
sendCommand(command: string, params?: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findGlobalDevTools(): Promise<string | undefined> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
child_process.exec('npm root -g', (error, basePath) => {
|
||||||
|
if (error) {
|
||||||
|
console.warn(
|
||||||
|
'Failed to find globally installed React DevTools: ' + error,
|
||||||
|
);
|
||||||
|
resolve(undefined);
|
||||||
|
} else {
|
||||||
|
const devToolsPath = path.join(
|
||||||
|
basePath.trim(),
|
||||||
|
'react-devtools',
|
||||||
|
'node_modules',
|
||||||
|
'react-devtools-core',
|
||||||
|
);
|
||||||
|
fs.stat(devToolsPath, (err, stats) => {
|
||||||
|
resolve(!err && stats ? devToolsPath : undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function createDevToolsNode(): HTMLElement {
|
function createDevToolsNode(): HTMLElement {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.id = DEV_TOOLS_NODE_ID;
|
div.id = DEV_TOOLS_NODE_ID;
|
||||||
@@ -77,17 +104,42 @@ export function devicePlugin(client: DevicePluginClient) {
|
|||||||
const connectionStatus = createState<ConnectionStatus>(
|
const connectionStatus = createState<ConnectionStatus>(
|
||||||
ConnectionStatus.Initializing,
|
ConnectionStatus.Initializing,
|
||||||
);
|
);
|
||||||
|
const globalDevToolsPath = createState<string>();
|
||||||
|
const useGlobalDevTools = createState(false); // TODO: store in local storage T69989583
|
||||||
|
let devToolsInstance: typeof ReactDevToolsStandalone =
|
||||||
|
ReactDevToolsStandalone;
|
||||||
|
let startResult: {close(): void} | undefined = undefined;
|
||||||
|
|
||||||
const containerRef = createRef<HTMLDivElement>();
|
const containerRef = createRef<HTMLDivElement>();
|
||||||
let pollHandle: NodeJS.Timeout | undefined = undefined;
|
let pollHandle: NodeJS.Timeout | undefined = undefined;
|
||||||
let isMounted = false;
|
let isMounted = false;
|
||||||
|
|
||||||
|
async function toggleUseGlobalDevTools() {
|
||||||
|
if (!globalDevToolsPath.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
useGlobalDevTools.update((v) => !v);
|
||||||
|
if (useGlobalDevTools.get()) {
|
||||||
|
console.log('Loading ' + globalDevToolsPath.get());
|
||||||
|
devToolsInstance = global.electronRequire(
|
||||||
|
globalDevToolsPath.get()!,
|
||||||
|
).default;
|
||||||
|
} else {
|
||||||
|
devToolsInstance = ReactDevToolsStandalone;
|
||||||
|
}
|
||||||
|
startResult?.close();
|
||||||
|
stopDevtools();
|
||||||
|
findDevToolsNode()!.remove();
|
||||||
|
await bootDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
async function bootDevTools() {
|
async function bootDevTools() {
|
||||||
isMounted = true;
|
isMounted = true;
|
||||||
let devToolsNode = findDevToolsNode();
|
let devToolsNode = findDevToolsNode();
|
||||||
if (!devToolsNode) {
|
if (!devToolsNode) {
|
||||||
devToolsNode = createDevToolsNode();
|
devToolsNode = createDevToolsNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
attachDevTools(containerRef.current!, devToolsNode);
|
attachDevTools(containerRef.current!, devToolsNode);
|
||||||
initializeDevTools(devToolsNode);
|
initializeDevTools(devToolsNode);
|
||||||
setStatus(
|
setStatus(
|
||||||
@@ -169,11 +221,12 @@ export function devicePlugin(client: DevicePluginClient) {
|
|||||||
);
|
);
|
||||||
// Currently a new port is negotatiated every time the plugin is opened.
|
// Currently a new port is negotatiated every time the plugin is opened.
|
||||||
// This can be potentially optimized by keeping the devTools instance around
|
// This can be potentially optimized by keeping the devTools instance around
|
||||||
ReactDevToolsStandalone.setContentDOMNode(devToolsNode)
|
startResult = devToolsInstance
|
||||||
|
.setContentDOMNode(devToolsNode)
|
||||||
.setStatusListener((status) => {
|
.setStatusListener((status) => {
|
||||||
setStatus(ConnectionStatus.Initializing, status);
|
setStatus(ConnectionStatus.Initializing, status);
|
||||||
})
|
})
|
||||||
.startServer(port);
|
.startServer(port) as any;
|
||||||
setStatus(ConnectionStatus.Initializing, 'Waiting for device');
|
setStatus(ConnectionStatus.Initializing, 'Waiting for device');
|
||||||
|
|
||||||
// This is a hack that should be cleaned up. Instead of setting up port forwarding
|
// This is a hack that should be cleaned up. Instead of setting up port forwarding
|
||||||
@@ -211,6 +264,16 @@ export function devicePlugin(client: DevicePluginClient) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.onReady(() => {
|
||||||
|
console.log('searching');
|
||||||
|
findGlobalDevTools().then((path) => {
|
||||||
|
globalDevToolsPath.set(path + '/standalone');
|
||||||
|
if (path) {
|
||||||
|
console.log('Found global React DevTools: ', path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
devtoolsHaveStarted,
|
devtoolsHaveStarted,
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
@@ -219,6 +282,9 @@ export function devicePlugin(client: DevicePluginClient) {
|
|||||||
metroDevice,
|
metroDevice,
|
||||||
containerRef,
|
containerRef,
|
||||||
stopDevtools,
|
stopDevtools,
|
||||||
|
globalDevToolsPath,
|
||||||
|
useGlobalDevTools,
|
||||||
|
toggleUseGlobalDevTools,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +292,8 @@ export function Component() {
|
|||||||
const instance = usePlugin(devicePlugin);
|
const instance = usePlugin(devicePlugin);
|
||||||
const connectionStatus = useValue(instance.connectionStatus);
|
const connectionStatus = useValue(instance.connectionStatus);
|
||||||
const statusMessage = useValue(instance.statusMessage);
|
const statusMessage = useValue(instance.statusMessage);
|
||||||
|
const globalDevToolsPath = useValue(instance.globalDevToolsPath);
|
||||||
|
const useGlobalDevTools = useValue(instance.useGlobalDevTools);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
instance.bootDevTools();
|
instance.bootDevTools();
|
||||||
@@ -234,6 +302,19 @@ export function Component() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Container grow>
|
<Layout.Container grow>
|
||||||
|
{globalDevToolsPath ? (
|
||||||
|
<Toolbar
|
||||||
|
right={
|
||||||
|
<>
|
||||||
|
<Switch
|
||||||
|
checked={useGlobalDevTools}
|
||||||
|
onChange={instance.toggleUseGlobalDevTools}
|
||||||
|
/>
|
||||||
|
Use globally installed DevTools
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{!instance.devtoolsHaveStarted() ? (
|
{!instance.devtoolsHaveStarted() ? (
|
||||||
<Layout.Container
|
<Layout.Container
|
||||||
style={{width: 400, margin: `${theme.space.large}px auto`}}>
|
style={{width: 400, margin: `${theme.space.large}px auto`}}>
|
||||||
|
|||||||
@@ -185,7 +185,11 @@ That is correct, the dependencies won't be actually included in the release (whe
|
|||||||
#### Q: Cannot inspect an element in the React DevTools: "Could not inspect element with id ..."
|
#### Q: Cannot inspect an element in the React DevTools: "Could not inspect element with id ..."
|
||||||
|
|
||||||
The "Could not inspect element with id XXX" error will appear when selecting a specific element in the React DevTools, when the version of the DevTools shipped in Flipper is incompatible with the `react-devtools-core` package used by the React Native application.
|
The "Could not inspect element with id XXX" error will appear when selecting a specific element in the React DevTools, when the version of the DevTools shipped in Flipper is incompatible with the `react-devtools-core` package used by the React Native application.
|
||||||
A way to fix this is to set the `resolutions` field in the `package.json` of the app to force a specific version and then run `yarn install`, for example:
|
|
||||||
|
Flipper supports using a globally installed `react-devtools` (after using `npm install -g react-devtools@x.x.x`) instead of the embedded one.
|
||||||
|
This should help with any compatibility issues.
|
||||||
|
|
||||||
|
Another way to fix this is to set the `resolutions` field in the `package.json` of the app to force a specific version and then run `yarn install`, for example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|||||||
Reference in New Issue
Block a user