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,
|
||||
theme,
|
||||
sleep,
|
||||
Toolbar,
|
||||
} from 'flipper-plugin';
|
||||
import React, {createRef, useEffect} from 'react';
|
||||
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';
|
||||
|
||||
@@ -28,6 +32,29 @@ interface MetroDevice {
|
||||
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 {
|
||||
const div = document.createElement('div');
|
||||
div.id = DEV_TOOLS_NODE_ID;
|
||||
@@ -77,17 +104,42 @@ export function devicePlugin(client: DevicePluginClient) {
|
||||
const connectionStatus = createState<ConnectionStatus>(
|
||||
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>();
|
||||
let pollHandle: NodeJS.Timeout | undefined = undefined;
|
||||
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() {
|
||||
isMounted = true;
|
||||
let devToolsNode = findDevToolsNode();
|
||||
if (!devToolsNode) {
|
||||
devToolsNode = createDevToolsNode();
|
||||
}
|
||||
|
||||
attachDevTools(containerRef.current!, devToolsNode);
|
||||
initializeDevTools(devToolsNode);
|
||||
setStatus(
|
||||
@@ -169,11 +221,12 @@ export function devicePlugin(client: DevicePluginClient) {
|
||||
);
|
||||
// Currently a new port is negotatiated every time the plugin is opened.
|
||||
// This can be potentially optimized by keeping the devTools instance around
|
||||
ReactDevToolsStandalone.setContentDOMNode(devToolsNode)
|
||||
startResult = devToolsInstance
|
||||
.setContentDOMNode(devToolsNode)
|
||||
.setStatusListener((status) => {
|
||||
setStatus(ConnectionStatus.Initializing, status);
|
||||
})
|
||||
.startServer(port);
|
||||
.startServer(port) as any;
|
||||
setStatus(ConnectionStatus.Initializing, 'Waiting for device');
|
||||
|
||||
// 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 {
|
||||
devtoolsHaveStarted,
|
||||
connectionStatus,
|
||||
@@ -219,6 +282,9 @@ export function devicePlugin(client: DevicePluginClient) {
|
||||
metroDevice,
|
||||
containerRef,
|
||||
stopDevtools,
|
||||
globalDevToolsPath,
|
||||
useGlobalDevTools,
|
||||
toggleUseGlobalDevTools,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,6 +292,8 @@ export function Component() {
|
||||
const instance = usePlugin(devicePlugin);
|
||||
const connectionStatus = useValue(instance.connectionStatus);
|
||||
const statusMessage = useValue(instance.statusMessage);
|
||||
const globalDevToolsPath = useValue(instance.globalDevToolsPath);
|
||||
const useGlobalDevTools = useValue(instance.useGlobalDevTools);
|
||||
|
||||
useEffect(() => {
|
||||
instance.bootDevTools();
|
||||
@@ -234,6 +302,19 @@ export function Component() {
|
||||
|
||||
return (
|
||||
<Layout.Container grow>
|
||||
{globalDevToolsPath ? (
|
||||
<Toolbar
|
||||
right={
|
||||
<>
|
||||
<Switch
|
||||
checked={useGlobalDevTools}
|
||||
onChange={instance.toggleUseGlobalDevTools}
|
||||
/>
|
||||
Use globally installed DevTools
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{!instance.devtoolsHaveStarted() ? (
|
||||
<Layout.Container
|
||||
style={{width: 400, margin: `${theme.space.large}px auto`}}>
|
||||
|
||||
Reference in New Issue
Block a user