react-x11 2.49.0 → 2.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +4 -4
- package/src/ClickToComponent.js +11 -132
- package/src/DevToolsIntegration.js +126 -42
- package/src/Reconciler.js +26 -10
- package/src/cli/dev.js +162 -19
- package/src/cli/errorwindow.js +368 -0
- package/src/cli/index.js +4 -0
- package/src/cocoa/app.js +22 -0
- package/src/cocoa/presenter.js +3 -1
- package/src/cocoa/promotion.js +10 -11
- package/src/cocoa/relaunch.js +114 -15
- package/src/cocoa/window.js +98 -15
- package/src/currentcolor.js +28 -0
- package/src/decorations.js +21 -9
- package/src/editor.js +151 -0
- package/src/foreignnodes.js +8 -1
- package/src/gloverlay.js +2 -1
- package/src/node.d.ts +5 -0
- package/src/nodes/animation.js +31 -3
- package/src/nodes/boxpaint.js +34 -8
- package/src/nodes/cascade.js +50 -3
- package/src/nodes/damage.js +2 -0
- package/src/nodes/layout.js +9 -17
- package/src/nodes/moveblit.js +5 -5
- package/src/nodes/node.js +8 -0
- package/src/nodes/paint.js +4 -2
- package/src/nodes/queries.js +46 -34
- package/src/nodes/scrollable.js +4 -1
- package/src/nodes/scrollblit.js +10 -7
- package/src/nodes/styling.js +176 -21
- package/src/nodes/window/capabilities.js +1 -1
- package/src/nodes/window/floors.js +63 -18
- package/src/nodes/window/size.js +94 -19
- package/src/refresh/loader.d.ts +16 -0
- package/src/refresh/loader.js +58 -8
- package/src/shorthands.js +1266 -0
- package/src/styles.js +370 -131
- package/src/types/elements.d.ts +5 -2
- package/src/types/style.d.ts +163 -39
- package/src/types/system.d.ts +4 -1
package/README.md
CHANGED
|
@@ -464,6 +464,10 @@ npx react-devtools # 1. start the standalone UI
|
|
|
464
464
|
REACT_X11_DEVTOOLS=1 npm run examples:dashboard # 2. run any example with the bridge on
|
|
465
465
|
```
|
|
466
466
|
|
|
467
|
+
Under `npx react-x11 dev` the bridge is on by default (with
|
|
468
|
+
`react-devtools-core` installed): run `npx react-devtools`, before or after,
|
|
469
|
+
and the app shows up.
|
|
470
|
+
|
|
467
471
|
The component tree, props and hook state show up live in the DevTools
|
|
468
472
|
window, and editing any of them re-renders the app. Hovering an element in
|
|
469
473
|
the tree tints its rect in the app's window; the toolbar's crosshair picks
|
|
@@ -498,7 +502,7 @@ Alt+Click any rendered element (Option+Click on macOS, either backend) to
|
|
|
498
502
|
open the JSX line that created it in your editor. `REACT_X11_EDITOR` picks
|
|
499
503
|
the CLI and enables the feature in one go; `REACT_X11_CLICK_TO_COMPONENT=1`
|
|
500
504
|
does the same with the default editor
|
|
501
|
-
(`cursor`). See [docs/click-to-component.md](docs/click-to-component.md).
|
|
505
|
+
(`cursor`), and `npx react-x11 dev` turns it on by default. See [docs/click-to-component.md](docs/click-to-component.md).
|
|
502
506
|
|
|
503
507
|
## TypeScript
|
|
504
508
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.51.0",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"yoga-layout": "^3.2.1"
|
|
113
113
|
},
|
|
114
114
|
"optionalDependencies": {
|
|
115
|
-
"@windowkit/appkit": "^0.
|
|
115
|
+
"@windowkit/appkit": "^0.30.0",
|
|
116
116
|
"@windowkit/wayland": "^3.1.1",
|
|
117
117
|
"@windowkit/win32": "^0.0.9",
|
|
118
118
|
"dbus-native": "^0.16.0",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"peerDependencies": {
|
|
122
122
|
"@babel/core": "^8.0.0",
|
|
123
123
|
"@babel/plugin-transform-react-jsx": "^8.0.0",
|
|
124
|
-
"hot-module-replacement": "^4.0.
|
|
124
|
+
"hot-module-replacement": "^4.0.2",
|
|
125
125
|
"react": "^19.3.0",
|
|
126
126
|
"react-refresh": "^0.19.0"
|
|
127
127
|
},
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
"eslint": "^9.32.0",
|
|
154
154
|
"eslint-plugin-react": "^7.37.5",
|
|
155
155
|
"globals": "^17.8.0",
|
|
156
|
-
"hot-module-replacement": "^4.0.
|
|
156
|
+
"hot-module-replacement": "^4.0.2",
|
|
157
157
|
"katex": "^0.19.0",
|
|
158
158
|
"prettier": "^3.6.0",
|
|
159
159
|
"react": "^19.3.0",
|
package/src/ClickToComponent.js
CHANGED
|
@@ -10,40 +10,9 @@
|
|
|
10
10
|
// loader both enable) has already rewritten that Error's stack to point at
|
|
11
11
|
// original source — we only have to parse the stack text and skip the
|
|
12
12
|
// frames inside React/the reconciler itself.
|
|
13
|
-
import { spawn } from 'node:child_process';
|
|
14
|
-
import { dirname, sep } from 'node:path';
|
|
15
|
-
import { fileURLToPath } from 'node:url';
|
|
16
13
|
import { setClickToComponentHandler } from './events.js';
|
|
17
14
|
import { selectInDevTools } from './DevToolsIntegration.js';
|
|
18
|
-
|
|
19
|
-
const STACK_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/;
|
|
20
|
-
|
|
21
|
-
// The frames to walk past before the JSX call site itself: React captures
|
|
22
|
-
// the Error inside `jsxDEV`/`createElement`, and an element created by this
|
|
23
|
-
// renderer (or by node internals) is never what a click means. `SELF_DIR` is
|
|
24
|
-
// whichever copy of react-x11 is running — the repo's own `src/` for the
|
|
25
|
-
// examples and tests, an installed `node_modules/react-x11/src` for an app.
|
|
26
|
-
//
|
|
27
|
-
// Everything *else* in `node_modules` is deliberately not skipped here: a
|
|
28
|
-
// frame inside an installed package is a real call site, it is just not one
|
|
29
|
-
// in the application's source, and telling the two apart is what lets
|
|
30
|
-
// `resolveOwnedLocation` climb to the owner that is. Skipping every
|
|
31
|
-
// `node_modules` frame instead would walk straight past the library
|
|
32
|
-
// component and land on whatever application frame happens to be deeper in
|
|
33
|
-
// the render stack — usually the `render()` call at startup, which looks
|
|
34
|
-
// like an answer and isn't.
|
|
35
|
-
const REACT_RUNTIME =
|
|
36
|
-
/[\\/]node_modules[\\/](react|react-dom|react-reconciler|scheduler)[\\/]/;
|
|
37
|
-
const SELF_DIR = dirname(fileURLToPath(import.meta.url)) + sep;
|
|
38
|
-
|
|
39
|
-
function isInternalFrame(file) {
|
|
40
|
-
return (
|
|
41
|
-
file.startsWith('node:') ||
|
|
42
|
-
file === '<anonymous>' ||
|
|
43
|
-
file.startsWith(SELF_DIR) ||
|
|
44
|
-
REACT_RUNTIME.test(file)
|
|
45
|
-
);
|
|
46
|
-
}
|
|
15
|
+
import { openInEditor, stackFrames } from './editor.js';
|
|
47
16
|
|
|
48
17
|
/** The first stack frame outside React/the reconciler/node internals — the
|
|
49
18
|
* JSX call site for whatever element this Error was captured at, wherever it
|
|
@@ -52,25 +21,16 @@ function isInternalFrame(file) {
|
|
|
52
21
|
* Exported because `react-x11/test`'s `sourceOf` answers the same question
|
|
53
22
|
* for a test that a click answers for an editor. */
|
|
54
23
|
export function resolveLocation(debugStack) {
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
functionName,
|
|
67
|
-
file,
|
|
68
|
-
line: Number(lineStr),
|
|
69
|
-
column: Number(columnStr),
|
|
70
|
-
installed: file.includes(`${sep}node_modules${sep}`),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
24
|
+
const frame = stackFrames(debugStack?.stack).find((f) => !f.internal);
|
|
25
|
+
if (!frame) return null;
|
|
26
|
+
const { functionName, file, line, column, installed } = frame;
|
|
27
|
+
return {
|
|
28
|
+
functionName: functionName ?? undefined,
|
|
29
|
+
file,
|
|
30
|
+
line,
|
|
31
|
+
column,
|
|
32
|
+
installed,
|
|
33
|
+
};
|
|
74
34
|
}
|
|
75
35
|
|
|
76
36
|
/** The nearest source location a click can mean: the clicked element's own
|
|
@@ -132,87 +92,6 @@ function logOwnerChain(fiber) {
|
|
|
132
92
|
}
|
|
133
93
|
}
|
|
134
94
|
|
|
135
|
-
// GUI editors: navigate their registered URI scheme (same idea as
|
|
136
|
-
// https://github.com/sidorares/show-component, minus the browser) instead of
|
|
137
|
-
// spawning their CLI shim. The CLI wrapper (e.g. `cursor -g ...`) round-trips
|
|
138
|
-
// through a shell script that re-detects and re-execs the real app, which is
|
|
139
|
-
// visibly slower than the OS handing the URL straight to the already-running
|
|
140
|
-
// instance via Launch Services (macOS) / xdg-open (Linux).
|
|
141
|
-
const EDITOR_SCHEMES = {
|
|
142
|
-
cursor: 'cursor',
|
|
143
|
-
code: 'vscode',
|
|
144
|
-
vscode: 'vscode',
|
|
145
|
-
'code-insiders': 'vscode-insiders',
|
|
146
|
-
'vscode-insiders': 'vscode-insiders',
|
|
147
|
-
windsurf: 'windsurf',
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Terminal editors have no URI scheme to navigate — spawn them directly.
|
|
151
|
-
const EDITOR_CLI = {
|
|
152
|
-
vim: (file, line, column) => [
|
|
153
|
-
'vim',
|
|
154
|
-
[`+call cursor(${line},${column})`, file],
|
|
155
|
-
],
|
|
156
|
-
nvim: (file, line, column) => [
|
|
157
|
-
'nvim',
|
|
158
|
-
[`+call cursor(${line},${column})`, file],
|
|
159
|
-
],
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const OPEN_URI_COMMAND = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
163
|
-
|
|
164
|
-
function editorUri(scheme, file, line, column) {
|
|
165
|
-
return `${scheme}://file${encodeURI(file)}:${line}:${column}`;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function openInEditor({ file, line, column }) {
|
|
169
|
-
const name = process.env.REACT_X11_EDITOR || 'cursor';
|
|
170
|
-
let bin, args;
|
|
171
|
-
if (EDITOR_CLI[name]) {
|
|
172
|
-
[bin, args] = EDITOR_CLI[name](file, line, column);
|
|
173
|
-
} else {
|
|
174
|
-
// an unrecognized name is assumed to be a URI scheme itself, so any
|
|
175
|
-
// editor that registers one works without a matching entry above
|
|
176
|
-
const scheme = EDITOR_SCHEMES[name] || name;
|
|
177
|
-
bin = OPEN_URI_COMMAND;
|
|
178
|
-
args = [editorUri(scheme, file, line, column)];
|
|
179
|
-
}
|
|
180
|
-
// stderr is kept (rather than the whole stdio ignored) for one reason:
|
|
181
|
-
// `open`/`xdg-open` exit non-zero and print *there* when nothing claims
|
|
182
|
-
// the scheme — an editor that isn't installed, or one whose URL handler
|
|
183
|
-
// was never registered. Swallowing that is what makes click-to-component
|
|
184
|
-
// look like it only logs: the location resolves, the console line prints,
|
|
185
|
-
// and the editor silently never opens.
|
|
186
|
-
const child = spawn(bin, args, {
|
|
187
|
-
detached: true,
|
|
188
|
-
stdio: ['ignore', 'ignore', 'pipe'],
|
|
189
|
-
});
|
|
190
|
-
let stderr = '';
|
|
191
|
-
// ...and unref'd, so keeping it does not keep the process alive: a pipe is
|
|
192
|
-
// a ref'd handle, and an editor that outlives the app it was launched from
|
|
193
|
-
// is the normal case, not a reason to hold the event loop open.
|
|
194
|
-
child.stderr.unref();
|
|
195
|
-
child.stderr.on('data', (chunk) => {
|
|
196
|
-
stderr += chunk;
|
|
197
|
-
});
|
|
198
|
-
child.on('error', (err) => {
|
|
199
|
-
console.warn(
|
|
200
|
-
`react-x11: click-to-component could not launch "${bin}" (${err.code ?? err.message}). ` +
|
|
201
|
-
'Set REACT_X11_EDITOR to your editor CLI (cursor, code, code-insiders, windsurf, vim, nvim).',
|
|
202
|
-
);
|
|
203
|
-
});
|
|
204
|
-
child.on('exit', (code) => {
|
|
205
|
-
if (!code) return;
|
|
206
|
-
console.warn(
|
|
207
|
-
`react-x11: click-to-component — \`${bin} ${args.join(' ')}\` exited ${code}. ` +
|
|
208
|
-
(stderr.trim() ? `${stderr.trim()} ` : '') +
|
|
209
|
-
`Set REACT_X11_EDITOR to the editor you actually use (currently "${name}"; ` +
|
|
210
|
-
'cursor, code, code-insiders, windsurf, vim, nvim, or any registered URI scheme).',
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
child.unref();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
95
|
/** Briefly tint the clicked node so there is visual confirmation of what was
|
|
217
96
|
* resolved, reusing the same overlay the DevTools hover highlight paints. */
|
|
218
97
|
function flashHighlight(node) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Opt-in React DevTools bridge. Enabled by setting REACT_X11_DEVTOOLS=1 in
|
|
2
|
-
// the environment
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// the environment, and on by default under `react-x11 dev`; requires the
|
|
3
|
+
// `react-devtools-core` dev dependency. Run the standalone UI
|
|
4
|
+
// (`npx react-devtools`) and the app, in either order:
|
|
5
|
+
// REACT_X11_DEVTOOLS=1 npm run examples:dashboard
|
|
5
6
|
//
|
|
6
7
|
// Most of what DevTools can do costs this file nothing: the component tree,
|
|
7
8
|
// props/state/hooks, and every edit the frontend makes go through the
|
|
@@ -12,12 +13,14 @@
|
|
|
12
13
|
// Each of those is an agent listener or an option below.
|
|
13
14
|
import { spawn } from 'node:child_process';
|
|
14
15
|
import fs from 'node:fs';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
15
17
|
import os from 'node:os';
|
|
16
18
|
import path from 'node:path';
|
|
17
19
|
import { setInspectHandler } from './events.js';
|
|
18
20
|
import { flattenStyle, isStyleProp, STYLE_PROP_NAMES } from './styles.js';
|
|
19
21
|
|
|
20
22
|
let api = null;
|
|
23
|
+
let prepared = null;
|
|
21
24
|
let connected = false;
|
|
22
25
|
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
@@ -107,9 +110,16 @@ export function createStorage(persist) {
|
|
|
107
110
|
/**
|
|
108
111
|
* Install the DevTools global hook. Must run before the first React commit
|
|
109
112
|
* so mounted roots are observed — Reconciler.js awaits this at module load
|
|
110
|
-
* when REACT_X11_DEVTOOLS is set
|
|
113
|
+
* when REACT_X11_DEVTOOLS is set — and before React Refresh patches the
|
|
114
|
+
* hook, which `react-x11 dev` calls it early for: the backend installs no
|
|
115
|
+
* hook where one exists, and Refresh's is a stub. Once per process.
|
|
111
116
|
*/
|
|
112
|
-
export
|
|
117
|
+
export function prepare() {
|
|
118
|
+
prepared ??= install();
|
|
119
|
+
return prepared;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function install() {
|
|
113
123
|
try {
|
|
114
124
|
// Each hook's source location is read off a stack captured at the hook
|
|
115
125
|
// dispatch, and V8 keeps ten frames — fewer than a loader leaves
|
|
@@ -122,9 +132,6 @@ export async function prepare() {
|
|
|
122
132
|
// react-devtools-core's backend bundle expects browser-ish globals
|
|
123
133
|
global.self ??= global;
|
|
124
134
|
global.window ??= global;
|
|
125
|
-
if (!global.WebSocket) {
|
|
126
|
-
global.WebSocket = (await import('ws')).default;
|
|
127
|
-
}
|
|
128
135
|
// The update-tracing overlay schedules its redraws on animation
|
|
129
136
|
// frames, which Node does not have; without these the first traced
|
|
130
137
|
// commit throws inside the backend. Unref'd, because an overlay must
|
|
@@ -162,6 +169,7 @@ export async function prepare() {
|
|
|
162
169
|
const mod = await import('react-devtools-core');
|
|
163
170
|
// v7 exposes the API on the default export under node ESM interop
|
|
164
171
|
api = mod.default?.initialize ? mod.default : mod;
|
|
172
|
+
HostSocket = hostSocketClass();
|
|
165
173
|
// The hook settings the user last chose in the DevTools UI — console
|
|
166
174
|
// patching and friends. `initialize()` takes them, `onSettingsUpdated`
|
|
167
175
|
// (below) records them, so a setting survives a restart.
|
|
@@ -170,7 +178,7 @@ export async function prepare() {
|
|
|
170
178
|
api = null;
|
|
171
179
|
console.warn(
|
|
172
180
|
'react-x11: REACT_X11_DEVTOOLS is set but devtools could not be loaded. ' +
|
|
173
|
-
'Install react-devtools-core
|
|
181
|
+
'Install react-devtools-core. Original error: ' +
|
|
174
182
|
err.message,
|
|
175
183
|
);
|
|
176
184
|
}
|
|
@@ -254,6 +262,80 @@ export function reloadAndProfile(recordChangeDescriptions, recordTimeline) {
|
|
|
254
262
|
// Connect
|
|
255
263
|
// ---------------------------------------------------------------------------
|
|
256
264
|
|
|
265
|
+
// The backend keeps trying to connect for as long as the process lives — a
|
|
266
|
+
// timer every two seconds while no UI is listening, and an open socket while
|
|
267
|
+
// one is — and either is enough to keep a Node process up after the app's
|
|
268
|
+
// last window closed. Under `react-x11 dev`, where it is on by default, that
|
|
269
|
+
// was every session hanging on close. Neither is reachable from the options:
|
|
270
|
+
// so the backend's own code runs in a scope where the timers it starts are
|
|
271
|
+
// unref'd and the sockets it opens are, too. The scope is entered where the
|
|
272
|
+
// backend is — our connect call, its socket's close and error handlers that
|
|
273
|
+
// schedule the retry, and the timers it set in the scope — and swaps the
|
|
274
|
+
// globals only for that synchronous stretch, so the app's own `setTimeout`
|
|
275
|
+
// and `WebSocket` are never the ones changed.
|
|
276
|
+
let HostSocket = null;
|
|
277
|
+
|
|
278
|
+
function inBackend(fn) {
|
|
279
|
+
const { setTimeout: appSetTimeout, WebSocket: appWebSocket } = globalThis;
|
|
280
|
+
globalThis.setTimeout = (callback, ...rest) => {
|
|
281
|
+
const timer = appSetTimeout(
|
|
282
|
+
typeof callback === 'function'
|
|
283
|
+
? (...args) => inBackend(() => callback(...args))
|
|
284
|
+
: callback,
|
|
285
|
+
...rest,
|
|
286
|
+
);
|
|
287
|
+
timer?.unref?.();
|
|
288
|
+
return timer;
|
|
289
|
+
};
|
|
290
|
+
if (HostSocket) globalThis.WebSocket = HostSocket;
|
|
291
|
+
try {
|
|
292
|
+
return fn();
|
|
293
|
+
} finally {
|
|
294
|
+
globalThis.setTimeout = appSetTimeout;
|
|
295
|
+
globalThis.WebSocket = appWebSocket;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* The backend's WebSocket: `ws`, from react-devtools-core's own
|
|
301
|
+
* dependencies, with its socket unref'd once open and its close and error
|
|
302
|
+
* handlers run in the backend's scope. Falls back to the platform's
|
|
303
|
+
* WebSocket, which works and holds the process open.
|
|
304
|
+
*/
|
|
305
|
+
function hostSocketClass() {
|
|
306
|
+
let WS;
|
|
307
|
+
try {
|
|
308
|
+
WS = createRequire(import.meta.resolve('react-devtools-core'))('ws');
|
|
309
|
+
} catch {
|
|
310
|
+
return globalThis.WebSocket ?? null;
|
|
311
|
+
}
|
|
312
|
+
class Socket extends WS {
|
|
313
|
+
constructor(...args) {
|
|
314
|
+
super(...args);
|
|
315
|
+
this.on('open', () => this._socket?.unref?.());
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (const name of ['onclose', 'onerror']) {
|
|
319
|
+
const base = Object.getOwnPropertyDescriptor(WS.prototype, name);
|
|
320
|
+
if (!base?.set) continue;
|
|
321
|
+
Object.defineProperty(Socket.prototype, name, {
|
|
322
|
+
configurable: true,
|
|
323
|
+
get() {
|
|
324
|
+
return base.get.call(this);
|
|
325
|
+
},
|
|
326
|
+
set(handler) {
|
|
327
|
+
base.set.call(
|
|
328
|
+
this,
|
|
329
|
+
typeof handler === 'function'
|
|
330
|
+
? (...args) => inBackend(() => handler(...args))
|
|
331
|
+
: handler,
|
|
332
|
+
);
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return Socket;
|
|
337
|
+
}
|
|
338
|
+
|
|
257
339
|
/** Connect the backend to the standalone DevTools app and register the
|
|
258
340
|
* renderer. Called by Reconciler.js right after the renderer is created. */
|
|
259
341
|
export function connect(renderer) {
|
|
@@ -261,39 +343,41 @@ export function connect(renderer) {
|
|
|
261
343
|
connected = true;
|
|
262
344
|
const profiling = readProfilingOnStart();
|
|
263
345
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
346
|
+
inBackend(() =>
|
|
347
|
+
api.connectToDevTools({
|
|
348
|
+
isAppActive: () => true,
|
|
349
|
+
host: process.env.REACT_X11_DEVTOOLS_HOST || 'localhost',
|
|
350
|
+
port: Number(process.env.REACT_X11_DEVTOOLS_PORT) || 8097,
|
|
351
|
+
// The style editor: `style` is a react-x11 style prop (an object, or a
|
|
352
|
+
// nested array of them), which is the shape DevTools' own resolver
|
|
353
|
+
// expects to be handed flat. `validAttributes` is what it offers to
|
|
354
|
+
// add to an element.
|
|
355
|
+
resolveRNStyle: resolveStyle,
|
|
356
|
+
nativeStyleEditorValidAttributes: STYLE_PROP_NAMES,
|
|
357
|
+
// Hook settings (console patching and so on) are the user's, not the
|
|
358
|
+
// run's: keep them where the next run will find them.
|
|
359
|
+
onSettingsUpdated: (settings) => {
|
|
360
|
+
try {
|
|
361
|
+
global.localStorage?.setItem(
|
|
362
|
+
HOOK_SETTINGS_KEY,
|
|
363
|
+
JSON.stringify(settings),
|
|
364
|
+
);
|
|
365
|
+
} catch {
|
|
366
|
+
// an unwritable store is not worth a warning per keystroke
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
// The backend's own check for this asks whether synchronous XHR works,
|
|
370
|
+
// which is a browser question — the honest answer for a node process
|
|
371
|
+
// that can re-exec itself is yes.
|
|
372
|
+
isReloadAndProfileSupported: true,
|
|
373
|
+
isProfiling: profiling != null,
|
|
374
|
+
onReloadAndProfile: reloadAndProfile,
|
|
375
|
+
onReloadAndProfileFlagsReset: () => {
|
|
376
|
+
delete process.env.REACT_X11_DEVTOOLS_PROFILING;
|
|
377
|
+
delete process.env.REACT_X11_DEVTOOLS_SESSION;
|
|
378
|
+
},
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
297
381
|
|
|
298
382
|
// No argument: react-reconciler 0.33 takes none, and the object this used
|
|
299
383
|
// to pass was discarded in silence. The renderer's name and version now
|
package/src/Reconciler.js
CHANGED
|
@@ -592,16 +592,15 @@ let integrationsSettled = false;
|
|
|
592
592
|
function loadIntegrations() {
|
|
593
593
|
if (integrationsSettled) return null;
|
|
594
594
|
if (
|
|
595
|
-
!
|
|
596
|
-
!
|
|
597
|
-
!process.env.REACT_X11_EDITOR &&
|
|
595
|
+
!envFlag('REACT_X11_DEVTOOLS') &&
|
|
596
|
+
!clickToComponentWanted() &&
|
|
598
597
|
!process.env.REACT_X11_TRACE
|
|
599
598
|
) {
|
|
600
599
|
return null;
|
|
601
600
|
}
|
|
602
601
|
if (integrations) return integrations;
|
|
603
602
|
integrations = (async () => {
|
|
604
|
-
if (
|
|
603
|
+
if (envFlag('REACT_X11_DEVTOOLS')) {
|
|
605
604
|
// Install the DevTools hook before any React commit, and register the
|
|
606
605
|
// renderer with the standalone DevTools app.
|
|
607
606
|
const devtools = await import('./DevToolsIntegration.js');
|
|
@@ -609,12 +608,7 @@ function loadIntegrations() {
|
|
|
609
608
|
devtools.connect(Renderer);
|
|
610
609
|
}
|
|
611
610
|
|
|
612
|
-
if (
|
|
613
|
-
process.env.REACT_X11_CLICK_TO_COMPONENT ||
|
|
614
|
-
process.env.REACT_X11_EDITOR
|
|
615
|
-
) {
|
|
616
|
-
// Naming an editor already means you want the feature on — no need to
|
|
617
|
-
// also set REACT_X11_CLICK_TO_COMPONENT=1 just to pick one.
|
|
611
|
+
if (clickToComponentWanted()) {
|
|
618
612
|
const clickToComponent = await import('./ClickToComponent.js');
|
|
619
613
|
clickToComponent.install();
|
|
620
614
|
}
|
|
@@ -632,6 +626,28 @@ function loadIntegrations() {
|
|
|
632
626
|
return integrations;
|
|
633
627
|
}
|
|
634
628
|
|
|
629
|
+
/**
|
|
630
|
+
* Whether to install click-to-component. Naming an editor already means you
|
|
631
|
+
* want the feature on — no need to also set REACT_X11_CLICK_TO_COMPONENT=1
|
|
632
|
+
* just to pick one — and `0` turns it off whatever else is set, which is
|
|
633
|
+
* the way out of `react-x11 dev`, where it is on by default and
|
|
634
|
+
* REACT_X11_EDITOR also picks the error window's editor.
|
|
635
|
+
*/
|
|
636
|
+
function clickToComponentWanted() {
|
|
637
|
+
if (process.env.REACT_X11_CLICK_TO_COMPONENT === '0') return false;
|
|
638
|
+
return (
|
|
639
|
+
envFlag('REACT_X11_CLICK_TO_COMPONENT') ||
|
|
640
|
+
Boolean(process.env.REACT_X11_EDITOR)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** A switch in the environment: set, and not `0` — the way out of a
|
|
645
|
+
* default `react-x11 dev` turns on. */
|
|
646
|
+
function envFlag(name) {
|
|
647
|
+
const value = process.env[name];
|
|
648
|
+
return Boolean(value) && value !== '0';
|
|
649
|
+
}
|
|
650
|
+
|
|
635
651
|
/**
|
|
636
652
|
* Open a connection this process owns. The wrapper is here for one reason:
|
|
637
653
|
* to say what is wrong when there is no server, which is the first thing
|