react-x11 2.52.0 → 2.53.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 +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1091 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/layouts.js +1 -1
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/refresh/index.js +2 -1
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +27 -0
- package/src/wayland/video.js +376 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
- package/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- package/docs/windows-gl.md +0 -221
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# Developer tooling
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The four tools answer four different questions, and it is worth knowing which
|
|
8
|
-
is which before you reach for one:
|
|
3
|
+
A react-x11 app is an ordinary Node process, so V8 and React tooling apply
|
|
4
|
+
unmodified; nothing here needs an adapter. Which tool answers which
|
|
5
|
+
question:
|
|
9
6
|
|
|
10
7
|
| Question | Tool |
|
|
11
8
|
| ---------------------------------------------- | ------------------------------------------------ |
|
|
@@ -14,21 +11,19 @@ is which before you reach for one:
|
|
|
14
11
|
| Where did the milliseconds go? | [`node --inspect`](#node-inspect), [`0x`](#0x) |
|
|
15
12
|
| Why is memory growing? | [`node --inspect`](#node-inspect) heap snapshots |
|
|
16
13
|
|
|
17
|
-
`react-scan`
|
|
18
|
-
under bare Node, and its programmatic API silently never fires
|
|
19
|
-
[
|
|
20
|
-
|
|
21
|
-
commit timings.
|
|
14
|
+
`react-scan` does not work: its documented entry point does not resolve
|
|
15
|
+
under bare Node, and its programmatic API silently never fires
|
|
16
|
+
([register](../ecosystem.md#silent-failures)). React's own `<Profiler>`
|
|
17
|
+
works unchanged and covers commit timings.
|
|
22
18
|
|
|
23
19
|
## React DevTools {#react-devtools}
|
|
24
20
|
|
|
25
21
|
**Out of the box — already wired behind an env var.** react-devtools-core@7.0.1.
|
|
26
22
|
|
|
27
|
-
`react-devtools-core` is the backend
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`src/DevToolsIntegration.js` and costs nothing when off.
|
|
23
|
+
`react-devtools-core` is the DevTools backend for a React tree outside a
|
|
24
|
+
browser page: it connects over WebSocket to the standalone DevTools UI and
|
|
25
|
+
streams the fiber tree, props, hooks and profiler data. It costs nothing
|
|
26
|
+
when off.
|
|
32
27
|
|
|
33
28
|
```sh
|
|
34
29
|
npm i -D react-devtools-core
|
|
@@ -40,39 +35,35 @@ npx react-devtools
|
|
|
40
35
|
REACT_X11_DEVTOOLS=1 npm run examples:dashboard
|
|
41
36
|
```
|
|
42
37
|
|
|
43
|
-
`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
a UI elsewhere, which is useful when the app runs on a headless box next to
|
|
49
|
-
the X server.
|
|
38
|
+
`REACT_X11_DEVTOOLS` is read on the first `render()`/`createRoot()`, which
|
|
39
|
+
installs the DevTools hook before the first commit, so every mounted root is
|
|
40
|
+
visible. `REACT_X11_DEVTOOLS_HOST` and `REACT_X11_DEVTOOLS_PORT` point the
|
|
41
|
+
backend at a UI elsewhere — useful when the app runs on a headless box next
|
|
42
|
+
to the X server.
|
|
50
43
|
|
|
51
|
-
Working
|
|
44
|
+
Working: the live component tree, props/hooks/state inspection, and
|
|
52
45
|
highlight-on-hover — hovering an element in the tree tints its rect in the
|
|
53
|
-
|
|
46
|
+
window. See [devtools](../devtools.md) for the full walkthrough.
|
|
54
47
|
|
|
55
|
-
- **Start the standalone UI first**, or wait for the backend's retry. The
|
|
56
|
-
side retries; the UI
|
|
48
|
+
- **Start the standalone UI first**, or wait for the backend's retry. The
|
|
49
|
+
app side retries; the UI does not attach to an already-running app unless
|
|
57
50
|
the hook was installed at startup, and `REACT_X11_DEVTOOLS` cannot be
|
|
58
51
|
turned on after the fact.
|
|
59
|
-
- The Profiler's commit-oriented views (flamegraph, ranked) work with the
|
|
60
|
-
reconciler. The **Timeline** view
|
|
61
|
-
which react-reconciler 0.33 does not expose
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
supported channel is `extraDevToolsConfig` on the host config.
|
|
52
|
+
- The Profiler's commit-oriented views (flamegraph, ranked) work with the
|
|
53
|
+
dev reconciler. The **Timeline** view does not — it needs
|
|
54
|
+
`injectProfilingHooks`, which react-reconciler 0.33 does not expose.
|
|
55
|
+
- DevTools cannot map a host node back to its fiber
|
|
56
|
+
(`findFiberByHostInstance` is never registered); the tree, props and
|
|
57
|
+
highlight do not need it.
|
|
58
|
+
|
|
59
|
+
Design notes: [design/ecosystem/dev-tooling.md](../../design/ecosystem/dev-tooling.md).
|
|
68
60
|
|
|
69
61
|
## react-refresh {#react-refresh}
|
|
70
62
|
|
|
71
63
|
**Supported — `react-x11 dev` is the command, and `react-x11/refresh` the
|
|
72
|
-
entry point under it.** Fast Refresh without a bundler: edit a component
|
|
73
|
-
|
|
74
|
-
connection, the window,
|
|
75
|
-
survive.
|
|
64
|
+
entry point under it.** Fast Refresh without a bundler: edit a component and
|
|
65
|
+
the mounted window updates in place with hook state intact — the
|
|
66
|
+
connection, the window, half-typed text in an input all survive.
|
|
76
67
|
|
|
77
68
|
```sh
|
|
78
69
|
npx react-x11 dev app.jsx
|
|
@@ -89,13 +80,13 @@ its work by rendering the app to a PNG in light and dark
|
|
|
89
80
|
|
|
90
81
|
### `react-x11 dev` {#react-x11-dev}
|
|
91
82
|
|
|
92
|
-
The package's `bin`,
|
|
93
|
-
|
|
83
|
+
The package's `bin`, with nothing to set up. What it does that the bare
|
|
84
|
+
loader leaves to the app:
|
|
94
85
|
|
|
95
86
|
- **It is the entry, and imports the file.** The file named on node's
|
|
96
|
-
command line is the one module
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
command line is the one module that never reloads (see below), so `dev`
|
|
88
|
+
imports yours instead, making it an ordinary hot module: an app in one
|
|
89
|
+
file reloads.
|
|
99
90
|
- **It mounts the file's default export.** A file that mounts a root of its
|
|
100
91
|
own while it is imported runs as it is — `dev` sees the root appear and
|
|
101
92
|
mounts nothing — and then the modules it imports reload while it does
|
|
@@ -104,16 +95,15 @@ each because the bare loader leaves it to the app:
|
|
|
104
95
|
`tsx` and `bun`; `examples/tasks.jsx` is written that way. One that mounts
|
|
105
96
|
itself unguarded _and_ exports only components is told, at start, that
|
|
106
97
|
every save re-runs its `createRoot` and opens another window.
|
|
107
|
-
- **It supplies a `<window>` while the tree renders none.**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
window
|
|
116
|
-
window does.
|
|
98
|
+
- **It supplies a `<window>` while the tree renders none.** A drawn
|
|
99
|
+
element at the root throws an error with `code: 'NOT_IN_WINDOW'` before
|
|
100
|
+
anything commits, and `dev` then mounts the tree inside a window titled
|
|
101
|
+
after the file, sized to its content with a 320×200 floor. Decided again
|
|
102
|
+
on every reload: render a `<window>` and `dev`'s goes (the component
|
|
103
|
+
remounts, so its state resets — `dev` says so); lose it and it comes
|
|
104
|
+
back. A tree that renders a `<window>` _beside_ content in none stays
|
|
105
|
+
wrapped, with a note saying which content needs a window. Closing the
|
|
106
|
+
window ends the process.
|
|
117
107
|
- **It says when a save will not reload.** A module reloads in place only
|
|
118
108
|
when everything it exports is a component by React Refresh's rule — a
|
|
119
109
|
capitalized name — so `export default () => …` and a module exporting a
|
|
@@ -121,11 +111,11 @@ each because the bare loader leaves it to the app:
|
|
|
121
111
|
says so, and why, when it starts, instead of leaving the first save to
|
|
122
112
|
find out.
|
|
123
113
|
- **It asks before it installs the toolchain.** The four packages below are
|
|
124
|
-
optional peer dependencies, so an app's users never download them
|
|
125
|
-
finds the ones
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
optional peer dependencies, so an app's users never download them. `dev`
|
|
115
|
+
finds the missing ones, pins them to react-x11's ranges, and on a yes runs
|
|
116
|
+
the project's package manager (from its lockfile, else the one that
|
|
117
|
+
launched `dev`, else npm) and starts again. With no terminal to ask on, it
|
|
118
|
+
prints the command and exits.
|
|
129
119
|
- **It connects to [React DevTools](#react-devtools)** whenever
|
|
130
120
|
`npx react-devtools` is running, started before the app or after it —
|
|
131
121
|
wherever `react-devtools-core` is installed. Where it is not, and a
|
|
@@ -134,48 +124,44 @@ each because the bare loader leaves it to the app:
|
|
|
134
124
|
- **It turns on [click-to-component](../click-to-component.md).**
|
|
135
125
|
Alt+Click (Option+Click on macOS) an element and its JSX line opens in
|
|
136
126
|
your editor. `REACT_X11_CLICK_TO_COMPONENT=0` turns it off.
|
|
137
|
-
- **It runs the project's react-x11.** A
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
install it.
|
|
127
|
+
- **It runs the project's react-x11.** A global or npx-cached `react-x11`
|
|
128
|
+
hands over to the copy the file imports (a second copy would be a root
|
|
129
|
+
nobody refreshes); a file that imports none is told to install it.
|
|
141
130
|
|
|
142
131
|
**Errors are shown in a window**, as well as printed:
|
|
143
132
|
|
|
144
|
-
- **A render error** takes the app's tree down
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
it back, and an app that throws again is back in the window.
|
|
133
|
+
- **A render error** takes the app's tree down and a window titled
|
|
134
|
+
`app.jsx — error` stands in for it: the message, the components it was
|
|
135
|
+
thrown under, the source around the line, and the stack's frames (React's,
|
|
136
|
+
Node's and react-x11's own counted rather than listed). Each save tries
|
|
137
|
+
the app again.
|
|
150
138
|
- **A save that does not compile** opens the same window beside the app,
|
|
151
139
|
which keeps running the module's last version with its state; it closes
|
|
152
140
|
once the file compiles, or when you close it.
|
|
153
|
-
-
|
|
141
|
+
- Clicking a line opens it in your editor, as
|
|
154
142
|
[click-to-component](../click-to-component.md) does, by
|
|
155
|
-
`REACT_X11_EDITOR` (`cursor` when
|
|
156
|
-
- A throw from an event handler leaves the app running
|
|
157
|
-
|
|
143
|
+
`REACT_X11_EDITOR` (`cursor` when unset).
|
|
144
|
+
- A throw from an event handler leaves the app running; it is only printed,
|
|
145
|
+
and dispatch continues.
|
|
158
146
|
|
|
159
|
-
Stack lines point at the source's own lines
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`
|
|
163
|
-
|
|
164
|
-
modules they import are hot. TypeScript is not supported yet.
|
|
147
|
+
Stack lines point at the source's own lines (`dev` turns source maps on).
|
|
148
|
+
Arguments after the file are the app's: `react-x11 dev app.jsx --size large`
|
|
149
|
+
gives the app the `process.argv` that `node app.jsx --size large` would.
|
|
150
|
+
`.jsx` files are compiled and hot; `.js` and `.mjs` run as Node runs them,
|
|
151
|
+
and the `.jsx` modules they import are hot. TypeScript is not supported.
|
|
165
152
|
|
|
166
153
|
### Under `--import` {#refresh-import}
|
|
167
154
|
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
A tool that hosts the loader itself — a workbench, a custom dev loop —
|
|
156
|
+
registers it with `--import`:
|
|
170
157
|
|
|
171
158
|
```sh
|
|
172
159
|
node --enable-source-maps --import react-x11/refresh/register main.jsx
|
|
173
160
|
```
|
|
174
161
|
|
|
175
|
-
The entry — the file named on the command line — is
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
as one file does not reload at all, and nothing says so. Keep the entry to
|
|
162
|
+
The entry — the file named on the command line — is **never reloaded**: a
|
|
163
|
+
module is watched once another module imports it, and nothing imports the
|
|
164
|
+
entry. So a one-file app does not reload at all, silently. Keep the entry to
|
|
179
165
|
mounting, and the components in modules it imports:
|
|
180
166
|
|
|
181
167
|
```jsx
|
|
@@ -188,9 +174,8 @@ const root = await ReactX11.createRoot();
|
|
|
188
174
|
root.render(<App />);
|
|
189
175
|
```
|
|
190
176
|
|
|
191
|
-
A save that does not compile is
|
|
192
|
-
|
|
193
|
-
that wants to show it subscribes to the loader:
|
|
177
|
+
A save that does not compile is only printed (the app keeps the module's
|
|
178
|
+
last version); a host that wants to show it subscribes to the loader:
|
|
194
179
|
|
|
195
180
|
```js
|
|
196
181
|
import { onTransform } from 'react-x11/refresh/loader';
|
|
@@ -216,13 +201,12 @@ await registerRefresh({
|
|
|
216
201
|
|
|
217
202
|
### How it reloads {#refresh-rules}
|
|
218
203
|
|
|
219
|
-
Both routes run the same loader.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
write; the one optional seam is the reload event:
|
|
204
|
+
Both routes run the same loader. A module whose exports are all components
|
|
205
|
+
is a **refresh boundary**: an edit to it (or to anything below it with no
|
|
206
|
+
boundary of its own) re-evaluates up to that module and re-renders the
|
|
207
|
+
edited components in place. A component whose hook signature changed
|
|
208
|
+
remounts alone. There are no accept handlers to write; the one optional
|
|
209
|
+
seam is the reload event:
|
|
226
210
|
|
|
227
211
|
```js
|
|
228
212
|
import { onReload } from 'react-x11/refresh';
|
|
@@ -232,33 +216,21 @@ onReload(({ urls, refreshed }) => {
|
|
|
232
216
|
});
|
|
233
217
|
```
|
|
234
218
|
|
|
235
|
-
Node 22.15 or newer is required
|
|
236
|
-
|
|
237
|
-
|
|
219
|
+
Node 22.15 or newer is required (for synchronous `module.registerHooks`),
|
|
220
|
+
plus four optional peer dependencies, which `dev` offers to install and
|
|
221
|
+
`--import` expects installed:
|
|
238
222
|
|
|
239
223
|
```sh
|
|
240
224
|
npm install --save-dev @babel/core @babel/plugin-transform-react-jsx react-refresh hot-module-replacement
|
|
241
225
|
```
|
|
242
226
|
|
|
243
|
-
|
|
244
|
-
`react-refresh/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
the renderer and `react-refresh/runtime` stay singletons.
|
|
251
|
-
|
|
252
|
-
Each layer puts lines of its own above a module — babel's the refresh
|
|
253
|
-
prelude, hot-module-replacement's a preamble — and each says so in its source
|
|
254
|
-
map and composes the map of the layer beneath it. So a stack through a hot
|
|
255
|
-
module points at the source's own line and column, which is what
|
|
256
|
-
click-to-component opens: run Node with `--enable-source-maps`, which
|
|
257
|
-
`dev` turns on itself. The composing half is hot-module-replacement
|
|
258
|
-
4.0.2's, which is why that is the floor; `test/refresh-stack.test.js` fails
|
|
259
|
-
on 4.0.1.
|
|
260
|
-
|
|
261
|
-
The constraints are enforced errors now, not folklore:
|
|
227
|
+
`node_modules` and react-x11's own sources are outside the hot boundary,
|
|
228
|
+
so React, the renderer and `react-refresh/runtime` stay singletons. A stack
|
|
229
|
+
through a hot module points at the source's own line and column (what
|
|
230
|
+
click-to-component opens) when Node runs with `--enable-source-maps`, which
|
|
231
|
+
`dev` turns on itself; this needs hot-module-replacement 4.0.2 or newer.
|
|
232
|
+
|
|
233
|
+
These constraints are enforced as errors:
|
|
262
234
|
|
|
263
235
|
- **A named import called at module top level inside a hot module is a
|
|
264
236
|
transform error.** Named imports become live bindings initialized in a
|
|
@@ -272,54 +244,48 @@ The constraints are enforced errors now, not folklore:
|
|
|
272
244
|
automatic runtime appends its `react/jsx-runtime` import to the last
|
|
273
245
|
import's line, which the line-oriented import rewrite breaks.
|
|
274
246
|
- **One statement per prelude entry.** A `prelude` entry holding two
|
|
275
|
-
statements or a newline throws
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
re-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
has for classes. Remember the named-import rule applies to it like
|
|
296
|
-
anything else: call it as `Host.registerElement(...)` through a
|
|
297
|
-
namespace or default import.
|
|
247
|
+
statements or a newline throws.
|
|
248
|
+
|
|
249
|
+
Not enforced:
|
|
250
|
+
|
|
251
|
+
- Under `--import`, an edit to the entry is dropped, whatever it exports;
|
|
252
|
+
editing it needs a restart. An edit elsewhere reaches the tree only if a
|
|
253
|
+
boundary sits on its import chain; a module whose importers reach the
|
|
254
|
+
entry without passing a component-only module is dropped the same way.
|
|
255
|
+
Neither says anything — `dev` reports the second, at start, for the file
|
|
256
|
+
it runs.
|
|
257
|
+
- **Module-scope side effects re-run on every reload** of the module and of
|
|
258
|
+
everything between it and its boundary. Keep hot modules' top level
|
|
259
|
+
idempotent, and keep identity that must survive (contexts, stores) outside
|
|
260
|
+
the hot graph — via the `ignore` seam or by extension. `registerElement`
|
|
261
|
+
at module scope is safe: during hot reload a duplicate registration
|
|
262
|
+
replaces silently instead of throwing
|
|
263
|
+
([re-registration policy](../extending.md#hot-reload)), and mounted nodes
|
|
264
|
+
keep the old prototype until they remount. The named-import rule applies:
|
|
265
|
+
call it as `Host.registerElement(...)` through a namespace or default
|
|
266
|
+
import.
|
|
298
267
|
- Fast Refresh needs the dev reconciler; `NODE_ENV=production` is a
|
|
299
|
-
registration error. There is no production hot path
|
|
268
|
+
registration error. There is no production hot path.
|
|
300
269
|
|
|
301
270
|
## `node --inspect` {#node-inspect}
|
|
302
271
|
|
|
303
272
|
**Out of the box.** Node built-in.
|
|
304
273
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
Everything that costs time in a react-x11 app is visible as ordinary JS
|
|
311
|
-
frames, which is why this is the recommended profiling path:
|
|
274
|
+
`node --inspect` exposes a debug port, and Chrome's `chrome://inspect` (or
|
|
275
|
+
VS Code's auto-attach) connects a full DevTools instance — sourcemap-aware
|
|
276
|
+
CPU profiler, heap snapshots, allocation timelines, live debugging.
|
|
277
|
+
Everything that costs time in a react-x11 app is ordinary JS frames, so this
|
|
278
|
+
is the recommended profiling path:
|
|
312
279
|
|
|
313
280
|
- **CPU profiles of commits.** Reconciler frames (`performUnitOfWork`,
|
|
314
281
|
`completeWork`), yoga layout (`calculateLayout`), style resolution and ntk
|
|
315
|
-
paint/flush show up in one call tree
|
|
316
|
-
|
|
317
|
-
- **Heap snapshots of retained nodes.** Every drawn element is a
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
interaction.
|
|
282
|
+
paint/flush show up in one call tree: "is it React, layout, or painting"
|
|
283
|
+
in one recording.
|
|
284
|
+
- **Heap snapshots of retained nodes.** Every drawn element is a node object
|
|
285
|
+
reachable from the ntk window's `_reactX11Node`, so leaked subtrees — a
|
|
286
|
+
`<popup>` that never unmounted, listeners held after close — are directly
|
|
287
|
+
countable in the constructor list. Two snapshots plus "Objects allocated
|
|
288
|
+
between…" isolates a leak per interaction.
|
|
323
289
|
|
|
324
290
|
```sh
|
|
325
291
|
# interactive: profile a running app from Chrome
|
|
@@ -334,23 +300,21 @@ kill -USR1 <pid> # inspector comes up on :9229, then attach and snapshot
|
|
|
334
300
|
```
|
|
335
301
|
|
|
336
302
|
- **Profile with `NODE_ENV=production` when chasing React costs.** The dev
|
|
337
|
-
reconciler's
|
|
338
|
-
|
|
339
|
-
- The inspector port is an arbitrary-code-execution door.
|
|
340
|
-
`127.0.0.1` (the default)
|
|
341
|
-
|
|
342
|
-
-
|
|
343
|
-
|
|
303
|
+
reconciler's bookkeeping (`runWithFiberInDEV`) inflates commit times and
|
|
304
|
+
clutters the tree; for layout and paint costs it matters less.
|
|
305
|
+
- The inspector port is an arbitrary-code-execution door. Keep it on
|
|
306
|
+
`127.0.0.1` (the default); for remote boxes, tunnel it with
|
|
307
|
+
`ssh -L 9229:localhost:9229`.
|
|
308
|
+
- The React DevTools Profiler names the _components_ per commit; the V8
|
|
309
|
+
profiler names the _functions_.
|
|
344
310
|
|
|
345
311
|
## `0x` {#0x}
|
|
346
312
|
|
|
347
313
|
**Out of the box.** 0x@6.0.0.
|
|
348
314
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
frames are directly searchable — `performUnitOfWork`/`completeWork`, `yoga`,
|
|
353
|
-
and your own components.
|
|
315
|
+
`0x -- node app.js` runs the process under the V8 sampling profiler and, on
|
|
316
|
+
exit, writes a self-contained interactive `flamegraph.html`, with
|
|
317
|
+
`performUnitOfWork`/`completeWork`, `yoga` and your components searchable.
|
|
354
318
|
|
|
355
319
|
```sh
|
|
356
320
|
# profile an example, write the output into a throwaway dir
|
|
@@ -364,29 +328,24 @@ npx 0x --output-dir /tmp/rx11-prof -- node --import tsx examples/dashboard.jsx
|
|
|
364
328
|
# "Merge" view -> collapses recursion for a cleaner ranking
|
|
365
329
|
```
|
|
366
330
|
|
|
367
|
-
|
|
368
|
-
so for flamegraphs it adds moving parts without adding signal. Its other
|
|
369
|
-
tools target server event-loop and async-I/O patterns that do not map onto a
|
|
370
|
-
GUI render loop, and the package has been dormant since early 2024 while 0x
|
|
371
|
-
keeps shipping.
|
|
331
|
+
clinic.js's flame command wraps 0x; use 0x directly.
|
|
372
332
|
|
|
373
|
-
- Sampling profiler: sub-millisecond handlers need a longer capture
|
|
374
|
-
|
|
333
|
+
- Sampling profiler: sub-millisecond handlers need a longer capture, so
|
|
334
|
+
drive the interaction in a loop.
|
|
375
335
|
- Profile with `NODE_ENV=production` when measuring React itself.
|
|
376
|
-
-
|
|
377
|
-
|
|
378
|
-
- 0x
|
|
379
|
-
|
|
380
|
-
|
|
336
|
+
- Under the tsx loader, frames from `.jsx` files show transformed function
|
|
337
|
+
names; component names usually survive.
|
|
338
|
+
- 0x is CPU-only and says nothing about _which commit_ or _why it
|
|
339
|
+
re-rendered_; pair it with the DevTools Profiler and why-did-you-render,
|
|
340
|
+
and use `node --inspect` for heap questions.
|
|
381
341
|
|
|
382
342
|
## why-did-you-render {#why-did-you-render}
|
|
383
343
|
|
|
384
344
|
**Out of the box.** @welldone-software/why-did-you-render@10.0.1.
|
|
385
345
|
|
|
386
|
-
Patches the `React` namespace so
|
|
387
|
-
re-renders — props or hook state
|
|
388
|
-
|
|
389
|
-
under a custom renderer.
|
|
346
|
+
Patches the `React` namespace so flagged components report _avoidable_
|
|
347
|
+
re-renders — props or hook state changed by reference but equal by value.
|
|
348
|
+
It hooks React, not `react-dom`, so it works under any renderer.
|
|
390
349
|
|
|
391
350
|
```jsx
|
|
392
351
|
import React from 'react';
|
|
@@ -411,26 +370,24 @@ const App = () => (
|
|
|
411
370
|
// terminal: "Label … props.user: different objects that are equal by value."
|
|
412
371
|
```
|
|
413
372
|
|
|
414
|
-
-
|
|
415
|
-
|
|
373
|
+
- A re-render caused by a new-but-deep-equal object prop is reported with
|
|
374
|
+
`diffType: 'deepEquals'` and the offending path; hook tracking
|
|
416
375
|
(`trackHooks`, on by default) reports `setState` with an equal-by-value
|
|
417
376
|
object the same way.
|
|
418
|
-
- The default notifier prints
|
|
419
|
-
|
|
420
|
-
`{displayName, reason: {propsDifferences, hookDifferences}}` object
|
|
421
|
-
|
|
377
|
+
- The default notifier prints `console.group` output in the terminal. A
|
|
378
|
+
custom `notifier` receives the structured
|
|
379
|
+
`{displayName, reason: {propsDifferences, hookDifferences}}` object — how
|
|
380
|
+
you assert on wasted renders in tests.
|
|
422
381
|
- Works with the automatic JSX runtime as compiled by tsx and esbuild; the
|
|
423
|
-
babel alias
|
|
424
|
-
- **Install it before components are defined and before the first render**
|
|
425
|
-
|
|
382
|
+
babel alias its docs describe for webpack is not needed.
|
|
383
|
+
- **Install it before components are defined and before the first render**
|
|
384
|
+
— it wraps `React.createElement` and the hook entry points on the
|
|
426
385
|
namespace.
|
|
427
|
-
- Dev-only
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
19, so in a scripted repro, wait a macrotask before asserting the
|
|
386
|
+
- Dev-only: React's production build drops the internals it reads. Plain
|
|
387
|
+
`node`/`tsx` gives the dev build.
|
|
388
|
+
- Updates scheduled outside an event handler flush on a delayed task in
|
|
389
|
+
React 19, so in a scripted repro wait a macrotask before asserting the
|
|
432
390
|
notification fired.
|
|
433
391
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
value to memoize.
|
|
392
|
+
DevTools tells you _that_ a component rendered; why-did-you-render tells you
|
|
393
|
+
the render was _wasted_ and which value to memoize.
|