Decapitate React DevTools

Summary:
Changelog: Migrate from react-devtools-core to -react-devtools-inline

Technical design doc: https://docs.google.com/document/d/1STUSUhXzrW_KkvqSu7Ge-rxjVFF7oU3_NbwzimkO_Z4

At this point, React DevTools doe snot support globally installed DevTools. Only the bundled version. The support for the globally installed DevTools comes in the subsequent diffs along with on-the-fly transpilation.

Reviewed By: mweststrate

Differential Revision: D34926472

fbshipit-source-id: fde1d4cf386adfbf8a8581ee5a54e950d2cb34ef
This commit is contained in:
Andrey Goncharov
2022-03-31 04:01:33 -07:00
committed by Facebook GitHub Bot
parent 1f83b4b414
commit 68aec1df60
8 changed files with 372 additions and 347 deletions

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
export type Events = {
message: any;
connected: never;
disconnected: never;
};
export type Methods = {
message: (data: any) => Promise<void>;
globalDevTools: () => Promise<string | undefined>;
};

View File

@@ -1,14 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
export function getInternalDevToolsModule<TModule>(): TModule {
throw new Error(
"Can't require internal version of React DevTools from public version of Flipper.",
);
}

View File

@@ -1,16 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
declare module 'get-port' {
const getPort: (options?: {
readonly port?: number;
readonly host?: string;
}) => Promise<number>;
export default getPort;
}

View File

@@ -7,50 +7,27 @@
* @format
*/
import type ReactDevToolsStandaloneType from 'react-devtools-core/standalone';
import {createRoot} from 'react-dom/client';
import {
Layout,
usePlugin,
DevicePluginClient,
createState,
useValue,
sleep,
Toolbar,
path,
getFlipperLib,
} from 'flipper-plugin';
import React from 'react';
import getPort from 'get-port';
import {Button, message, Switch, Typography, Select} from 'antd';
import fs from 'fs';
import {Button, message, Switch, Typography} from 'antd';
// @ts-expect-error
import * as ReactDevToolsOSS from 'react-devtools-inline/frontend';
import {DevToolsEmbedder} from './DevToolsEmbedder';
import {getInternalDevToolsModule} from './fb-stubs/getInternalDevToolsModule';
import {Events, Methods} from './contract';
const DEV_TOOLS_NODE_ID = 'reactdevtools-out-of-react-node';
const CONNECTED = 'DevTools connected';
const DEV_TOOLS_PORT = 8097; // hardcoded in RN
async function findGlobalDevTools(): Promise<string | undefined> {
try {
const {stdout: basePath} =
await getFlipperLib().remoteServerContext.childProcess.exec(
'npm root -g',
);
const devToolsPath = path.join(
basePath.trim(),
'react-devtools',
'node_modules',
'react-devtools-core',
);
await fs.promises.stat(devToolsPath);
return devToolsPath;
} catch (error) {
console.warn('Failed to find globally installed React DevTools: ' + error);
return undefined;
}
}
enum ConnectionStatus {
None = 'None',
Initializing = 'Initializing...',
WaitingForReload = 'Waiting for connection from device...',
WaitingForMetroReload = 'Waiting for Metro to reload...',
@@ -58,76 +35,70 @@ enum ConnectionStatus {
Error = 'Error',
}
type DevToolsInstanceType = 'global' | 'internal' | 'oss';
type DevToolsInstanceType = 'global' | 'oss';
type DevToolsInstance = {
type: DevToolsInstanceType;
module: ReactDevToolsStandaloneType;
module: any;
};
export function devicePlugin(client: DevicePluginClient) {
export function devicePlugin(client: DevicePluginClient<Events, Methods>) {
const metroDevice = client.device;
const statusMessage = createState('initializing');
const connectionStatus = createState<ConnectionStatus>(
ConnectionStatus.Initializing,
);
const globalDevToolsPath = createState<string>();
const statusMessage = createState('Empty');
const connectionStatus = createState<ConnectionStatus>(ConnectionStatus.None);
const initialized = createState(false);
const globalDevToolsAvailable = createState(false);
let globalDevToolsInstance: DevToolsInstance | undefined;
const useGlobalDevTools = createState(false, {
persist: 'useGlobalDevTools',
persistToLocalStorage: true,
});
let devToolsInstance = getDefaultDevToolsInstance();
const selectedDevToolsInstanceType = createState<DevToolsInstanceType>(
devToolsInstance.type,
);
let startResult: {close(): void} | undefined = undefined;
let devToolsInstance: DevToolsInstance | undefined;
const selectedDevToolsInstanceType = createState<DevToolsInstanceType>('oss');
let pollHandle: NodeJS.Timeout | undefined = undefined;
let metroReloadAttempts = 0;
function getGlobalDevToolsModule(): ReactDevToolsStandaloneType {
const required = (global as any).electronRequire(
globalDevToolsPath.get()!,
).default;
return required.default ?? required;
}
function getOSSDevToolsModule(): ReactDevToolsStandaloneType {
const required = require('react-devtools-core/standalone').default;
return required.default ?? required;
}
async function maybeGetInitialGlobalDevTools(): Promise<DevToolsInstance> {
const path = await findGlobalDevTools();
let instance = devToolsInstance;
if (path) {
globalDevToolsPath.set(path + '/standalone');
console.log('Found global React DevTools: ', path);
// load global devtools instance if the flag is set and
// we're running a non-FB version of Flipper
if (useGlobalDevTools.get() && !client.isFB) {
selectedDevToolsInstanceType.set('global');
console.debug(
'flipper-plugin-react-devtools.maybeGetInitialGlobalDevTools',
);
try {
const newGlobalDevToolsSource = await client.sendToServerAddOn(
'globalDevTools',
);
instance = {
if (newGlobalDevToolsSource) {
globalDevToolsInstance = {
type: 'global',
module: getGlobalDevToolsModule(),
// eslint-disable-next-line no-eval
module: eval(newGlobalDevToolsSource),
};
globalDevToolsAvailable.set(true);
}
} else {
useGlobalDevTools.set(false); // disable in case it was enabled
}
return instance;
} catch (e) {
console.error(
'flipper-plugin-react-devtools.maybeGetInitialGlobalDevTools -> failed to load global devtools',
e,
);
}
function getDefaultDevToolsInstance(): DevToolsInstance {
const type = client.isFB ? 'internal' : 'oss';
const module = client.isFB
? getInternalDevToolsModule<ReactDevToolsStandaloneType>()
: getOSSDevToolsModule();
return {type, module};
if (useGlobalDevTools.get() && globalDevToolsInstance) {
console.debug(
'flipper-plugin-react-devtools.maybeGetInitialGlobalDevTools -> using global devtools',
);
return globalDevToolsInstance;
}
useGlobalDevTools.set(false); // disable in case it was enabled
console.debug(
'flipper-plugin-react-devtools.maybeGetInitialGlobalDevTools -> using OSS devtools',
);
return {type: 'oss', module: ReactDevToolsOSS};
}
function getDevToolsInstance(
@@ -136,13 +107,10 @@ export function devicePlugin(client: DevicePluginClient) {
let module;
switch (instanceType) {
case 'global':
module = getGlobalDevToolsModule();
break;
case 'internal':
module = getInternalDevToolsModule<ReactDevToolsStandaloneType>();
module = globalDevToolsInstance!.module;
break;
case 'oss':
module = getOSSDevToolsModule();
module = ReactDevToolsOSS;
break;
}
return {
@@ -151,41 +119,19 @@ export function devicePlugin(client: DevicePluginClient) {
};
}
async function setDevToolsInstance(instanceType: DevToolsInstanceType) {
selectedDevToolsInstanceType.set(instanceType);
if (instanceType === 'global') {
if (!globalDevToolsPath.get()) {
message.warn(
"No globally installed react-devtools package found. Run 'npm install -g react-devtools'.",
);
return;
}
useGlobalDevTools.set(true);
} else {
useGlobalDevTools.set(false);
}
devToolsInstance = getDevToolsInstance(instanceType);
await rebootDevTools();
}
async function toggleUseGlobalDevTools() {
if (!globalDevToolsPath.get()) {
if (!globalDevToolsInstance) {
message.warn(
"No globally installed react-devtools package found. Run 'npm install -g react-devtools'.",
);
return;
}
selectedDevToolsInstanceType.update((prev: DevToolsInstanceType) => {
if (prev === 'global') {
devToolsInstance = getDefaultDevToolsInstance();
devToolsInstance = getDevToolsInstance(
prev === 'global' ? 'oss' : 'global',
);
return devToolsInstance.type;
} else {
devToolsInstance = getDevToolsInstance('global');
return devToolsInstance.type;
}
});
useGlobalDevTools.update((v) => !v);
@@ -194,102 +140,94 @@ export function devicePlugin(client: DevicePluginClient) {
async function rebootDevTools() {
metroReloadAttempts = 0;
setStatus(ConnectionStatus.Initializing, 'Loading DevTools...');
setStatus(ConnectionStatus.None, 'Loading DevTools...');
// clean old instance
if (pollHandle) {
clearTimeout(pollHandle);
}
startResult?.close();
await sleep(5000); // wait for port to close
startResult = undefined;
await bootDevTools();
}
async function bootDevTools() {
if (connectionStatus.get() !== ConnectionStatus.None) {
return;
}
if (!initialized.get()) {
console.debug(
'flipper-plugin-react-devtools -> waiting for initialization',
);
await new Promise<void>((resolve) =>
initialized.subscribe((newInitialized) => {
if (newInitialized) {
resolve();
}
}),
);
}
const devToolsNode = document.getElementById(DEV_TOOLS_NODE_ID);
if (!devToolsNode) {
setStatus(ConnectionStatus.Error, 'Failed to find target DOM Node');
return;
}
// React DevTools were initilized before
if (startResult) {
if (devtoolsHaveStarted()) {
setStatus(ConnectionStatus.Connected, CONNECTED);
} else {
startPollForConnection();
}
return;
}
// They're new!
try {
setStatus(
ConnectionStatus.Initializing,
'Waiting for port ' + DEV_TOOLS_PORT,
);
const port = await getPort({port: DEV_TOOLS_PORT}); // default port for dev tools
if (port !== DEV_TOOLS_PORT) {
setStatus(
ConnectionStatus.Error,
`Port ${DEV_TOOLS_PORT} is already taken`,
);
return;
}
setStatus(
ConnectionStatus.Initializing,
'Starting DevTools server on ' + DEV_TOOLS_PORT,
);
startResult = devToolsInstance.module
.setContentDOMNode(devToolsNode)
.setStatusListener((message: string, status: string) => {
// TODO: since devToolsInstance is an instance, we are probably leaking memory here
if (typeof status === 'undefined') {
// Preserves old behavior in case DevTools doesn't provide status,
// which may happen if loading an older version of DevTools.
setStatus(ConnectionStatus.Initializing, message);
return;
console.debug('flipper-plugin-react-devtools -> waiting for device');
setStatus(ConnectionStatus.Initializing, 'Waiting for device...');
client.onServerAddOnMessage('connected', () => {
if (pollHandle) {
clearTimeout(pollHandle);
}
switch (status) {
case 'server-connected': {
setStatus(ConnectionStatus.Initializing, message);
break;
}
case 'devtools-connected': {
if (pollHandle) {
clearTimeout(pollHandle);
}
setStatus(ConnectionStatus.Connected, message);
break;
}
case 'error': {
if (pollHandle) {
clearTimeout(pollHandle);
}
setStatus(ConnectionStatus.Error, message);
break;
}
}
})
.startServer(DEV_TOOLS_PORT, 'localhost', undefined, {
surface: 'flipper',
console.debug('flipper-plugin-react-devtools -> device found');
setStatus(
ConnectionStatus.Initializing,
'Device found. Initializing frontend...',
);
const wall = {
listen(listener: any) {
client.onServerAddOnMessage('message', (data) => {
console.debug(
'flipper-plugin-react-devtools.onServerAddOnMessage',
data,
);
listener(data);
});
setStatus(ConnectionStatus.Initializing, 'Waiting for device...');
},
send(event: any, payload: any) {
const data = {event, payload};
client.sendToServerAddOn('message', data);
},
};
const bridge = devToolsInstance!.module.createBridge(window, wall);
const store = devToolsInstance!.module.createStore(bridge);
const DevTools = devToolsInstance!.module.initialize(window, {
bridge,
store,
});
const root = createRoot(devToolsNode);
root.render(React.createElement(DevTools));
console.debug('flipper-plugin-react-devtools -> connected');
setStatus(ConnectionStatus.Connected, 'Connected');
});
startPollForConnection();
} catch (e) {
console.error('Failed to initalize React DevTools' + e);
setStatus(ConnectionStatus.Error, 'Failed to initialize DevTools: ' + e);
}
setStatus(
ConnectionStatus.Initializing,
'DevTools initialized, waiting for connection...',
);
if (devtoolsHaveStarted()) {
setStatus(ConnectionStatus.Connected, CONNECTED);
} else {
startPollForConnection();
}
}
function setStatus(cs: ConnectionStatus, status: string) {
@@ -350,17 +288,18 @@ export function devicePlugin(client: DevicePluginClient) {
);
}
client.onReady(async () => {
client.onReady(() => {
client.onServerAddOnStart(async () => {
devToolsInstance = await maybeGetInitialGlobalDevTools();
initialized.set(true);
});
client.onDestroy(() => {
startResult?.close();
});
client.onActivate(() => {
client.onServerAddOnStart(async () => {
bootDevTools();
});
});
client.onDeactivate(() => {
if (pollHandle) {
@@ -369,84 +308,63 @@ export function devicePlugin(client: DevicePluginClient) {
});
return {
isFB: client.isFB,
devtoolsHaveStarted,
connectionStatus,
statusMessage,
bootDevTools,
rebootDevTools,
metroDevice,
globalDevToolsPath,
globalDevToolsAvailable,
useGlobalDevTools,
selectedDevToolsInstanceType,
setDevToolsInstance,
toggleUseGlobalDevTools,
initialized,
};
}
export function Component() {
const instance = usePlugin(devicePlugin);
const globalDevToolsAvailable = useValue(instance.globalDevToolsAvailable);
const connectionStatus = useValue(instance.connectionStatus);
const displayToolbar =
globalDevToolsAvailable || connectionStatus !== ConnectionStatus.Connected;
return (
<Layout.Container grow>
<>
<DevToolsInstanceToolbar />
<DevToolsEmbedder offset={40} nodeId={DEV_TOOLS_NODE_ID} />
</Layout.Container>
<DevToolsEmbedder
offset={displayToolbar ? 40 : 0}
nodeId={DEV_TOOLS_NODE_ID}
/>
</>
);
}
function DevToolsInstanceToolbar() {
const instance = usePlugin(devicePlugin);
const globalDevToolsPath = useValue(instance.globalDevToolsPath);
const globalDevToolsAvailable = useValue(instance.globalDevToolsAvailable);
const connectionStatus = useValue(instance.connectionStatus);
const statusMessage = useValue(instance.statusMessage);
const useGlobalDevTools = useValue(instance.useGlobalDevTools);
const selectedDevToolsInstanceType = useValue(
instance.selectedDevToolsInstanceType,
);
const initialized = useValue(instance.initialized);
if (!globalDevToolsPath && !instance.isFB) {
return null;
}
let selectionControl;
if (instance.isFB) {
const devToolsInstanceOptions = [{value: 'internal'}, {value: 'oss'}];
if (globalDevToolsPath) {
devToolsInstanceOptions.push({value: 'global'});
}
selectionControl = (
<>
Select preferred DevTools version:
<Select
options={devToolsInstanceOptions}
value={selectedDevToolsInstanceType}
onChange={instance.setDevToolsInstance}
style={{width: 90}}
size="small"
/>
</>
);
} else if (globalDevToolsPath) {
selectionControl = (
const selectionControl = globalDevToolsAvailable ? (
<>
<Switch
checked={useGlobalDevTools}
onChange={instance.toggleUseGlobalDevTools}
size="small"
disabled={!initialized}
/>
Use globally installed DevTools
</>
);
} else {
throw new Error(
'Should not render Toolbar if not FB build or a global DevTools install not available.',
);
}
) : null;
return (
<Layout.Container grow>
<Toolbar right={selectionControl} wash>
{connectionStatus !== ConnectionStatus.Connected ? (
<Typography.Text type="secondary">{statusMessage}</Typography.Text>
) : null}
{connectionStatus === ConnectionStatus.WaitingForReload ||
connectionStatus === ConnectionStatus.WaitingForMetroReload ||
connectionStatus === ConnectionStatus.Error ? (
@@ -460,5 +378,6 @@ function DevToolsInstanceToolbar() {
</Button>
) : null}
</Toolbar>
</Layout.Container>
);
}

View File

@@ -12,14 +12,15 @@
],
"main": "dist/bundle.js",
"flipperBundlerEntry": "index.tsx",
"serverAddOn": "dist/serverAddOn.js",
"flipperBundlerEntryServerAddOn": "serverAddOn.tsx",
"license": "MIT",
"keywords": [
"flipper-plugin"
],
"dependencies": {
"address": "^1.1.2",
"get-port": "^5.0.0",
"react-devtools-core": "4.24.1"
"react-devtools-inline": "^4.24.3",
"ws": "^8.5.0"
},
"title": "React DevTools",
"icon": "app-react",

View File

@@ -1,35 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
type ServerOptions = {
key?: string;
cert?: string;
};
type LoggerOptions = {
surface?: string;
};
type StatusTypes = 'server-connected' | 'devtools-connected' | 'error';
type StatusListener = (message: string, status: StatusTypes) => void;
declare module 'react-devtools-core/standalone' {
interface DevTools {
setContentDOMNode(node: HTMLElement): this;
startServer(
port?: number,
host?: string,
httpsOptions?: ServerOptions,
loggerOptions?: LoggerOptions,
): {close: () => void};
setStatusListener(listener: StatusListener): this;
}
const DevTools: DevTools;
export default DevTools;
}

View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {
createControlledPromise,
FlipperServerForServerAddOn,
ServerAddOn,
} from 'flipper-plugin';
import path from 'path';
import {WebSocketServer, WebSocket} from 'ws';
import {Events, Methods} from './contract';
const DEV_TOOLS_PORT = 8097; // hardcoded in RN
async function findGlobalDevTools(
flipperServer: FlipperServerForServerAddOn,
): Promise<string | undefined> {
try {
const {stdout: basePath} = await flipperServer.exec(
'node-api-exec',
'npm root -g',
);
console.debug(
'flipper-plugin-react-devtools.findGlobalDevTools -> npm root',
basePath,
);
const devToolsPath = path.join(
basePath.trim(),
'react-devtools-inline',
'dist',
'frontend.js',
);
await flipperServer.exec('node-api-fs-stat', devToolsPath);
return devToolsPath;
} catch (error) {
console.warn('Failed to find globally installed React DevTools: ' + error);
return undefined;
}
}
const serverAddOn: ServerAddOn<Events, Methods> = async (
connection,
{flipperServer},
) => {
console.debug('flipper-plugin-react-devtools.serverAddOn -> starting');
const startServer = async () => {
console.debug('flipper-plugin-react-devtools.serverAddOn -> startServer');
const wss = new WebSocketServer({port: DEV_TOOLS_PORT});
const startedPromise = createControlledPromise<void>();
wss.on('listening', () => startedPromise.resolve());
wss.on('error', (err) => {
if (startedPromise.state === 'pending') {
startedPromise.reject(err);
return;
}
console.error('flipper-plugin-react-devtools.serverAddOn -> error', err);
});
await startedPromise.promise;
console.debug(
'flipper-plugin-react-devtools.serverAddOn -> started server',
);
wss.on('connection', (ws) => {
connection.send('connected');
console.debug(
'flipper-plugin-react-devtools.serverAddOn -> connected a client',
);
ws.on('message', (data) => {
connection.send('message', JSON.parse(data.toString()));
console.debug(
'flipper-plugin-react-devtools.serverAddOn -> client sent a message',
data.toString(),
);
});
ws.on('error', (err) => {
console.error(
'flipper-plugin-react-devtools.serverAddOn -> client error',
err,
);
});
ws.on('close', () => {
connection.send('disconnected');
console.debug(
'flipper-plugin-react-devtools.serverAddOn -> client left',
);
});
});
connection.receive('message', (data) => {
console.debug(
'flipper-plugin-react-devtools.serverAddOn -> desktop sent a message',
data,
);
wss!.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
});
});
return wss;
};
const wss = await startServer();
connection.receive('globalDevTools', async () => {
const globalDevToolsPath = await findGlobalDevTools(flipperServer);
if (!globalDevToolsPath) {
console.info(
'flipper-plugin-react-devtools.serverAddOn -> not found global React DevTools',
);
return;
}
console.info(
'flipper-plugin-react-devtools.serverAddOn -> found global React DevTools: ',
globalDevToolsPath,
);
// TODO: Transform ReactDevTools for browsers
/*
const globalDevToolsSource = globalDevToolsPath;
return globalDevToolsSource;
*/
return;
});
return async () => {
console.debug('flipper-plugin-react-devtools.serverAddOn -> stopping');
if (wss) {
console.debug(
'flipper-plugin-react-devtools.serverAddOn -> stopping wss',
);
await new Promise<void>((resolve, reject) =>
wss!.close((err) => (err ? reject(err) : resolve())),
);
console.debug('flipper-plugin-react-devtools.serverAddOn -> stopped wss');
}
};
};
export default serverAddOn;

View File

@@ -267,11 +267,6 @@ JSONSelect@0.2.1:
resolved "https://registry.yarnpkg.com/JSONSelect/-/JSONSelect-0.2.1.tgz#415418a526d33fe31d74b4defa3c836d485ec203"
integrity sha1-QVQYpSbTP+MddLTe+jyDbUhewgM=
address@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
@@ -880,11 +875,6 @@ get-intrinsic@^1.0.2:
has "^1.0.3"
has-symbols "^1.0.1"
get-port@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@@ -1483,13 +1473,13 @@ raf@^3.1.0, raf@^3.4.0:
dependencies:
performance-now "^2.1.0"
react-devtools-core@4.24.1:
version "4.24.1"
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.24.1.tgz#d274390db86898d779b6202a318fe6422eb046bb"
integrity sha512-skar+cqSg5Oz89n4lQ/aBQS8RGj93FMufg2TrMJqE+RSUTO9nLEYawRMXXCs8PnDVRSfG5pPVU5Nt1OegNflyA==
react-devtools-inline@^4.24.3:
version "4.24.3"
resolved "https://registry.yarnpkg.com/react-devtools-inline/-/react-devtools-inline-4.24.3.tgz#a60259ec397c126452b259444b39e6cc98d253d4"
integrity sha512-A98BC0538RDCRJeM2tHFOL6xzl1DvGxEWqf0oW51NFEmRmtALyw47OtRmyR1cBA4Q7cIKKWLab/DHIVTEZssbA==
dependencies:
shell-quote "^1.6.1"
ws "^7"
source-map-js "^0.6.2"
sourcemap-codec "^1.4.8"
react-is@16.10.2:
version "16.10.2"
@@ -1721,11 +1711,6 @@ shebang-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
shell-quote@^1.6.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123"
integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
@@ -1761,6 +1746,11 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
source-map-js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
source-map-resolve@^0.5.0:
version "0.5.3"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
@@ -1782,6 +1772,11 @@ source-map@^0.5.6:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -1910,10 +1905,10 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^7:
version "7.5.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@^8.5.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
xml-beautifier@^0.4.0:
version "0.4.3"