@farm.js/react 0.1.0-beta.0 → 0.1.0-beta.2
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 +277 -11
- package/dist/compiler-runtime.cjs +1290 -17
- package/dist/compiler-runtime.cjs.map +1 -1
- package/dist/compiler-runtime.d.mts +132 -5
- package/dist/compiler-runtime.d.ts +132 -5
- package/dist/compiler-runtime.mjs +1290 -17
- package/dist/compiler-runtime.mjs.map +1 -1
- package/dist/index.cjs +24 -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 +24 -2
- package/dist/index.mjs.map +1 -1
- package/dist/list.cjs +27 -0
- package/dist/list.cjs.map +1 -0
- package/dist/list.d.mts +20 -0
- package/dist/list.d.ts +20 -0
- package/dist/list.mjs +21 -0
- package/dist/list.mjs.map +1 -0
- package/dist/vite.cjs +2332 -140
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +2332 -140
- package/dist/vite.mjs.map +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @farm.js/react
|
|
2
2
|
|
|
3
|
-
React renderer integration for FARMJS, including the experimental
|
|
3
|
+
React renderer integration for FARMJS, including the experimental AOT compiler.
|
|
4
4
|
|
|
5
5
|
React remains the default renderer and needs no package or configuration. Install this package only
|
|
6
6
|
when opting into the compiler experiment:
|
|
@@ -49,27 +49,293 @@ export function Counter() {
|
|
|
49
49
|
|
|
50
50
|
The directive is configurable and only has meaning in annotation mode.
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Current compiler contract
|
|
53
53
|
|
|
54
|
-
The
|
|
54
|
+
The current compiler handles components that it can prove have:
|
|
55
55
|
|
|
56
|
-
- one host-element root and a
|
|
56
|
+
- one host-element root and a statically known host-element tree around supported conditional,
|
|
57
|
+
keyed-list, and React component-island boundaries;
|
|
58
|
+
- an identifier props parameter or flat object destructuring with aliases and defaults;
|
|
57
59
|
- top-level `useState` declarations;
|
|
58
|
-
-
|
|
60
|
+
- optional compiler-safe derived `const` values declared after state and in source order;
|
|
61
|
+
- whitelisted `Boolean`, `Number`, `String`, and deterministic `Math` calculations;
|
|
62
|
+
- optional synchronous `const` or function-declaration handlers used by JSX events;
|
|
63
|
+
- state-driven text, attributes, per-property inline styles, and controlled form properties;
|
|
64
|
+
- host-rooted `condition && <element>` and `condition ? <element> : <element>` child blocks at
|
|
65
|
+
statically known locations, including supported nested boundaries;
|
|
66
|
+
- item-keyed `collection.map(...)` children and explicit `List` boundaries at statically known
|
|
67
|
+
container locations, including supported non-mutating collection pipelines;
|
|
68
|
+
- inline synchronous events inside otherwise eligible host-only keyed rows;
|
|
69
|
+
- row-local logical and ternary host branches inside stable keyed-row containers;
|
|
70
|
+
- stable module-level child components with compiler-safe props;
|
|
59
71
|
- React-managed event handlers; and
|
|
60
|
-
- no refs, effects,
|
|
72
|
+
- no refs, effects, or unsupported dynamic child structures.
|
|
61
73
|
|
|
62
|
-
The generated component preserves React ownership of placement, props, events, SSR, and
|
|
63
|
-
Local state cells batch updates into a microtask and patch only compiler-known DOM
|
|
64
|
-
|
|
74
|
+
The generated component preserves React ownership of initial placement, props, events, SSR, and
|
|
75
|
+
hydration. Local state cells batch updates into a microtask and patch only compiler-known DOM
|
|
76
|
+
targets. Proven, dedicated host containers can transfer child ownership after mount for host-only
|
|
77
|
+
conditional branches and non-interactive host-only keyed rows. An interactive row or a row with
|
|
78
|
+
local conditional slots uses a hybrid boundary instead: React retains its events, conditional
|
|
79
|
+
Fibers, and structural reconciliation, while Farm patches same-key bindings and refreshes only
|
|
80
|
+
changed row-local branches. React still creates or hydrates all initial DOM. Anything outside those
|
|
81
|
+
narrow contracts uses a small React-owned boundary or the complete original component.
|
|
82
|
+
|
|
83
|
+
For a common dedicated conditional, no new component or annotation is required:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<div className="status-slot">
|
|
87
|
+
{enabled ? <strong>Enabled {count}</strong> : <span>Disabled {count}</span>}
|
|
88
|
+
</div>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
When the conditional is the container's only meaningful child, the container has only static
|
|
92
|
+
properties, and each branch is a statically known host tree without events, refs, custom components,
|
|
93
|
+
or other dynamic structures, the compiler emits
|
|
94
|
+
both host descriptors and their exact text/attribute/style bindings. After the initial React render
|
|
95
|
+
or hydration, a same-branch update patches those bindings without replacing the element. A condition
|
|
96
|
+
change creates, removes, or replaces only the selected branch. No marker node is added to SSR output.
|
|
97
|
+
More complex conditional shapes keep the existing React-owned conditional boundary.
|
|
98
|
+
|
|
99
|
+
For a common keyed map, no new API is required:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<ul>
|
|
103
|
+
{items.map((item) => (
|
|
104
|
+
<li key={item.id}>{item.label}</li>
|
|
105
|
+
))}
|
|
106
|
+
</ul>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
When this map is the only meaningful child of a dedicated host container and its rows are host-only,
|
|
110
|
+
the compiler prepares a row descriptor, a key reader, and exact text/attribute/style bindings at
|
|
111
|
+
build time. After React performs the initial render or hydration, Farm adopts those row elements.
|
|
112
|
+
Later list updates patch surviving rows by key, create or remove only the changed rows, and use a
|
|
113
|
+
longest increasing subsequence (LIS) to minimize DOM moves during a reorder. The outer user
|
|
114
|
+
component and the list callback do not rerun for those compiler-cell updates.
|
|
115
|
+
|
|
116
|
+
An otherwise eligible host row may also contain an inline synchronous React event:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<ul>
|
|
120
|
+
{items.map((item, index) => (
|
|
121
|
+
<li key={item.id}>
|
|
122
|
+
<span>{item.label}</span>
|
|
123
|
+
<button onClick={() => select(item.id, index)}>Select</button>
|
|
124
|
+
</li>
|
|
125
|
+
))}
|
|
126
|
+
</ul>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The compiler leaves the event prop on the React element and emits no native listener. Its stable
|
|
130
|
+
keyed proxy looks up the latest item and index when React dispatches the event, so a same-key item
|
|
131
|
+
replacement cannot leave a stale row closure. If the key order is unchanged, Farm patches the
|
|
132
|
+
prepared text, attribute, and style bindings without rerunning the component or map callback. An
|
|
133
|
+
insert, removal, or reorder asks React to reconcile the rows, adopts the committed host elements,
|
|
134
|
+
reapplies current bindings, and then resumes direct same-key patches. This avoids creating eventful
|
|
135
|
+
DOM outside React's Fiber tree.
|
|
136
|
+
|
|
137
|
+
A keyed row may also contain dedicated conditional slots:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
<ul>
|
|
141
|
+
{items.map((item) => (
|
|
142
|
+
<li key={item.id}>
|
|
143
|
+
<span>{item.label}</span>
|
|
144
|
+
<div className="status-slot">
|
|
145
|
+
{item.done ? <strong>{item.label} complete</strong> : <span>In progress</span>}
|
|
146
|
+
</div>
|
|
147
|
+
<section className="details-slot">{item.expanded && <p>{item.description}</p>}</section>
|
|
148
|
+
</li>
|
|
149
|
+
))}
|
|
150
|
+
</ul>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Each conditional must be the only meaningful child of its persistent lowercase host container.
|
|
154
|
+
The compiler records its test and branch bindings per row key. A same-key collection update compares
|
|
155
|
+
those prepared snapshots and asks React to render only the conditional boundaries whose selected
|
|
156
|
+
branch or branch values changed. Other conditional rows and the outer map stay untouched. Inserts,
|
|
157
|
+
removals, and reorders remain React structural commits for these rows, after which Farm re-adopts
|
|
158
|
+
the host skeleton and resumes row-local updates. This keeps empty branches, SSR, hydration,
|
|
159
|
+
recoverable hydration errors, error boundaries, and Fast Refresh inside React's ownership.
|
|
160
|
+
|
|
161
|
+
A keyed collection may use derived locals or an inline chain of `filter`, `slice`, `toSorted`, and
|
|
162
|
+
`toReversed`:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
const visible = items.filter((item) => item.visible && item.rank >= minimumRank);
|
|
166
|
+
const page = visible
|
|
167
|
+
.toSorted((left, right) => left.rank - right.rank)
|
|
168
|
+
.slice(offset, offset + pageSize)
|
|
169
|
+
.toReversed();
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<ul>
|
|
173
|
+
{page.map((item) => (
|
|
174
|
+
<li key={item.id}>{item.label}</li>
|
|
175
|
+
))}
|
|
176
|
+
</ul>
|
|
177
|
+
);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The compiler records dependencies used by the source collection, inline predicate, inline
|
|
181
|
+
comparator, and slice arguments. It reruns the pipeline only when one of those compiler cells
|
|
182
|
+
changes, then gives the result to the existing keyed-row and LIS runtime. The required filtering or
|
|
183
|
+
sorting still runs; unrelated local updates avoid that work and do not rerun the outer component.
|
|
184
|
+
Callbacks must be synchronous, inline, and contain one compiler-safe returned expression. Mutating
|
|
185
|
+
`sort`, `reverse`, and `splice`, external or async callbacks, Hooks, assignments, spread arguments,
|
|
186
|
+
and unproven calls use the original React fallback.
|
|
187
|
+
|
|
188
|
+
`toSorted` and `toReversed` are emitted as standard runtime calls rather than polyfilled. Configure
|
|
189
|
+
the TypeScript `lib` with ES2023 and target a runtime that supports them when using those methods.
|
|
190
|
+
|
|
191
|
+
React remains the fallback and compatibility boundary. An interactive map beside static children,
|
|
192
|
+
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 a conditional mixed directly beside
|
|
194
|
+
other children in the same host slot uses the existing React-owned keyed boundary. The outer
|
|
195
|
+
compiled component can still be skipped, but React reconciles that list's rows and owns their
|
|
196
|
+
events, lifecycle, and state.
|
|
197
|
+
|
|
198
|
+
Non-interactive host maps can share a nested or component-root container with static host siblings:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
<ul>
|
|
202
|
+
<li>Primary</li>
|
|
203
|
+
{primary.map((item) => (
|
|
204
|
+
<li key={item.id}>{item.label}</li>
|
|
205
|
+
))}
|
|
206
|
+
<li>Secondary</li>
|
|
207
|
+
{secondary.map((item) => (
|
|
208
|
+
<li key={item.id}>{item.label}</li>
|
|
209
|
+
))}
|
|
210
|
+
<li>{primary.length + secondary.length} total</li>
|
|
211
|
+
</ul>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The compiler emits one range block for that host container. React renders and hydrates the original
|
|
215
|
+
markup. After mount, Farm records the static element segments and reconciles each keyed range with
|
|
216
|
+
its own row table and LIS pass. It does not add wrappers or hydration markers. When the container is
|
|
217
|
+
the component root, the block forwards that exact host element to the outer binding runtime, so
|
|
218
|
+
root attributes and styles keep updating normally. A root with one eligible map or `List` also
|
|
219
|
+
works without static siblings. Direct children must be host elements or eligible ranges; events,
|
|
220
|
+
controlled rows, row conditionals, components, fragments, and nested dynamic structures keep the
|
|
221
|
+
React boundary. Duplicate keys, an adoption shape mismatch, or a parent-driven static markup change
|
|
222
|
+
remounts the complete container through React.
|
|
223
|
+
|
|
224
|
+
Controlled host fields can use the hybrid keyed-row path:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
<ul>
|
|
228
|
+
{items.map((item) => (
|
|
229
|
+
<li key={item.id}>
|
|
230
|
+
<input
|
|
231
|
+
value={item.label}
|
|
232
|
+
onChange={(event) =>
|
|
233
|
+
setItems((current) =>
|
|
234
|
+
current.map((row) =>
|
|
235
|
+
row.id === item.id ? { ...row, label: event.currentTarget.value } : row,
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
/>
|
|
240
|
+
<input type="checkbox" checked={item.done} onChange={/* keyed update */} />
|
|
241
|
+
</li>
|
|
242
|
+
))}
|
|
243
|
+
</ul>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
React owns the controls, events, hydration, and every structural commit. Farm patches `value`,
|
|
247
|
+
`checked`, selected options, and dependent row bindings by stable key. It preserves focused text
|
|
248
|
+
selection and leaves composition events on React. When `event.currentTarget` is referenced inside
|
|
249
|
+
a queued functional compiler-state updater, generated code snapshots the referenced property while
|
|
250
|
+
the handler is active. Static input types and static option attributes are required. File inputs,
|
|
251
|
+
dynamic types or options, `contentEditable`, refs, custom controls, textarea children beside
|
|
252
|
+
`value`, and async or non-inline handlers keep the React fallback.
|
|
253
|
+
|
|
254
|
+
For custom rows or an explicit key selector, use the public component:
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
import { List } from "@farm.js/react/list";
|
|
258
|
+
|
|
259
|
+
<div>
|
|
260
|
+
<List each={items} by={(item) => item.id}>
|
|
261
|
+
{(item) => <StatefulRow item={item} />}
|
|
262
|
+
</List>
|
|
263
|
+
</div>;
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`List` also works with the compiler disabled. `each` accepts an iterable, `null`, or `undefined`;
|
|
267
|
+
`by` supplies the React key; and the child function returns one React element. An inline host-only
|
|
268
|
+
row inside a dedicated container can use compiler-owned keyed rows. A custom row such as
|
|
269
|
+
`StatefulRow` remains a React-owned keyed boundary, which is necessary for its Hooks, lifecycle,
|
|
270
|
+
and Fiber state. Put Hooks inside the row component, not directly inside the iteration
|
|
271
|
+
callback. The optimized explicit shape also requires inline `by` and child functions, a safe
|
|
272
|
+
`each` expression, an item-derived key, and a statically known location. Other shapes keep normal
|
|
273
|
+
React behavior.
|
|
274
|
+
|
|
275
|
+
A normal child component can become an automatic React-owned island:
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
<Header title="Dashboard" />
|
|
279
|
+
<Chart value={count} />
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
If `count` changes, the compiler leaves the outer component and `Header` alone and asks React to
|
|
283
|
+
render only the `Chart` boundary. `Chart` remains ordinary React and may use Hooks, context, local
|
|
284
|
+
state, effects, lifecycle, and error boundaries. The first contract accepts stable imported or
|
|
285
|
+
module-level identifiers with explicit compiler-safe props. Component children, spreads, `ref`,
|
|
286
|
+
`key`, member-expression or prop-selected component types, and identity-bearing inline prop values
|
|
287
|
+
fall back to the complete React component.
|
|
288
|
+
|
|
289
|
+
Generated callback refs give directly patched host elements stable private target identities. A
|
|
290
|
+
React-owned component may therefore return `null`, a fragment, or multiple host nodes without
|
|
291
|
+
shifting an unrelated compiler binding. These refs do not add attributes to SSR output.
|
|
292
|
+
|
|
293
|
+
Conditional blocks have two safe ownership levels. A dedicated container with exactly one proven
|
|
294
|
+
host-only conditional child can use compiler-owned branch instances. Events, keys, custom
|
|
295
|
+
components, fragments, refs, SVG, attribute spreads, `dangerouslySetInnerHTML`, nested dynamic
|
|
296
|
+
blocks, and static siblings in that container keep React ownership. The more general React-owned
|
|
297
|
+
conditional path still accepts supported nested conditionals, keyed lists, and component islands.
|
|
298
|
+
An empty ternary branch may be `null` or `false`. The inactive branch is described at build time but
|
|
299
|
+
is never pre-mounted or cached. If a logical `&&` evaluates to a number such as `0`, the runtime also
|
|
300
|
+
falls back to React so JavaScript and React rendering semantics remain exact.
|
|
301
|
+
|
|
302
|
+
All component-level boundary types share one block graph and one ID sequence. A nested
|
|
303
|
+
binding records its nearest conditional parent. If one state flush affects both an outer
|
|
304
|
+
conditional and its descendants, the runtime refreshes the mounted outer boundary once and skips
|
|
305
|
+
the redundant descendant refreshes. React unmounts inner boundaries normally, their subscriptions
|
|
306
|
+
are removed, and a later remount reads the latest compiler-cell values. Host-only keyed rows have
|
|
307
|
+
separate runtime instances per key. Row-local conditional IDs are scoped to that instance and
|
|
308
|
+
paired with its stable key, so two rows can use the same build-time slot ID without sharing data or
|
|
309
|
+
subscriptions. Unsupported row subtrees stay complete React-owned keyed boundaries.
|
|
65
310
|
|
|
66
311
|
Unsupported components fall back to React by default. Use `onUnsupported: "warn"` for diagnostics
|
|
67
312
|
or `onUnsupported: "error"` while tightening an annotated migration.
|
|
68
313
|
|
|
314
|
+
Enable a deterministic production-build coverage report to see which components compiled and why
|
|
315
|
+
the rest stayed on React:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
compiler: {
|
|
319
|
+
report: true,
|
|
320
|
+
// reportFile: "artifacts/react-compiler.json",
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The default path is `.farm/react-compiler.json`. The report covers the production browser graph and
|
|
325
|
+
contains project-relative module paths, compiled component names, fallback details, and fallback
|
|
326
|
+
reasons aggregated by count. A custom project-relative `reportFile` also enables reporting.
|
|
327
|
+
|
|
69
328
|
The runtime test compares the same counter interaction on both paths: ordinary React performs a
|
|
70
329
|
second component render and commit, while the compiled component remains at one render and one
|
|
71
330
|
commit and updates its two bindings directly. This is a deterministic structural performance
|
|
72
331
|
assertion; it is not presented as a cross-machine timing benchmark.
|
|
73
332
|
|
|
74
|
-
|
|
75
|
-
|
|
333
|
+
Application and prototype calls, dynamic style objects, handlers outside JSX events, nested,
|
|
334
|
+
computed, and rest props patterns, async handlers, unkeyed or index-keyed lists, chained maps,
|
|
335
|
+
unsupported conditional roots, effects, and more advanced hook support intentionally stay on React
|
|
336
|
+
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. Inline synchronous
|
|
338
|
+
events and dedicated row-local host conditional slots can use the single-list hybrid path, but
|
|
339
|
+
branch events, interactive ranges beside siblings, nested dynamic blocks, conditionals mixed with
|
|
340
|
+
siblings in one slot, non-inline or async row handlers, unsupported form shapes, custom components,
|
|
341
|
+
fragments, refs, SVG, and duplicate runtime keys keep or switch to React ownership.
|