Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Savona
557022396b [compiler] Improved ref validation for non-mutating functions
If a function is known to freeze its inputs, and captures refs, then we can safely assume those refs are not mutated during render.

An example is React Native's PanResponder, which is designed for use in interaction handling. Calling `PanResponder.create()` creates an object that shouldn't be interacted with at render time, so we can treat it as freezing its arguments, returning a frozen value, and not accessing any refs in the callbacks passed to it. ValidateNoRefAccessInRender is updated accordingly - if we see a Freeze <place> and ImmutableCapture <place> for the same place in the same instruction, we know that it's not being mutated.

Note that this is a pretty targeted fix. One weakness is that we may not always emit a Freeze effect if a value is already frozen, which could cause this optimization not to kick in. The worst case there is that you'd just get a ref access in render error though, not miscompilation. And we could always choose to always emit Freeze effects, even for frozen values, just to retain the information for validations like this.
2026-02-24 09:45:59 -08:00
20 changed files with 32 additions and 677 deletions

View File

@@ -792,20 +792,18 @@ export class Environment {
* NOTE: Zod doesn't work when specifying a function as a default, so we have to
* fallback to the default value here
*/
const moduleTypeProvider = this.config.moduleTypeProvider;
if (
moduleTypeProvider != null &&
typeof moduleTypeProvider !== 'function'
) {
const moduleTypeProvider =
this.config.moduleTypeProvider ?? defaultModuleTypeProvider;
if (moduleTypeProvider == null) {
return null;
}
if (typeof moduleTypeProvider !== 'function') {
CompilerError.throwInvalidConfig({
reason: `Expected a function for \`moduleTypeProvider\``,
loc,
});
}
const unparsedModuleConfig =
(typeof moduleTypeProvider === 'function'
? moduleTypeProvider(moduleName)
: null) ?? defaultModuleTypeProvider(moduleName);
const unparsedModuleConfig = moduleTypeProvider(moduleName);
if (unparsedModuleConfig != null) {
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
if (!parsedModuleConfig.success) {

View File

@@ -28,14 +28,6 @@ import {
BuiltInWeakMapId,
BuiltInWeakSetId,
BuiltInEffectEventId,
BuiltInIntlDateTimeFormatId,
BuiltInIntlNumberFormatId,
BuiltInIntlCollatorId,
BuiltInIntlPluralRulesId,
BuiltInIntlListFormatId,
BuiltInIntlRelativeTimeFormatId,
BuiltInIntlSegmenterId,
BuiltInIntlDisplayNamesId,
ReanimatedSharedValueId,
ShapeRegistry,
addFunction,
@@ -628,145 +620,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
true,
),
],
[
'Intl',
addObject(DEFAULT_SHAPES, 'Intl', [
[
'DateTimeFormat',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {
kind: 'Object',
shapeId: BuiltInIntlDateTimeFormatId,
},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
[
'NumberFormat',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInIntlNumberFormatId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
[
'Collator',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInIntlCollatorId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
[
'PluralRules',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInIntlPluralRulesId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
[
'ListFormat',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInIntlListFormatId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
[
'RelativeTimeFormat',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {
kind: 'Object',
shapeId: BuiltInIntlRelativeTimeFormatId,
},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
[
'Segmenter',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInIntlSegmenterId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
[
'DisplayNames',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInIntlDisplayNamesId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},
null,
true,
),
],
]),
],
// TODO: rest of Global objects
];

View File

@@ -389,16 +389,6 @@ export const BuiltInEffectEventId = 'BuiltInEffectEventFunction';
// See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types
export const ReanimatedSharedValueId = 'ReanimatedSharedValueId';
// Intl formatter instance shapes
export const BuiltInIntlDateTimeFormatId = 'BuiltInIntlDateTimeFormat';
export const BuiltInIntlNumberFormatId = 'BuiltInIntlNumberFormat';
export const BuiltInIntlCollatorId = 'BuiltInIntlCollator';
export const BuiltInIntlPluralRulesId = 'BuiltInIntlPluralRules';
export const BuiltInIntlListFormatId = 'BuiltInIntlListFormat';
export const BuiltInIntlRelativeTimeFormatId = 'BuiltInIntlRelativeTimeFormat';
export const BuiltInIntlSegmenterId = 'BuiltInIntlSegmenter';
export const BuiltInIntlDisplayNamesId = 'BuiltInIntlDisplayNames';
// ShapeRegistry with default definitions for built-ins.
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
@@ -1242,297 +1232,6 @@ addObject(BUILTIN_SHAPES, BuiltInRefValueId, [
addObject(BUILTIN_SHAPES, ReanimatedSharedValueId, []);
/**
* Intl formatter instance shapes.
*
* All Intl formatter objects are immutable after construction — calling their
* methods does not modify the formatter. Methods like `format()` return
* primitives (strings/numbers), `formatToParts()` returns a new array, and
* `resolvedOptions()` returns a new object.
*/
/* Intl.DateTimeFormat instance */
addObject(BUILTIN_SHAPES, BuiltInIntlDateTimeFormatId, [
[
'format',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'formatToParts',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'formatRange',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'formatRangeToParts',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
/* Intl.NumberFormat instance */
addObject(BUILTIN_SHAPES, BuiltInIntlNumberFormatId, [
[
'format',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'formatToParts',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'formatRange',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'formatRangeToParts',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
/* Intl.Collator instance */
addObject(BUILTIN_SHAPES, BuiltInIntlCollatorId, [
[
'compare',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
/* Intl.PluralRules instance */
addObject(BUILTIN_SHAPES, BuiltInIntlPluralRulesId, [
[
'select',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'selectRange',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
/* Intl.ListFormat instance */
addObject(BUILTIN_SHAPES, BuiltInIntlListFormatId, [
[
'format',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'formatToParts',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
/* Intl.RelativeTimeFormat instance */
addObject(BUILTIN_SHAPES, BuiltInIntlRelativeTimeFormatId, [
[
'format',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'formatToParts',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read, Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
/* Intl.Segmenter instance */
addObject(BUILTIN_SHAPES, BuiltInIntlSegmenterId, [
[
'segment',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
/* Intl.DisplayNames instance */
addObject(BUILTIN_SHAPES, BuiltInIntlDisplayNamesId, [
[
'of',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'resolvedOptions',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addFunction(
BUILTIN_SHAPES,
[],

View File

@@ -1,62 +0,0 @@
## Input
```javascript
function DateComponent({date}) {
const formatter = new Intl.DateTimeFormat('en-US');
return <time dateTime={date.toISOString()}>{formatter.format(date)}</time>;
}
export const FIXTURE_ENTRYPOINT = {
fn: DateComponent,
params: [{date: new Date('2024-01-01')}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function DateComponent(t0) {
const $ = _c(6);
const { date } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = new Intl.DateTimeFormat("en-US");
$[0] = t1;
} else {
t1 = $[0];
}
const formatter = t1;
let t2;
if ($[1] !== date) {
t2 = date.toISOString();
$[1] = date;
$[2] = t2;
} else {
t2 = $[2];
}
const t3 = formatter.format(date);
let t4;
if ($[3] !== t2 || $[4] !== t3) {
t4 = <time dateTime={t2}>{t3}</time>;
$[3] = t2;
$[4] = t3;
$[5] = t4;
} else {
t4 = $[5];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: DateComponent,
params: [{ date: new Date("2024-01-01") }],
};
```
### Eval output
(kind: ok) <time datetime="2024-01-01T00:00:00.000Z">12/31/2023</time>

View File

@@ -1,10 +0,0 @@
function DateComponent({date}) {
const formatter = new Intl.DateTimeFormat('en-US');
return <time dateTime={date.toISOString()}>{formatter.format(date)}</time>;
}
export const FIXTURE_ENTRYPOINT = {
fn: DateComponent,
params: [{date: new Date('2024-01-01')}],
};

View File

@@ -54,7 +54,6 @@ Each filter object must include `type` and `isEnabled`. Some filters also requir
|------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------|
| `host` | `"localhost"` | Socket connection to frontend should use this host. |
| `isAppActive` | | (Optional) function that returns true/false, telling DevTools when it's ready to connect to React. |
| `path` | `""` | Path appended to the WebSocket URI (e.g. `"/__react_devtools__/"`). Useful when proxying through a reverse proxy on a subpath. A leading `/` is added automatically if missing. |
| `port` | `8097` | Socket connection to frontend should use this port. |
| `resolveRNStyle` | | (Optional) function that accepts a key (number) and returns a style (object); used by React Native. |
| `retryConnectionDelay` | `200` | Delay (ms) to wait between retrying a failed Websocket connection |
@@ -142,51 +141,16 @@ function onStatus(
}
```
#### `startServer(port?, host?, httpsOptions?, loggerOptions?, path?, clientOptions?)`
#### `startServer(port?: number, host?: string, httpsOptions?: Object, loggerOptions?: Object)`
Start a socket server (used to communicate between backend and frontend) and renders the DevTools UI.
This method accepts the following parameters:
| Name | Default | Description |
|---|---|---|
| `port` | `8097` | Port the local server listens on. |
| `host` | `"localhost"` | Host the local server binds to. |
| `port` | `8097` | Socket connection to backend should use this port. |
| `host` | `"localhost"` | Socket connection to backend should use this host. |
| `httpsOptions` | | _Optional_ object defining `key` and `cert` strings. |
| `loggerOptions` | | _Optional_ object defining a `surface` string (to be included with DevTools logging events). |
| `path` | | _Optional_ path to append to the WebSocket URI served to connecting clients (e.g. `"/__react_devtools__/"`). Also set via the `REACT_DEVTOOLS_PATH` env var in the Electron app. |
| `clientOptions` | | _Optional_ object with client-facing overrides (see below). |
##### `clientOptions`
When connecting through a reverse proxy, the client may need to connect to a different host, port, or protocol than the local server. Use `clientOptions` to override what appears in the `connectToDevTools()` script served to clients. Any field not set falls back to the corresponding server value.
| Field | Default | Description |
|---|---|---|
| `host` | server `host` | Host the client connects to. |
| `port` | server `port` | Port the client connects to. |
| `useHttps` | server `useHttps` | Whether the client should use `wss://`. |
These can also be set via environment variables in the Electron app:
| Env Var | Description |
|---|---|
| `REACT_DEVTOOLS_CLIENT_HOST` | Overrides the host in the served client script. |
| `REACT_DEVTOOLS_CLIENT_PORT` | Overrides the port in the served client script. |
| `REACT_DEVTOOLS_CLIENT_USE_HTTPS` | Set to `"true"` to make the served client script use `wss://`. |
##### Reverse proxy example
Run DevTools locally on the default port, but tell clients to connect through a remote proxy:
```sh
REACT_DEVTOOLS_CLIENT_HOST=remote.example.com \
REACT_DEVTOOLS_CLIENT_PORT=443 \
REACT_DEVTOOLS_CLIENT_USE_HTTPS=true \
REACT_DEVTOOLS_PATH=/__react_devtools__/ \
react-devtools
```
The server listens on `localhost:8097`. The served script tells clients:
```js
connectToDevTools({host: 'remote.example.com', port: 443, useHttps: true, path: '/__react_devtools__/'})
```
# Development

View File

@@ -33,7 +33,6 @@ import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeS
type ConnectOptions = {
host?: string,
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
path?: string,
port?: number,
useHttps?: boolean,
resolveRNStyle?: ResolveNativeStyle,
@@ -94,7 +93,6 @@ export function connectToDevTools(options: ?ConnectOptions) {
const {
host = 'localhost',
nativeStyleEditorValidAttributes,
path = '',
useHttps = false,
port = 8097,
websocket,
@@ -109,7 +107,6 @@ export function connectToDevTools(options: ?ConnectOptions) {
} = options || {};
const protocol = useHttps ? 'wss' : 'ws';
const prefixedPath = path !== '' && !path.startsWith('/') ? '/' + path : path;
let retryTimeoutID: TimeoutID | null = null;
function scheduleRetry() {
@@ -132,7 +129,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
let bridge: BackendBridge | null = null;
const messageListeners = [];
const uri = protocol + '://' + host + ':' + port + prefixedPath;
const uri = protocol + '://' + host + ':' + port;
// If existing websocket is passed, use it.
// This is necessary to support our custom integrations.

View File

@@ -306,19 +306,11 @@ type LoggerOptions = {
surface?: ?string,
};
type ClientOptions = {
host?: string,
port?: number,
useHttps?: boolean,
};
function startServer(
port: number = 8097,
host: string = 'localhost',
httpsOptions?: ServerOptions,
loggerOptions?: LoggerOptions,
path?: string,
clientOptions?: ClientOptions,
): {close(): void} {
registerDevToolsEventLogger(loggerOptions?.surface ?? 'standalone');
@@ -353,18 +345,7 @@ function startServer(
server.on('error', (event: $FlowFixMe) => {
onError(event);
log.error('Failed to start the DevTools server', event);
startServerTimeoutID = setTimeout(
() =>
startServer(
port,
host,
httpsOptions,
loggerOptions,
path,
clientOptions,
),
1000,
);
startServerTimeoutID = setTimeout(() => startServer(port), 1000);
});
httpServer.on('request', (request: $FlowFixMe, response: $FlowFixMe) => {
@@ -377,21 +358,14 @@ function startServer(
// This will ensure that saved filters are shared across different web pages.
const componentFiltersString = JSON.stringify(getSavedComponentFilters());
// Client overrides: when connecting through a reverse proxy, the client
// may need to connect to a different host/port/protocol than the server.
const clientHost = clientOptions?.host ?? host;
const clientPort = clientOptions?.port ?? port;
const clientUseHttps = clientOptions?.useHttps ?? useHttps;
response.end(
backendFile.toString() +
'\n;' +
`var ReactDevToolsBackend = typeof ReactDevToolsBackend !== "undefined" ? ReactDevToolsBackend : require("ReactDevToolsBackend");\n` +
`ReactDevToolsBackend.initialize(undefined, undefined, undefined, ${componentFiltersString});` +
'\n' +
`ReactDevToolsBackend.connectToDevTools({port: ${clientPort}, host: '${clientHost}', useHttps: ${
clientUseHttps ? 'true' : 'false'
}${path != null ? `, path: '${path}'` : ''}});
`ReactDevToolsBackend.connectToDevTools({port: ${port}, host: '${host}', useHttps: ${
useHttps ? 'true' : 'false'
}});
`,
);
});
@@ -399,18 +373,7 @@ function startServer(
httpServer.on('error', (event: $FlowFixMe) => {
onError(event);
statusListener('Failed to start the server.', 'error');
startServerTimeoutID = setTimeout(
() =>
startServer(
port,
host,
httpsOptions,
loggerOptions,
path,
clientOptions,
),
1000,
);
startServerTimeoutID = setTimeout(() => startServer(port), 1000);
});
httpServer.listen(port, () => {

View File

@@ -44,7 +44,6 @@ module.exports = {
// This name is important; standalone references it in order to connect.
library: 'ReactDevToolsBackend',
libraryTarget: 'umd',
umdNamedDefine: true,
},
resolve: {
alias: {

View File

@@ -87,31 +87,7 @@ This will ensure the developer tools are connected. **Dont forget to remove i
## Advanced
By default DevTools listen to port `8097` on `localhost`. If you need to customize the server or client connection settings, the following environment variables are available:
| Env Var | Default | Description |
|---|---|---|
| `HOST` | `"localhost"` | Host the local server binds to. |
| `PORT` | `8097` | Port the local server listens on. |
| `REACT_DEVTOOLS_PORT` | | Alias for `PORT`. Takes precedence if both are set. |
| `KEY` | | Path to an SSL key file. Enables HTTPS when set alongside `CERT`. |
| `CERT` | | Path to an SSL certificate file. Enables HTTPS when set alongside `KEY`. |
| `REACT_DEVTOOLS_PATH` | | Path appended to the WebSocket URI served to clients (e.g. `/__react_devtools__/`). |
| `REACT_DEVTOOLS_CLIENT_HOST` | `HOST` | Overrides the host in the script served to connecting clients. |
| `REACT_DEVTOOLS_CLIENT_PORT` | `PORT` | Overrides the port in the script served to connecting clients. |
| `REACT_DEVTOOLS_CLIENT_USE_HTTPS` | | Set to `"true"` to make the served client script use `wss://`. |
When connecting through a reverse proxy, use the `REACT_DEVTOOLS_CLIENT_*` variables to tell clients to connect to a different host/port/protocol than the local server:
```sh
REACT_DEVTOOLS_CLIENT_HOST=remote.example.com \
REACT_DEVTOOLS_CLIENT_PORT=443 \
REACT_DEVTOOLS_CLIENT_USE_HTTPS=true \
REACT_DEVTOOLS_PATH=/__react_devtools__/ \
react-devtools
```
For more details, see the [`react-devtools-core` documentation](https://github.com/facebook/react/tree/main/packages/react-devtools-core).
By default DevTools listen to port `8097` on `localhost`. The port can be modified by setting the `REACT_DEVTOOLS_PORT` environment variable. If you need to further customize host, port, or other settings, see the `react-devtools-core` package instead.
## FAQ

View File

@@ -158,19 +158,12 @@
<script>
// window.api is defined in preload.js
const {electron, readEnv, ip, getDevTools} = window.api;
const {options, useHttps, host, protocol, port, path, clientHost, clientPort, clientUseHttps} = readEnv();
const {options, useHttps, host, protocol, port} = readEnv();
const localIp = ip.address();
// Effective values for display URLs: client overrides take precedence over server values.
const effectiveHost = clientHost != null ? clientHost : host;
const effectivePort = clientPort != null ? clientPort : port;
const effectiveUseHttps = clientUseHttps != null ? clientUseHttps : useHttps;
const effectiveProtocol = effectiveUseHttps ? 'https' : 'http';
const defaultPort = (effectivePort === 443 && effectiveUseHttps) || (effectivePort === 80 && !effectiveUseHttps);
const pathStr = path != null ? path : '';
const server = defaultPort ? `${effectiveProtocol}://${effectiveHost}${pathStr}` : `${effectiveProtocol}://${effectiveHost}:${effectivePort}${pathStr}`;
const serverIp = defaultPort ? `${effectiveProtocol}://${localIp}${pathStr}` : `${effectiveProtocol}://${localIp}:${effectivePort}${pathStr}`;
const defaultPort = (port === 443 && useHttps) || (port === 80 && !useHttps);
const server = defaultPort ? `${protocol}://${host}` : `${protocol}://${host}:${port}`;
const serverIp = defaultPort ? `${protocol}://${localIp}` : `${protocol}://${localIp}:${port}`;
const $ = document.querySelector.bind(document);
let timeoutID;
@@ -241,7 +234,7 @@
element.innerText = status;
}
})
.startServer(port, host, options, undefined, path, {host: clientHost, port: clientPort, useHttps: clientUseHttps});
.startServer(port, host, options);
</script>
</body>
</html>

View File

@@ -36,23 +36,6 @@ contextBridge.exposeInMainWorld('api', {
const host = process.env.HOST || 'localhost';
const protocol = useHttps ? 'https' : 'http';
const port = +process.env.REACT_DEVTOOLS_PORT || +process.env.PORT || 8097;
const path = process.env.REACT_DEVTOOLS_PATH || undefined;
const clientHost = process.env.REACT_DEVTOOLS_CLIENT_HOST || undefined;
const clientPort = process.env.REACT_DEVTOOLS_CLIENT_PORT
? +process.env.REACT_DEVTOOLS_CLIENT_PORT
: undefined;
const clientUseHttps =
process.env.REACT_DEVTOOLS_CLIENT_USE_HTTPS === 'true' ? true : undefined;
return {
options,
useHttps,
host,
protocol,
port,
path,
clientHost,
clientPort,
clientUseHttps,
};
return {options, useHttps, host, protocol, port};
},
});

View File

@@ -208,7 +208,7 @@ export const disableLegacyMode: boolean = true;
// in open source, but www codebase still relies on it. Need to remove.
export const disableCommentsAsDOMContainers: boolean = true;
export const enableTrustedTypesIntegration: boolean = true;
export const enableTrustedTypesIntegration: boolean = false;
// Prevent the value and checked attributes from syncing with their related
// DOM properties

View File

@@ -62,7 +62,7 @@ export const enableSuspenseAvoidThisFallback: boolean = false;
export const enableSuspenseCallback: boolean = true;
export const enableTaint: boolean = true;
export const enableTransitionTracing: boolean = false;
export const enableTrustedTypesIntegration: boolean = true;
export const enableTrustedTypesIntegration: boolean = false;
export const enableUpdaterTracking: boolean = __PROFILE__;
export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;

View File

@@ -50,7 +50,7 @@ export const enableSuspenseAvoidThisFallback: boolean = false;
export const enableSuspenseCallback: boolean = false;
export const enableTaint: boolean = true;
export const enableTransitionTracing: boolean = false;
export const enableTrustedTypesIntegration: boolean = true;
export const enableTrustedTypesIntegration: boolean = false;
export const passChildrenWhenCloningPersistedNodes: boolean = false;
export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;

View File

@@ -26,7 +26,7 @@ export const disableInputAttributeSyncing: boolean = false;
export const enableScopeAPI: boolean = false;
export const enableCreateEventHandleAPI: boolean = false;
export const enableSuspenseCallback: boolean = false;
export const enableTrustedTypesIntegration: boolean = true;
export const enableTrustedTypesIntegration: boolean = false;
export const disableTextareaChildren: boolean = false;
export const enableSuspenseAvoidThisFallback: boolean = false;
export const enableCPUSuspense: boolean = false;

View File

@@ -47,7 +47,7 @@ export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseCallback = false;
export const enableTaint = true;
export const enableTransitionTracing = false;
export const enableTrustedTypesIntegration = true;
export const enableTrustedTypesIntegration = false;
export const enableUpdaterTracking = false;
export const passChildrenWhenCloningPersistedNodes = false;
export const retryLaneExpirationMs = 5000;

View File

@@ -28,7 +28,7 @@ export const enableCreateEventHandleAPI: boolean = false;
export const enableSuspenseCallback: boolean = true;
export const disableLegacyContext: boolean = false;
export const disableLegacyContextForFunctionComponents: boolean = false;
export const enableTrustedTypesIntegration: boolean = true;
export const enableTrustedTypesIntegration: boolean = false;
export const disableTextareaChildren: boolean = false;
export const enableSuspenseAvoidThisFallback: boolean = true;
export const enableCPUSuspense: boolean = false;

View File

@@ -36,6 +36,7 @@ export const enableFragmentRefs: boolean = __VARIANT__;
export const enableFragmentRefsScrollIntoView: boolean = __VARIANT__;
export const enableFragmentRefsTextNodes: boolean = __VARIANT__;
export const enableInternalInstanceMap: boolean = __VARIANT__;
export const enableTrustedTypesIntegration: boolean = __VARIANT__;
export const enableParallelTransitions: boolean = __VARIANT__;
export const enableEffectEventMutationPhase: boolean = __VARIANT__;

View File

@@ -25,6 +25,7 @@ export const {
enableObjectFiber,
enableRetryLaneExpiration,
enableTransitionTracing,
enableTrustedTypesIntegration,
retryLaneExpirationMs,
syncLaneExpirationMs,
transitionLaneExpirationMs,
@@ -44,7 +45,7 @@ export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = __PROFILE__;
export const enableProfilerNestedUpdatePhase = __PROFILE__;
export const enableUpdaterTracking = __PROFILE__;
export const enableTrustedTypesIntegration: boolean = true;
export const enableSuspenseAvoidThisFallback: boolean = true;
export const enableAsyncDebugInfo: boolean = true;