react-x11 2.51.0 → 2.52.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 +87 -54
- package/docs/README.md +410 -0
- package/docs/accessibility.md +628 -0
- package/docs/appearance.md +416 -0
- package/docs/architecture/accessibility.md +470 -0
- package/docs/architecture/animation.md +707 -0
- package/docs/architecture/container-queries.md +525 -0
- package/docs/architecture/custom-layout.md +334 -0
- package/docs/architecture/drag-and-drop.md +941 -0
- package/docs/architecture/element-layer-contents.md +622 -0
- package/docs/architecture/frame-pacing.md +255 -0
- package/docs/architecture/grid-layout.md +358 -0
- package/docs/architecture/protocol-efficiency.md +357 -0
- package/docs/architecture/video.md +1149 -0
- package/docs/architecture/windowed-regions.md +854 -0
- package/docs/click-to-component.md +125 -0
- package/docs/clipboard.md +281 -0
- package/docs/components.md +1749 -0
- package/docs/dbus.md +277 -0
- package/docs/debugging.md +464 -0
- package/docs/desktop-calendar.md +284 -0
- package/docs/desktop.md +648 -0
- package/docs/devtools.md +250 -0
- package/docs/drag-and-drop.md +663 -0
- package/docs/ecosystem/animation.md +394 -0
- package/docs/ecosystem/data-fetching.md +201 -0
- package/docs/ecosystem/dev-tooling.md +436 -0
- package/docs/ecosystem/forms.md +313 -0
- package/docs/ecosystem/headless.md +219 -0
- package/docs/ecosystem/i18n.md +89 -0
- package/docs/ecosystem/icons.md +410 -0
- package/docs/ecosystem/layout.md +216 -0
- package/docs/ecosystem/routing.md +146 -0
- package/docs/ecosystem/state.md +361 -0
- package/docs/ecosystem/testing.md +500 -0
- package/docs/ecosystem/theming.md +540 -0
- package/docs/ecosystem.md +473 -0
- package/docs/elements.md +2762 -0
- package/docs/embedding.md +288 -0
- package/docs/events.md +1025 -0
- package/docs/extending.md +2242 -0
- package/docs/eyedropper.md +223 -0
- package/docs/filedialog.md +258 -0
- package/docs/frame.md +580 -0
- package/docs/gl.md +170 -0
- package/docs/globalmenu.md +278 -0
- package/docs/glx.md +137 -0
- package/docs/macos.md +3295 -0
- package/docs/notifications.md +164 -0
- package/docs/packaging.md +611 -0
- package/docs/permissions.md +148 -0
- package/docs/react-features.md +430 -0
- package/docs/remote.md +223 -0
- package/docs/scale.md +339 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1928 -0
- package/docs/system.md +599 -0
- package/docs/testing.md +514 -0
- package/docs/typescript.md +117 -0
- package/docs/uri-schemes.md +380 -0
- package/docs/wayland-backend.md +591 -0
- package/docs/wayland.md +545 -0
- package/docs/windows-embedding.md +271 -0
- package/docs/windows-gl.md +221 -0
- package/docs/windows-integrations.md +233 -0
- package/docs/windows.md +1404 -0
- package/package.json +10 -3
- package/src/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- package/src/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/filedialog.js +3 -3
- package/src/keyboardstate.js +28 -0
- package/src/node.d.ts +10 -10
- package/src/portal.js +31 -0
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- package/src/wayland/app.js +59 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- package/src/wayland/xkb.js +137 -0
package/docs/devtools.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# React DevTools
|
|
2
|
+
|
|
3
|
+
react-x11 speaks to the standalone React DevTools app over its WebSocket
|
|
4
|
+
bridge.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm i -D react-devtools-core # already a dev dep of this repo
|
|
10
|
+
|
|
11
|
+
# terminal 1 — the standalone UI (listens on :8097)
|
|
12
|
+
npx react-devtools
|
|
13
|
+
|
|
14
|
+
# terminal 2 — any app/example with the bridge enabled
|
|
15
|
+
REACT_X11_DEVTOOLS=1 npm run examples:dashboard
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Either order works: the backend retries every two seconds, so an app
|
|
19
|
+
started first attaches a moment after the UI comes up. Under
|
|
20
|
+
`npx react-x11 dev` the bridge is on by default wherever
|
|
21
|
+
`react-devtools-core` is installed — run `npx react-devtools` and the app
|
|
22
|
+
shows up, `REACT_X11_DEVTOOLS=0` turns it off — and where it is not
|
|
23
|
+
installed while a UI is listening, `dev` says what to install.
|
|
24
|
+
|
|
25
|
+
What you get:
|
|
26
|
+
|
|
27
|
+
- the **component tree**, live as the app updates;
|
|
28
|
+
- **props, hooks and state** when selecting a component, and editing any of
|
|
29
|
+
them — the edit goes through the reconciler's own override path, so the
|
|
30
|
+
app really re-renders;
|
|
31
|
+
- **highlight-on-hover** — hovering an element in the tree tints its rect
|
|
32
|
+
in the X11 window;
|
|
33
|
+
- **"highlight updates when components render"** — the rects that just
|
|
34
|
+
re-rendered are outlined, in DevTools' own colour ramp (teal for a first
|
|
35
|
+
render through amber for one that keeps re-rendering), fading out on the
|
|
36
|
+
backend's clock;
|
|
37
|
+
- the **element picker** — the crosshair in the DevTools toolbar. While it
|
|
38
|
+
is on, the pointer belongs to DevTools: motion tints whatever is under
|
|
39
|
+
it, a click selects that element in the tree, `Escape` gives up. The app
|
|
40
|
+
sees none of those events;
|
|
41
|
+
- the **style editor** — the box model and the flattened `style` of a
|
|
42
|
+
selected element, editable live. Host elements are hidden by the default
|
|
43
|
+
component filter, so turn that filter off (⚙ → Components → uncheck
|
|
44
|
+
"host components") to have something with a style to select;
|
|
45
|
+
- the Profiler's commit list and flamegraph, including **"reload and start
|
|
46
|
+
profiling"**, which re-execs the app with profiling already on so the
|
|
47
|
+
mount itself is recorded. **Not** its Timeline view, which needs
|
|
48
|
+
`injectProfilingHooks` — this reconciler build does not expose it, so
|
|
49
|
+
there is nothing to enable;
|
|
50
|
+
- **DevTools' own settings** — component filters, console patching — kept
|
|
51
|
+
between runs, in `$XDG_CACHE_HOME/react-x11/devtools.json`
|
|
52
|
+
(`~/Library/Caches/react-x11/devtools.json` on macOS).
|
|
53
|
+
|
|
54
|
+
With `REACT_X11_CLICK_TO_COMPONENT=1` as well, an Alt+Click also selects
|
|
55
|
+
the clicked element in the DevTools tree on its way to opening the source
|
|
56
|
+
([click-to-component.md](click-to-component.md)).
|
|
57
|
+
|
|
58
|
+
`REACT_X11_DEVTOOLS_HOST` / `REACT_X11_DEVTOOLS_PORT` point the backend at
|
|
59
|
+
a non-default standalone (e.g. devtools on another machine — handy when the
|
|
60
|
+
X app runs on a headless box). `REACT_X11_DEVTOOLS_STATE` moves the
|
|
61
|
+
settings file.
|
|
62
|
+
|
|
63
|
+
DevTools has no network panel — nothing in its protocol carries network
|
|
64
|
+
data, and no injection would change that. For that, `node --inspect` and
|
|
65
|
+
`chrome://inspect` still work alongside all of this.
|
|
66
|
+
|
|
67
|
+
## Opening the source from the DevTools panel
|
|
68
|
+
|
|
69
|
+
The inspected-element panel already shows where a component was written —
|
|
70
|
+
`file:///…/tasks.jsx:42:7`, from the same React 19 call-site stacks
|
|
71
|
+
click-to-component reads, absolute and source-mapped. Making that line
|
|
72
|
+
_clickable_ is a standalone-side setting, and it has two independent
|
|
73
|
+
routes:
|
|
74
|
+
|
|
75
|
+
- **Spawn an editor.** The standalone launches one itself, choosing it from
|
|
76
|
+
`REACT_EDITOR` (a full command line, shell-quoted), else — on macOS — by
|
|
77
|
+
looking through `ps x` for an editor that is already running, else
|
|
78
|
+
`VISUAL`, else `EDITOR`. It knows each editor's argument style — vim
|
|
79
|
+
takes `+42`, sublime and the JetBrains editors take `file:42` — so this
|
|
80
|
+
is the route to reach for:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
REACT_EDITOR=cursor npx react-devtools
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **Open a URL.** ⚙ → General → "Open local files directly in your code
|
|
87
|
+
editor", with the VS Code preset or a custom template using `{path}`,
|
|
88
|
+
`{line}` and `{column}`. The frontend turns our `file://` source into a
|
|
89
|
+
plain path before filling it in. The setting lives in the standalone's
|
|
90
|
+
own storage, not the app's.
|
|
91
|
+
|
|
92
|
+
Both are configured where the standalone runs, which may be a different
|
|
93
|
+
machine from the app. Neither is something the app can inject: the source
|
|
94
|
+
location is all the backend is asked for, and it is already sent.
|
|
95
|
+
Click-to-component ([click-to-component.md](click-to-component.md)) is the
|
|
96
|
+
app-side answer to the same question, and the one that works when DevTools
|
|
97
|
+
runs somewhere without your editor on it.
|
|
98
|
+
|
|
99
|
+
The same backend, minus the socket, powers `react-x11/test`'s component
|
|
100
|
+
inspection: `inspect()`/`setHook` in
|
|
101
|
+
[testing.md](testing.md#inspectnode-and-sethook) drive the renderer
|
|
102
|
+
interface the backend registers at injection, entirely in-process — no
|
|
103
|
+
standalone app, no `ws`, no env variable. If a test run does set
|
|
104
|
+
`REACT_X11_DEVTOOLS=1`, the two share one renderer registration (see the
|
|
105
|
+
guard in `connect()`).
|
|
106
|
+
|
|
107
|
+
## Watching the wire
|
|
108
|
+
|
|
109
|
+
The bridge is a plain WebSocket carrying text frames of `{"event": …,
|
|
110
|
+
"payload": …}` — no CDP, no binary framing. `scripts/devtools-proxy.mjs`
|
|
111
|
+
sits in the middle of it and prints what goes past, decoding the packed
|
|
112
|
+
`operations` arrays (the tree deltas) into readable lines:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npx react-devtools # terminal 1 — the real UI on :8097
|
|
116
|
+
npm run devtools:proxy # terminal 2 — :8098 -> :8097
|
|
117
|
+
REACT_X11_DEVTOOLS=1 REACT_X11_DEVTOOLS_PORT=8098 npm run examples:dashboard
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
0.008 ▲ app operations 193B
|
|
122
|
+
· renderer 1, root 1
|
|
123
|
+
· add root 1
|
|
124
|
+
· add 3 <Counter> (Function) under 2
|
|
125
|
+
0.291 ▼ devtools inspectElement {"id":3,"rendererID":1,…}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`--only`/`--skip` filter by event name, `--full` prints whole payloads, and
|
|
129
|
+
`--jsonl FILE` appends every frame for later analysis.
|
|
130
|
+
|
|
131
|
+
## How it works (and its sharp edges)
|
|
132
|
+
|
|
133
|
+
`src/DevToolsIntegration.js`. When `REACT_X11_DEVTOOLS` is set, the hook is
|
|
134
|
+
installed from `createRoot()` rather than at import time — a
|
|
135
|
+
top-level await there would make every bundle of react-x11 ESM-only, which
|
|
136
|
+
rules out a single executable ([packaging.md](packaging.md)). The ordering
|
|
137
|
+
guarantee is the same: a commit can only follow a root, and there is no way
|
|
138
|
+
to get one without awaiting the install. It connects the backend and
|
|
139
|
+
registers the renderer via `injectIntoDevTools`.
|
|
140
|
+
|
|
141
|
+
The backend never stops trying to connect — a retry timer while no UI
|
|
142
|
+
listens, an open socket while one does — and either kept the process up
|
|
143
|
+
after the app's last window closed. Its code runs in a scope
|
|
144
|
+
(`inBackend`) where the timers it starts and the sockets it opens are
|
|
145
|
+
unref'd, entered at our connect call, at its socket's close and error
|
|
146
|
+
handlers, and at the timers it armed there; the globals are swapped only
|
|
147
|
+
for that synchronous stretch, so the app's own `setTimeout` and
|
|
148
|
+
`WebSocket` are untouched. The socket is `ws` from react-devtools-core's
|
|
149
|
+
own dependencies, which is why `ws` is not a separate install.
|
|
150
|
+
|
|
151
|
+
Under `react-x11 dev` the hook is installed earlier, before React Refresh
|
|
152
|
+
loads: Refresh installs a stub hook where it finds none, the backend
|
|
153
|
+
installs none where it finds one, and the stub cannot connect.
|
|
154
|
+
|
|
155
|
+
Notes for maintainers:
|
|
156
|
+
|
|
157
|
+
- **`injectIntoDevTools()` takes no arguments** in react-reconciler 0.34.
|
|
158
|
+
What it reports comes from the host config — `rendererPackageName`,
|
|
159
|
+
`rendererVersion`, `extraDevToolsConfig` — and an argument passed here is
|
|
160
|
+
ignored without a word, which is how react-x11 shipped for a while with
|
|
161
|
+
its renderer name and version quietly missing (#121). A test in
|
|
162
|
+
`test/smoke.test.js` asserts what reaches the hook, so the next
|
|
163
|
+
reconciler bump cannot repeat it.
|
|
164
|
+
- react-devtools-core v7 requires `initialize()` before
|
|
165
|
+
`connectToDevTools`, exposes its API on the **default export** under
|
|
166
|
+
node ESM interop, and expects browser-ish globals (`self`, `window`) —
|
|
167
|
+
all handled in `prepare()`.
|
|
168
|
+
- The Highlighter only emits `showNativeHighlight` for host instances that
|
|
169
|
+
implement `getClientRects()` returning a non-empty rect, and anything
|
|
170
|
+
with `getClientRects` is measured at mount through
|
|
171
|
+
`instance.ownerDocument.documentElement` — nodes therefore expose both
|
|
172
|
+
(see `Node.getClientRects` / the `ownerDocument` stub in
|
|
173
|
+
`src/nodes/`).
|
|
174
|
+
- Highlight events arrive on the backend agent
|
|
175
|
+
(`hook.reactDevtoolsAgent`, or the `'react-devtools'` hook event);
|
|
176
|
+
`attachHighlightAgent` maps the public instance to its owning window
|
|
177
|
+
node and paints a translucent overlay.
|
|
178
|
+
- **What makes the native paths run at all** is
|
|
179
|
+
`isReactNativeEnvironment()`, which the backend defines as
|
|
180
|
+
`window.document == null`. That is true for a node process with
|
|
181
|
+
`window = global`, so the backend takes React Native's route everywhere:
|
|
182
|
+
it _asks the host_ to draw the highlight and the update outlines, and to
|
|
183
|
+
drive the picker, instead of drawing into a DOM overlay of its own. Every
|
|
184
|
+
injection below is one of those asks.
|
|
185
|
+
- **Update outlines**: the backend counts updates, picks each rect's colour
|
|
186
|
+
and expires it on its own clock, then emits `drawTraceUpdates`
|
|
187
|
+
(`[{node, color}]` — the whole live set, every time) and
|
|
188
|
+
`disableTraceUpdates`. `attachTraceUpdatesAgent` groups them by window
|
|
189
|
+
and hands each root the rects; `WindowNode.setTraceUpdates` is a dumb
|
|
190
|
+
overlay that replaces rather than accumulates. It schedules those redraws
|
|
191
|
+
on `requestAnimationFrame`, which node does not have — `prepare()`
|
|
192
|
+
installs an unref'd `setTimeout` shim, and without it the first traced
|
|
193
|
+
commit throws inside the backend.
|
|
194
|
+
- **The picker**: with no `window.addEventListener` to install, the backend
|
|
195
|
+
emits `startInspectingNative` and waits. `attachPickerAgent` takes the
|
|
196
|
+
pointer through `setInspectHandler` (events.js) — motion, press and
|
|
197
|
+
release are answered and go no further — and calls `agent.selectNode()`
|
|
198
|
+
then `agent.stopInspectingNative(true)`. Cancelling from the DevTools
|
|
199
|
+
side arrives as a bridge message with no host-facing event, so the
|
|
200
|
+
picker also listens on `agent._bridge` directly; without that a
|
|
201
|
+
cancelled picker would keep swallowing the app's input.
|
|
202
|
+
- **The style editor** needs three things and is offered only if it gets
|
|
203
|
+
them: `resolveRNStyle` (our `flattenStyle`, minus state blocks, which are
|
|
204
|
+
not editable values), `nativeStyleEditorValidAttributes`
|
|
205
|
+
(`STYLE_PROP_NAMES`), and `instance.measure(cb)` on host instances for
|
|
206
|
+
the box model — RN's contract, implemented by `Node.measure`. Edits
|
|
207
|
+
arrive as `overrideValueAtPath` on `['style']` with the value wrapped in
|
|
208
|
+
an array, which works here only because a react-x11 `style` prop already
|
|
209
|
+
takes an array.
|
|
210
|
+
- **Reload and profile** is gated on a backend check that asks whether
|
|
211
|
+
synchronous XHR works, so `isReloadAndProfileSupported: true` is passed
|
|
212
|
+
explicitly. Registering a renderer does not start profiling, either — the
|
|
213
|
+
run that comes back from the restart starts it itself, before the first
|
|
214
|
+
commit, which is the only reason to restart at all.
|
|
215
|
+
- `localStorage`/`sessionStorage` are **defined** on the global rather than
|
|
216
|
+
assigned: reading `global.localStorage` first is what makes node print
|
|
217
|
+
its `--localstorage-file` warning on every DevTools run.
|
|
218
|
+
- **Hook names and sources need a deeper stack than V8 keeps.** React reads
|
|
219
|
+
each hook's location — and the custom-hook nesting the names come from —
|
|
220
|
+
off a stack captured at the hook dispatch, and `Error.stackTraceLimit` is
|
|
221
|
+
10 by default. A loader leaves frames of its own between the hook and its
|
|
222
|
+
call site (`tsx`, which every `npm run examples:*` goes through, costs
|
|
223
|
+
about ten), so the identifying frames fall off the end: `hookSource`
|
|
224
|
+
comes back `{fileName: null, lineNumber: null, …}`, the frontend logs
|
|
225
|
+
"Hook source code location not found", and the element shows its hooks
|
|
226
|
+
with no names and no source lines (#514). `prepare()` raises the limit to
|
|
227
|
+
50 when the bridge is enabled, and only upwards, so an app that asked for
|
|
228
|
+
more keeps it. `test/devtools-hooks.test.js` squeezes the budget and
|
|
229
|
+
asserts the names and sources still come back over the bridge.
|
|
230
|
+
- **Selecting a component with hooks can blank the DevTools window.** That
|
|
231
|
+
is the standalone app's own renderer process aborting, upstream of
|
|
232
|
+
anything the host sends: it happens with every hook source complete, and
|
|
233
|
+
under runtimes with no loader at all. Turning ⚙ → Components → "Always
|
|
234
|
+
parse hook names for the selected element" off makes it intermittent
|
|
235
|
+
rather than reliable; restarting `npx react-devtools` reconnects to the
|
|
236
|
+
app, which is unaffected.
|
|
237
|
+
- `test/devtools.test.js` drives the real backend over a socket for all of
|
|
238
|
+
the above; the fake-agent tests in `test/smoke.test.js` prove the drawing
|
|
239
|
+
but not that the backend still asks for it.
|
|
240
|
+
|
|
241
|
+
## Other debugging aids
|
|
242
|
+
|
|
243
|
+
- `REACT_X11_DEBUG_LAYOUT=1` — outline every laid-out node, color-coded by
|
|
244
|
+
tree depth.
|
|
245
|
+
- Refs: drawn elements expose the retained node (`abs` rect, `scrollTo`,
|
|
246
|
+
…); `<window>`/`<popup>` expose the live ntk window (`getContext('2d')`,
|
|
247
|
+
`queryTree`, `setCursor`, …).
|
|
248
|
+
- The in-process X server (`x11/lib/xserver`) renders headlessly and reads
|
|
249
|
+
pixels back — see `test/integration.test.js` for the harness pattern,
|
|
250
|
+
which is also how the README screenshots are generated.
|