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.
Files changed (112) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1091 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/layouts.js +1 -1
  76. package/src/nodes/animation.js +1 -1
  77. package/src/nodes/invalidate.js +1 -1
  78. package/src/nodes/layouthost.js +1 -1
  79. package/src/nodes/video.js +19 -8
  80. package/src/nodes/window/droptarget.js +1 -1
  81. package/src/pacing.js +1 -1
  82. package/src/refresh/index.js +2 -1
  83. package/src/types/video.d.ts +5 -2
  84. package/src/videobackend.js +28 -0
  85. package/src/videoframes.js +29 -16
  86. package/src/wayland/app.js +27 -0
  87. package/src/wayland/video.js +376 -0
  88. package/src/win32/a11y.js +1 -1
  89. package/src/win32/app.js +2 -2
  90. package/src/win32/fonts.js +1 -1
  91. package/src/win32/glarea.js +4 -4
  92. package/src/win32/overlay.js +1 -1
  93. package/src/win32/panewindow.js +3 -3
  94. package/src/win32/shell.js +1 -1
  95. package/src/win32/window.js +2 -2
  96. package/src/windowid.js +1 -1
  97. package/src/xvideo.js +189 -0
  98. package/docs/architecture/accessibility.md +0 -470
  99. package/docs/architecture/animation.md +0 -707
  100. package/docs/architecture/container-queries.md +0 -525
  101. package/docs/architecture/custom-layout.md +0 -334
  102. package/docs/architecture/drag-and-drop.md +0 -941
  103. package/docs/architecture/element-layer-contents.md +0 -622
  104. package/docs/architecture/frame-pacing.md +0 -255
  105. package/docs/architecture/grid-layout.md +0 -358
  106. package/docs/architecture/protocol-efficiency.md +0 -357
  107. package/docs/architecture/video.md +0 -1149
  108. package/docs/architecture/windowed-regions.md +0 -854
  109. package/docs/glx.md +0 -137
  110. package/docs/wayland.md +0 -545
  111. package/docs/windows-embedding.md +0 -271
  112. package/docs/windows-gl.md +0 -221
@@ -1,11 +1,8 @@
1
1
  # Developer tooling
2
2
 
3
- This is the payoff of a pure-JS stack: a react-x11 app is an ordinary Node
4
- process, so the whole V8 and React tooling story applies unmodified. Nothing
5
- on this page needs an adapter.
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` is not on this page: its documented entry point does not resolve
18
- under bare Node, and its programmatic API silently never fires. See the
19
- [negative-results register](../ecosystem.md#silent-failures). React's own
20
- `<Profiler>` works unchanged under react-x11 and covers the same ground for
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 half of React DevTools, made for exactly
28
- this situation: a React tree running outside a browser page. It connects over
29
- WebSocket to the standalone DevTools UI and streams the fiber tree, props,
30
- hooks and profiler data. The integration lives in
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
- `src/Reconciler.js` checks `REACT_X11_DEVTOOLS` when you first call
44
- `render()`/`createRoot()`, and installs the DevTools global hook _before the
45
- first commit_ — that ordering is what makes mounted roots visible, and it
46
- holds because a commit can only follow a root.
47
- `REACT_X11_DEVTOOLS_HOST` and `REACT_X11_DEVTOOLS_PORT` point the backend at
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 today: the live component tree, props/hooks/state inspection, and
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
- X11 window. See [devtools](../devtools.md) for the full walkthrough.
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 app
56
- side retries; the UI side does not attach to an already-running app unless
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 dev
60
- reconciler. The **Timeline** view cannot — it needs `injectProfilingHooks`,
61
- which react-reconciler 0.33 does not expose at all.
62
- - `injectIntoDevTools` takes zero arguments in react-reconciler 0.33, so the
63
- config object passed to it is silently discarded and
64
- `findFiberByHostInstance` is never registered — DevTools cannot map a host
65
- node back to its fiber. The tree, props and highlight features do not need
66
- it. Do not add options to that call expecting them to do anything; the
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
- and the mounted window updates in place with hook state intact. The X
74
- connection, the window, the task list, half-typed text in an input — all
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`, and the route with nothing to set up. What it does,
93
- each because the bare loader leaves it to the app:
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 hot-module-replacement never watches (see
97
- below), so here the file is imported instead, which makes it an ordinary
98
- hot module: an app in one file reloads.
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.** The tree is
108
- mounted as it is; a drawn element at the root throws an error with
109
- `code: 'NOT_IN_WINDOW'` before anything commits, and on that `dev` mounts
110
- it again inside a window titled after the file, sized to its content with
111
- a 320×200 floor. Decided again on every reload: render a `<window>` and
112
- `dev`'s goes (the component remounts, so its state resets — `dev` says
113
- so), lose it and it comes back. A tree that renders a `<window>` _beside_
114
- content in none stays wrapped, with a note saying which content needs a
115
- window. Closing the window ends the process, as closing an app's primary
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; `dev`
125
- finds the ones missing, pins them to the ranges react-x11 is written
126
- against, and runs the project's package manager — from its lockfile, else
127
- the one that launched `dev`, else npm — when you say yes, then starts
128
- again. With no terminal to ask on it prints the command and exits.
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 second copy of the renderer is a
138
- root nobody refreshes, so a global or npx-cached `react-x11` hands over to
139
- the copy the file imports, and a file that imports none is told to
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, as it would anywhere, and a
145
- window titled `app.jsx — error` stands in for it. It shows the message, the
146
- components it was thrown under, the source around the line, and the
147
- stack's frames — React's, Node's and react-x11's own counted rather than
148
- listed. The next save tries the app again; the save that fixes it brings
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
- - A line in it opens in your editor on a click, the way
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 it is unset).
156
- - A throw from an event handler leaves the app running, so it is only
157
- printed, and dispatch continues.
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, through every loader layer's
160
- source map; `dev` turns source maps on for you. Arguments after the file
161
- are the app's: `react-x11 dev app.jsx --size large` hands the app the
162
- `process.argv` that `node app.jsx --size large` would. `.jsx` files are
163
- compiled and hot; `.js` and `.mjs` run as Node runs them, and the `.jsx`
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
- `dev` hosts the loader in its own process; a tool that hosts it itself —
169
- a workbench, a custom dev loop — registers it with `--import`:
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 the one module that is
176
- **never reloaded**: hot-module-replacement starts watching a module when
177
- another module imports it, and nothing imports the entry. So an app written
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 otherwise only printed — hot-module-replacement
192
- catches the error, and the app keeps the module's last version — so a host
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. It instruments every `.jsx`
220
- module and decides per module whether it is a **refresh boundary**: a
221
- module whose exports are all components self-accepts, so an edit to it (or
222
- to anything below it with no boundary of its own) re-evaluates up to that
223
- module and re-renders the edited components in place. A component whose
224
- hook signature changed remounts alone. There are no accept handlers to
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, for synchronous `module.registerHooks`,
236
- and the toolchain rides four optional peer dependencies, which `dev` offers
237
- to install and `--import` expects installed:
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
- Under the hood two loader layers stack: babel (classic JSX transform plus
244
- `react-refresh/babel`) instruments the components, and above it
245
- `hot-module-replacement` rewrites static imports into live bindings and
246
- wires `import.meta.hot`. The runtime half patches the DevTools global hook
247
- before the renderer registers with it — ordering the loader guarantees by
248
- injecting the runtime import into every hot module's prelude. The hot
249
- boundary excludes `node_modules` and react-x11's own sources, so React,
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, because the import rewrite replaces a
276
- whole import statement's span on its line.
277
-
278
- What is _not_ enforced, so it is not rediscovered as a bug:
279
-
280
- - Under `--import`, an edit to the entry is dropped, whatever the entry
281
- exports: it is never watched (above), so editing it needs a restart. An
282
- edit anywhere else reaches the tree only if a boundary sits on its import
283
- chain; a module whose importers lead to the entry without passing a
284
- component-only module is dropped the same way. Neither says anything —
285
- `dev` says the second, at start, for the file it runs.
286
- - **Module-scope side effects re-run on every reload** of the module and
287
- of everything between it and its boundary. Keep hot modules'
288
- top level idempotent, and keep identity that must survive (contexts,
289
- stores) in modules outside the hot graph — via the `ignore` seam or by
290
- extension. `registerElement` at module scope is safe: once the first hot
291
- re-import begins, a duplicate registration replaces silently instead of
292
- throwing (the
293
- [re-registration policy](../extending.md#hot-reload)) — mounted nodes keep the old
294
- prototype until they remount, the same staleness contract Fast Refresh
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, by design.
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
- Node's V8 inspector protocol: `node --inspect` exposes a debug port, and
306
- Chrome's `chrome://inspect` (or VS Code's auto-attach) connects a full
307
- DevTools instance — sourcemap-aware CPU profiler, heap snapshots, allocation
308
- timelines, live debugging.
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. That answers "is it React, layout, or
316
- painting" in a single recording.
317
- - **Heap snapshots of retained nodes.** Every drawn element is a retained
318
- node object reachable from the ntk window's `_reactX11Node`. Snapshot,
319
- filter the constructor list, and leaked subtrees — a `<popup>` that never
320
- unmounted, listeners held after close — are directly countable. Two
321
- snapshots plus "Objects allocated between…" isolates a leak per
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 stack-frame bookkeeping (`runWithFiberInDEV`) inflates commit
338
- times and clutters the tree. For layout and paint costs it matters less.
339
- - The inspector port is an arbitrary-code-execution door. Bind it to
340
- `127.0.0.1` (the default) and never expose it on a network; for remote
341
- boxes, tunnel it with `ssh -L 9229:localhost:9229`.
342
- - This and the React DevTools Profiler are complementary: DevTools names the
343
- _components_ per commit, the V8 profiler names the _functions_.
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
- A single-command flamegraph generator: `0x -- node app.js` runs the process
350
- under the V8 sampling profiler and, on exit, writes a self-contained
351
- interactive `flamegraph.html`. No integration at all, and the interesting
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
- **Why 0x and not clinic.js:** Clinic's flame command is a wrapper around 0x,
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 to show
374
- up, so drive the interaction in a loop.
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
- - Running through the tsx loader is fine, though frames from `.jsx` files
377
- show transformed function names. Component names usually survive.
378
- - 0x answers "where does CPU time go", and says nothing about _which commit_
379
- or _why it re-rendered_. Pair it with the DevTools Profiler and
380
- why-did-you-render. For heap questions use `node --inspect`; 0x is CPU-only.
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 that components you flag report _avoidable_
387
- re-renders — props or hook state that changed by reference but are equal by
388
- value. It hooks React itself, not `react-dom`, which is exactly why it works
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
- - Props tracking reports a re-render caused by a new-but-deep-equal object
415
- prop with `diffType: 'deepEquals'` and the offending path. Hook tracking
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 readable `console.group` output in a terminal —
419
- no browser console needed. A custom `notifier` receives the structured
420
- `{displayName, reason: {propsDifferences, hookDifferences}}` object, which
421
- is also how you assert on wasted renders in tests.
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 the docs describe for webpack setups is not needed.
424
- - **Install it before components are defined and before the first render** —
425
- the patch wraps `React.createElement` and the hook entry points on the
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 by design. With `NODE_ENV=production` React's prod build drops the
428
- internals it reads; under plain `node`/`tsx` you get the dev build, which
429
- is the right mode for it anyway.
430
- - Updates scheduled outside an event handler flush on a delayed task in React
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
- This complements React DevTools: DevTools tells you _that_ a component
435
- rendered; why-did-you-render tells you the render was _wasted_ and which
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.