@farm.js/react 0.1.0-beta.2 → 0.1.0-beta.4
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 +148 -42
- package/dist/compiler-runtime.cjs +2087 -134
- package/dist/compiler-runtime.cjs.map +1 -1
- package/dist/compiler-runtime.d.mts +94 -1
- package/dist/compiler-runtime.d.ts +94 -1
- package/dist/compiler-runtime.mjs +2087 -134
- package/dist/compiler-runtime.mjs.map +1 -1
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +5 -1
- package/dist/index.mjs.map +1 -1
- package/dist/list.cjs +18 -1
- package/dist/list.cjs.map +1 -1
- package/dist/list.mjs +18 -1
- package/dist/list.mjs.map +1 -1
- package/dist/vite.cjs +1321 -75
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +1321 -75
- package/dist/vite.mjs.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -23,7 +23,14 @@ export default defineConfig({
|
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
`compiler: true` uses inference: every application TSX/JSX component is considered, eligible
|
|
26
|
-
components are compiled, and everything else keeps the normal React path.
|
|
26
|
+
components are compiled with hybrid reactivity, and everything else keeps the normal React path.
|
|
27
|
+
|
|
28
|
+
Hybrid reactivity starts from the compiler's proven state-to-binding dependency lists, then marks
|
|
29
|
+
only multi-state short-circuit bindings for runtime read tracking. For
|
|
30
|
+
`enabled ? activeValue : inactiveValue`, an update to the inactive value does not schedule the
|
|
31
|
+
binding. The runtime uses direct subscription indexes rather than proxies or a component-wide
|
|
32
|
+
scan. Use `compiler: { reactivity: "static" }` to retain only the complete build-time dependency
|
|
33
|
+
lists for comparison or diagnosis.
|
|
27
34
|
|
|
28
35
|
For selective adoption, use annotation mode:
|
|
29
36
|
|
|
@@ -34,6 +41,7 @@ renderer: react({
|
|
|
34
41
|
mode: "annotation",
|
|
35
42
|
directive: "use compiler",
|
|
36
43
|
onUnsupported: "warn",
|
|
44
|
+
reactivity: "hybrid",
|
|
37
45
|
},
|
|
38
46
|
},
|
|
39
47
|
}),
|
|
@@ -56,29 +64,37 @@ The current compiler handles components that it can prove have:
|
|
|
56
64
|
- one host-element root and a statically known host-element tree around supported conditional,
|
|
57
65
|
keyed-list, and React component-island boundaries;
|
|
58
66
|
- an identifier props parameter or flat object destructuring with aliases and defaults;
|
|
67
|
+
- build-time dependency cells for referenced flat primitive props, with runtime identity guards;
|
|
59
68
|
- top-level `useState` declarations;
|
|
60
69
|
- optional compiler-safe derived `const` values declared after state and in source order;
|
|
61
70
|
- whitelisted `Boolean`, `Number`, `String`, and deterministic `Math` calculations;
|
|
62
71
|
- optional synchronous `const` or function-declaration handlers used by JSX events;
|
|
63
72
|
- state-driven text, attributes, per-property inline styles, and controlled form properties;
|
|
64
73
|
- host-rooted `condition && <element>` and `condition ? <element> : <element>` child blocks at
|
|
65
|
-
statically known locations, including
|
|
74
|
+
statically known locations, including direct ranges among static siblings and at the component
|
|
75
|
+
root;
|
|
66
76
|
- item-keyed `collection.map(...)` children and explicit `List` boundaries at statically known
|
|
67
77
|
container locations, including supported non-mutating collection pipelines;
|
|
68
78
|
- inline synchronous events inside otherwise eligible host-only keyed rows;
|
|
69
|
-
-
|
|
79
|
+
- multiple and recursively nested logical or ternary host branches inside non-interactive keyed
|
|
80
|
+
rows, including branches beside static host siblings;
|
|
70
81
|
- stable module-level child components with compiler-safe props;
|
|
71
82
|
- React-managed event handlers; and
|
|
72
83
|
- no refs, effects, or unsupported dynamic child structures.
|
|
73
84
|
|
|
74
|
-
The generated component preserves React ownership of initial placement,
|
|
75
|
-
hydration. Local state cells batch updates into a
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
The generated component preserves React ownership of initial placement, parent update commits,
|
|
86
|
+
identity-bearing props, events, SSR, and hydration. Local state cells batch updates into a
|
|
87
|
+
microtask; eligible flat destructured primitive props join the same dependency index after React
|
|
88
|
+
commits their parent update. The runtime skips unchanged binding output and patches only
|
|
89
|
+
compiler-known DOM targets. Objects, arrays, functions, symbols, React elements, `children`, and
|
|
90
|
+
identifier props retain the normal React prop-render path. Proven host containers
|
|
91
|
+
can transfer child ownership after mount for host-only conditional
|
|
92
|
+
branches and ranges, plus non-interactive host-only keyed rows and their recursively nested host
|
|
93
|
+
conditions. An interactive row uses a hybrid boundary instead: React retains its events,
|
|
94
|
+
conditional Fibers, and structural reconciliation, while Farm patches same-key bindings and
|
|
95
|
+
refreshes only changed React-owned row-local branches. React still creates or hydrates all initial
|
|
96
|
+
DOM. Anything outside those narrow contracts uses a small React-owned boundary or the complete
|
|
97
|
+
original component.
|
|
82
98
|
|
|
83
99
|
For a common dedicated conditional, no new component or annotation is required:
|
|
84
100
|
|
|
@@ -96,6 +112,26 @@ or hydration, a same-branch update patches those bindings without replacing the
|
|
|
96
112
|
change creates, removes, or replaces only the selected branch. No marker node is added to SSR output.
|
|
97
113
|
More complex conditional shapes keep the existing React-owned conditional boundary.
|
|
98
114
|
|
|
115
|
+
Eligible direct conditionals can also share a host container with static host siblings:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<article data-update={updates}>
|
|
119
|
+
<header>Status</header>
|
|
120
|
+
{loading && <p>Loading {updates}</p>}
|
|
121
|
+
<section>Stable content</section>
|
|
122
|
+
{enabled ? <strong>Enabled</strong> : <span>Disabled</span>}
|
|
123
|
+
<footer>Actions</footer>
|
|
124
|
+
</article>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
That `article` may be the component's exact returned root. Farm adopts the original container and
|
|
128
|
+
counts its direct static elements as anchors for the conditional ranges. Same-branch bindings patch
|
|
129
|
+
in place; branch changes insert, remove, or replace only their range. Multiple changed ranges use
|
|
130
|
+
one state snapshot and reconcile from right to left, including when adjacent ranges are empty. The
|
|
131
|
+
root and every unchanged static sibling keep their DOM identity, and no wrapper or marker is added.
|
|
132
|
+
Branch events, components, fragments, refs, SVG, spreads, nested dynamic blocks, or another
|
|
133
|
+
unsupported direct child keep the container on the React-owned path.
|
|
134
|
+
|
|
99
135
|
For a common keyed map, no new API is required:
|
|
100
136
|
|
|
101
137
|
```tsx
|
|
@@ -134,7 +170,7 @@ insert, removal, or reorder asks React to reconcile the rows, adopts the committ
|
|
|
134
170
|
reapplies current bindings, and then resumes direct same-key patches. This avoids creating eventful
|
|
135
171
|
DOM outside React's Fiber tree.
|
|
136
172
|
|
|
137
|
-
A keyed row may also
|
|
173
|
+
A non-interactive keyed row may also transfer its safe host conditions to the same keyed instance:
|
|
138
174
|
|
|
139
175
|
```tsx
|
|
140
176
|
<ul>
|
|
@@ -142,7 +178,16 @@ A keyed row may also contain dedicated conditional slots:
|
|
|
142
178
|
<li key={item.id}>
|
|
143
179
|
<span>{item.label}</span>
|
|
144
180
|
<div className="status-slot">
|
|
145
|
-
|
|
181
|
+
<i>State</i>
|
|
182
|
+
{item.done ? (
|
|
183
|
+
<article>
|
|
184
|
+
<strong>{item.label} complete</strong>
|
|
185
|
+
<div>{item.details && <small>{item.description}</small>}</div>
|
|
186
|
+
</article>
|
|
187
|
+
) : (
|
|
188
|
+
<span>In progress</span>
|
|
189
|
+
)}
|
|
190
|
+
<b>Prepared</b>
|
|
146
191
|
</div>
|
|
147
192
|
<section className="details-slot">{item.expanded && <p>{item.description}</p>}</section>
|
|
148
193
|
</li>
|
|
@@ -150,13 +195,53 @@ A keyed row may also contain dedicated conditional slots:
|
|
|
150
195
|
</ul>
|
|
151
196
|
```
|
|
152
197
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
removals
|
|
158
|
-
|
|
159
|
-
|
|
198
|
+
The compiler prepares the row descriptor, static sibling positions, branch factories, and
|
|
199
|
+
text/attribute/style bindings at build time. React creates or hydrates the initial list. After
|
|
200
|
+
mount, a same-key update patches the active branch in place or mounts, replaces, or removes only the
|
|
201
|
+
changed branch. Multiple and deeper host-only conditions share that row scope. Insertions and
|
|
202
|
+
removals create or clean one row scope, while reorders use the normal LIS pass. The component body
|
|
203
|
+
and map callback do not rerun, and no wrapper or hydration marker is added.
|
|
204
|
+
|
|
205
|
+
This compiler-owned tier requires a lowercase non-interactive host row. Hooks, custom components,
|
|
206
|
+
branch events, refs, SVG, fragments, spreads, dangerous HTML, controlled inputs inside a condition,
|
|
207
|
+
and unproven expressions stay on React. An otherwise eligible row with inline events continues to
|
|
208
|
+
use the hybrid boundary: React owns its conditional Fibers and structural commits, while Farm
|
|
209
|
+
compares per-key snapshots and refreshes only changed slots. Duplicate runtime keys or invalid
|
|
210
|
+
adopted DOM switch the complete list to React.
|
|
211
|
+
|
|
212
|
+
An eligible outer keyed row may also own nested keyed ranges:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<div>
|
|
216
|
+
{projects.map((project) => (
|
|
217
|
+
<section key={project.id}>
|
|
218
|
+
<h2>{project.name}</h2>
|
|
219
|
+
<ul>
|
|
220
|
+
<li>Tasks</li>
|
|
221
|
+
{project.tasks.map((task) => (
|
|
222
|
+
<li key={task.id}>{task.title}</li>
|
|
223
|
+
))}
|
|
224
|
+
<li>End</li>
|
|
225
|
+
</ul>
|
|
226
|
+
</section>
|
|
227
|
+
))}
|
|
228
|
+
</div>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Every outer key receives an isolated inner key table and LIS pass. Moving a project preserves its
|
|
232
|
+
task scope; reordering one task list touches only that project. Surviving project, task, and static
|
|
233
|
+
sibling elements keep their DOM identity, and removing an outer row cleans its inner scope. The
|
|
234
|
+
initial contract accepts non-interactive lowercase inner rows from `.map(...)` or inline `List`,
|
|
235
|
+
including multiple ranges separated by static host siblings. Inner events or forms, Hooks, custom
|
|
236
|
+
components, fragments, refs, SVG, spreads, dangerous HTML, and index keys keep the complete outer
|
|
237
|
+
row on React's fallback. Additional keyed levels that satisfy the same host-only contract are
|
|
238
|
+
analyzed recursively instead of using a fixed nesting limit.
|
|
239
|
+
|
|
240
|
+
For example, `boards → columns → cards → tags` becomes a tree of keyed scopes. Each stable parent
|
|
241
|
+
key owns its child key table and LIS pass. Moving a board carries its column, card, and tag scopes;
|
|
242
|
+
reordering cards reconciles only the selected column. Block IDs stay globally unique while runtime
|
|
243
|
+
instances are isolated by their complete parent-key path. Removing any parent cleans every
|
|
244
|
+
descendant subscription before its DOM leaves the document.
|
|
160
245
|
|
|
161
246
|
A keyed collection may use derived locals or an inline chain of `filter`, `slice`, `toSorted`, and
|
|
162
247
|
`toReversed`:
|
|
@@ -190,10 +275,9 @@ the TypeScript `lib` with ES2023 and target a runtime that supports them when us
|
|
|
190
275
|
|
|
191
276
|
React remains the fallback and compatibility boundary. An interactive map beside static children,
|
|
192
277
|
a non-host sibling between ranges, a row with a non-inline or async handler, a file input or dynamic
|
|
193
|
-
form control shape, a fragment, a ref, a custom component, or
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
events, lifecycle, and state.
|
|
278
|
+
form control shape, a fragment, a ref, a custom component, or any unsupported nested dynamic
|
|
279
|
+
structure uses the existing React-owned keyed boundary. The outer compiled component can still be
|
|
280
|
+
skipped, but React reconciles that list's rows and owns their events, lifecycle, and state.
|
|
197
281
|
|
|
198
282
|
Non-interactive host maps can share a nested or component-root container with static host siblings:
|
|
199
283
|
|
|
@@ -290,23 +374,41 @@ Generated callback refs give directly patched host elements stable private targe
|
|
|
290
374
|
React-owned component may therefore return `null`, a fragment, or multiple host nodes without
|
|
291
375
|
shifting an unrelated compiler binding. These refs do not add attributes to SSR output.
|
|
292
376
|
|
|
293
|
-
Conditional blocks have
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
377
|
+
Conditional blocks have three safe ownership levels. One or more proven host-only direct branches
|
|
378
|
+
may become compiler-owned ranges among static host siblings, including at the exact component root.
|
|
379
|
+
A dedicated container with exactly one conditional child uses the smaller compiler-owned branch
|
|
380
|
+
boundary. The more general React-owned conditional path accepts supported events, nested host
|
|
381
|
+
conditionals, keyed lists, and component islands. A compiler-owned host branch may now recursively
|
|
382
|
+
contain eligible host-only conditional ranges and non-interactive keyed ranges in nested host
|
|
383
|
+
containers. Each nested range receives its own dependency entry and updates without rerunning or
|
|
384
|
+
replacing the outer branch. When conditionals and keyed maps/`List` ranges are direct siblings in
|
|
385
|
+
one safe host container, the compiler emits one ordered mixed-range controller. It mounts or removes
|
|
386
|
+
conditional branches, applies independent LIS reconciliation to each keyed range, and preserves
|
|
387
|
+
static siblings in place. Stateful leaf text, attributes, `className`, and individual inline style
|
|
388
|
+
properties on those siblings are compiled into stable segment/sibling/path bindings. Their address
|
|
389
|
+
does not shift when a nearby condition mounts or a keyed range inserts, removes, or reorders rows,
|
|
390
|
+
so the same controller patches them from the current state snapshot without replacing the sibling
|
|
391
|
+
or rerunning the owner. The same mixed layout may recurse inside safe branches and keyed rows.
|
|
392
|
+
Keys on branches, custom components, fragments, refs, SVG, attribute spreads,
|
|
393
|
+
`dangerouslySetInnerHTML`, branch events, interactive rows, and unsupported mixed children keep
|
|
394
|
+
React ownership. An empty ternary branch may be `null` or `false`. The
|
|
395
|
+
inactive branch is described at build time but is never pre-mounted or cached. If a logical `&&`
|
|
396
|
+
evaluates to a number such as `0`,
|
|
397
|
+
or adopted DOM no longer matches its descriptor, the affected container remounts through React so
|
|
398
|
+
JavaScript, hydration, and React rendering semantics remain exact.
|
|
301
399
|
|
|
302
400
|
All component-level boundary types share one block graph and one ID sequence. A nested
|
|
303
401
|
binding records its nearest conditional parent. If one state flush affects both an outer
|
|
304
402
|
conditional and its descendants, the runtime refreshes the mounted outer boundary once and skips
|
|
305
|
-
the redundant descendant refreshes. React
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
403
|
+
the redundant descendant refreshes. React-owned inner boundaries unmount normally. Compiler-owned
|
|
404
|
+
host scopes explicitly remove every nested subscription before their outer DOM is removed, and a
|
|
405
|
+
later mount reads the latest compiler-cell values. Surviving keyed rows move with the existing LIS
|
|
406
|
+
algorithm. Duplicate runtime keys, invalid adoption, or an unsafe logical value transfer the outer
|
|
407
|
+
container back to React; descendant dependencies remain subscribed so that fallback stays live.
|
|
408
|
+
Host-only keyed rows have separate runtime instances per key. Row-local conditional and keyed-range
|
|
409
|
+
IDs are scoped to that instance and paired with its stable key, so two rows can use the same
|
|
410
|
+
build-time slot ID without sharing data or subscriptions. Unsupported row subtrees stay complete
|
|
411
|
+
React-owned keyed boundaries.
|
|
310
412
|
|
|
311
413
|
Unsupported components fall back to React by default. Use `onUnsupported: "warn"` for diagnostics
|
|
312
414
|
or `onUnsupported: "error"` while tightening an annotated migration.
|
|
@@ -334,8 +436,12 @@ Application and prototype calls, dynamic style objects, handlers outside JSX eve
|
|
|
334
436
|
computed, and rest props patterns, async handlers, unkeyed or index-keyed lists, chained maps,
|
|
335
437
|
unsupported conditional roots, effects, and more advanced hook support intentionally stay on React
|
|
336
438
|
in this release. Compiler-owned keyed rows support either one dedicated host-only map/`List` or
|
|
337
|
-
one or more non-interactive ranges in a nested or component-root host container.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
439
|
+
one or more non-interactive ranges in a nested or component-root host container. A dedicated
|
|
440
|
+
non-interactive row may also contain multiple recursive logical or ternary host branches beside
|
|
441
|
+
stateful static host siblings, plus recursively nested non-interactive keyed ranges scoped by every
|
|
442
|
+
stable parent key. Static sibling bindings support safe leaf text, attributes, `className`, and
|
|
443
|
+
individual style properties; lifecycle-sensitive shapes continue to fall back. Inline synchronous
|
|
444
|
+
row events continue to use the hybrid React-owned row path. Branch
|
|
445
|
+
or nested-row events, interactive ranges beside siblings, non-inline or async row handlers,
|
|
446
|
+
unsupported form shapes, custom components, fragments, refs, SVG, and duplicate runtime keys keep
|
|
447
|
+
or switch to React ownership.
|