@barefootjs/cli 0.35.4 → 0.35.6
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/dist/docs/core/README.mdx +1 -1
- package/dist/docs/core/adapters/hono-adapter.md +156 -1
- package/dist/docs/core/adapters.md +14 -10
- package/dist/docs/core/advanced/compiler-internals.md +19 -4
- package/dist/docs/core/advanced/error-codes.md +18 -61
- package/dist/docs/core/components/props-type-safety.md +3 -2
- package/dist/docs/core/core-concepts/backend-freedom.md +16 -14
- package/dist/docs/core/quick-start.mdx +1 -1
- package/dist/docs/core/reactivity/props-reactivity.md +69 -31
- package/dist/index.js +6171 -5816
- package/package.json +5 -5
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
- [IR Schema Reference](./advanced/ir-schema.md) — Node types, metadata, hydration markers
|
|
62
62
|
- [Compiler Internals](./advanced/compiler-internals.md) — Pipeline phases, reactivity analysis, code generation
|
|
63
|
-
- [Error Codes Reference](./advanced/error-codes.md) — All
|
|
63
|
+
- [Error Codes Reference](./advanced/error-codes.md) — All BF-prefixed errors with solutions
|
|
64
64
|
- [Performance Optimization](./advanced/performance.md) — Minimal client JS, fast hydration, efficient reactivity
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -126,7 +126,7 @@ export function Counter(__allProps: { initial?: number } & { __instanceId?: stri
|
|
|
126
126
|
A build-time post-processing step injects `useRequestContext()` calls into generated templates. `BfScripts` renders the collected `<script>` tags:
|
|
127
127
|
|
|
128
128
|
```tsx
|
|
129
|
-
import { BfScripts } from '@barefootjs/hono'
|
|
129
|
+
import { BfScripts } from '@barefootjs/hono/scripts'
|
|
130
130
|
|
|
131
131
|
export function Layout({ children }) {
|
|
132
132
|
return (
|
|
@@ -212,3 +212,158 @@ export default defineConfig({
|
|
|
212
212
|
}),
|
|
213
213
|
})
|
|
214
214
|
```
|
|
215
|
+
|
|
216
|
+
## Adding to an Existing Project
|
|
217
|
+
|
|
218
|
+
`npm create barefootjs@latest` scaffolds the layout above from scratch; this section retrofits it onto an existing Hono + Cloudflare Workers app that already renders with `hono/jsx-renderer` and deploys with `wrangler deploy`. The end state is the same as the scaffold's, so a freshly scaffolded project is a valid reference to diff against when something doesn't line up.
|
|
219
|
+
|
|
220
|
+
### What changes
|
|
221
|
+
|
|
222
|
+
`barefoot()` compiles **individual components** — each `.tsx` under `components` becomes an SSR template under `templates` plus hydration JS under `build.outDir`. It never bundles the server app into a single file. For a project that currently does that with a whole-app Vite bundler (e.g. `@hono/vite-build`), the consequence is:
|
|
223
|
+
|
|
224
|
+
| | Before | After |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| Server build | Bundler plugin emits a single-file SSR bundle | Step removed; nothing replaces it |
|
|
227
|
+
| `wrangler.jsonc` `main` | The pre-bundled output | The uncompiled entry file — wrangler's own esbuild bundles it at dev/deploy time |
|
|
228
|
+
| `vite build` | Produces the server bundle | Produces compiled templates + hydration JS only |
|
|
229
|
+
|
|
230
|
+
`barefoot()` takes the bundler plugin's place in `vite.config.ts`, and any `package.json` script or CI step that referenced the old bundle path goes with it.
|
|
231
|
+
|
|
232
|
+
**Paths in this section are the scaffold's** (`components/`, `dist/components`, `public/components`, `server.tsx`, `renderer.tsx`). Substitute your own. Two that are easy to copy literally by mistake:
|
|
233
|
+
|
|
234
|
+
- `wrangler.jsonc` `main` must be your app's actual Hono entry file (e.g. `src/index.tsx`). Copying `server.tsx` as-is fails immediately with `The entry-point file at "server.tsx" was not found`.
|
|
235
|
+
- `import { renderer } from './renderer'` assumes `jsxRenderer` lives in its own module, as in the scaffold. If yours is defined inline in the entry file, edit it there and skip the import — there's no need to split it out.
|
|
236
|
+
|
|
237
|
+
### 1. Install
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
npm install @barefootjs/client @barefootjs/hono @barefootjs/jsx @barefootjs/shared
|
|
241
|
+
npm install -D @barefootjs/vite
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
> **Peer dependencies.** `@barefootjs/hono` peer-depends on `vite ^6.0.0` and `@barefootjs/jsx` on `typescript ^5.0.0`. A project already on a newer major of either (e.g. `vite@8`, `typescript@7`) fails `npm install` with an `ERESOLVE` error. Either install with `--legacy-peer-deps` or pin `typescript` to `^5`. The `typescript` range is the one that matters: a newer `vite` alone works in practice, but a newer `typescript` makes `vite build` crash at config-load time with `TypeError: ts.createPrinter is not a function` — an opaque runtime error, not a version check — because `@barefootjs/vite` calls the `typescript` package's compiler API directly.
|
|
245
|
+
|
|
246
|
+
### 2. Config files
|
|
247
|
+
|
|
248
|
+
#### `tsconfig.json`
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"compilerOptions": {
|
|
253
|
+
"jsx": "react-jsx",
|
|
254
|
+
"jsxImportSource": "@barefootjs/hono/jsx",
|
|
255
|
+
"baseUrl": ".",
|
|
256
|
+
"paths": {
|
|
257
|
+
"@/components/*": ["./dist/components/*", "./components/*"]
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"exclude": ["node_modules", "dist/components"]
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`@/components/*` has two targets, tried in order by both TypeScript and wrangler: the compiled SSR template under `dist/components/` (carrying the hydration markers from Output Format above) whenever it exists, otherwise the raw source under `components/` so editor tooling and type-checking resolve before the first build. Excluding `dist/components` keeps the compiled copy out of type-checking. Everything else already in `compilerOptions` (`types`, `strict`, …) stays as it is.
|
|
265
|
+
|
|
266
|
+
#### `vite.config.ts`
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
// vite.config.ts
|
|
270
|
+
import { dirname, resolve } from 'node:path'
|
|
271
|
+
import { fileURLToPath } from 'node:url'
|
|
272
|
+
import { defineConfig } from 'vite'
|
|
273
|
+
import { barefoot } from '@barefootjs/hono/vite'
|
|
274
|
+
|
|
275
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
276
|
+
|
|
277
|
+
export default defineConfig({
|
|
278
|
+
// Public URL prefix of the client build. Must match the path Workers
|
|
279
|
+
// Assets serves `build.outDir` under (wrangler.jsonc below).
|
|
280
|
+
base: '/components/',
|
|
281
|
+
resolve: {
|
|
282
|
+
// Required, not optional: Vite's dev-server dependency pre-scan runs
|
|
283
|
+
// before this plugin's `transform` hook and ignores tsconfig `paths`.
|
|
284
|
+
// Points at the SOURCE tree, never at `dist/components`.
|
|
285
|
+
alias: {
|
|
286
|
+
'@/components': resolve(HERE, 'components'),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
// `build.outDir` sits inside `public/`, which Workers Assets already
|
|
290
|
+
// serves as-is. Vite's default `publicDir` would copy the rest of
|
|
291
|
+
// `public/` into `public/components` on every build for nothing.
|
|
292
|
+
publicDir: false,
|
|
293
|
+
build: {
|
|
294
|
+
outDir: 'public/components', // hydration JS → served by Workers Assets
|
|
295
|
+
emptyOutDir: true,
|
|
296
|
+
},
|
|
297
|
+
plugins: barefoot({
|
|
298
|
+
components: ['components'], // .tsx source directories to compile
|
|
299
|
+
templates: 'dist/components', // compiled SSR templates → imported by the server, never served
|
|
300
|
+
}),
|
|
301
|
+
})
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Everything not shown (hashing, chunking, minification, dev server) is stock Vite. `adapterOptions`, `assets`, and `assetsOutputFile` (see Options above) are not needed for a retrofit.
|
|
305
|
+
|
|
306
|
+
#### `wrangler.jsonc`
|
|
307
|
+
|
|
308
|
+
```jsonc
|
|
309
|
+
{
|
|
310
|
+
"$schema": "node_modules/wrangler/config-schema.json",
|
|
311
|
+
"name": "my-app",
|
|
312
|
+
// Your existing uncompiled Hono entry (see "Paths" above). Wrangler
|
|
313
|
+
// bundles it and follows the tsconfig `paths` mapping into dist/components/.
|
|
314
|
+
"main": "server.tsx",
|
|
315
|
+
"compatibility_date": "2025-01-01",
|
|
316
|
+
// Must cover `build.outDir` from vite.config.ts and must not include
|
|
317
|
+
// `templates` — see Deploying to Cloudflare Workers above.
|
|
318
|
+
"assets": {
|
|
319
|
+
"directory": "./public"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 3. Wire the app
|
|
325
|
+
|
|
326
|
+
**Render `<BfScripts />` once** (from `@barefootjs/hono/scripts`) in the layout your `jsxRenderer` already defines — not inside individual components. It emits the `<script>` tags loading each compiled component's hydration JS exactly once per page, however many instances are rendered (see Script Collection above). Existing `ContextRenderer` augmentation and stylesheet links stay as they are.
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
import { jsxRenderer } from 'hono/jsx-renderer'
|
|
330
|
+
import { BfScripts } from '@barefootjs/hono/scripts'
|
|
331
|
+
|
|
332
|
+
export const renderer = jsxRenderer(({ children, title }) => (
|
|
333
|
+
<html lang="en">
|
|
334
|
+
<head>
|
|
335
|
+
<meta charset="UTF-8" />
|
|
336
|
+
<title>{title ?? 'My app'}</title>
|
|
337
|
+
</head>
|
|
338
|
+
<body>
|
|
339
|
+
{children}
|
|
340
|
+
<BfScripts />
|
|
341
|
+
</body>
|
|
342
|
+
</html>
|
|
343
|
+
))
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Import components through the alias**, not a relative path into the source tree, so the dist-first resolution from `tsconfig.json` applies:
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
// server.tsx
|
|
350
|
+
import { Hono } from 'hono'
|
|
351
|
+
import { renderer } from './renderer'
|
|
352
|
+
import { Counter } from '@/components/Counter'
|
|
353
|
+
|
|
354
|
+
const app = new Hono()
|
|
355
|
+
app.use('*', renderer)
|
|
356
|
+
app.get('/', (c) => c.render(<main><Counter /></main>, { title: 'My app' }))
|
|
357
|
+
export default app
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Existing plain Hono JSX components can stay put and move under `components/` one at a time — only files under a configured `components` directory are compiled.
|
|
361
|
+
|
|
362
|
+
### 4. Build and verify
|
|
363
|
+
|
|
364
|
+
```sh
|
|
365
|
+
npx vite build # writes dist/components/ (templates) and public/components/ (hydration JS)
|
|
366
|
+
npx wrangler dev # or: npx wrangler deploy
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Load a page that renders a converted `"use client"` component: its element carries a `bf` marker attribute (see Output Format above) and it responds to interaction. During development, run `vite dev` alongside `wrangler dev` so `dist/components/` keeps regenerating on change.
|
|
@@ -18,16 +18,20 @@ JSX Source
|
|
|
18
18
|
|
|
19
19
|
## Available Adapters
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
| [
|
|
25
|
-
| [
|
|
26
|
-
| [
|
|
27
|
-
| [
|
|
28
|
-
| [
|
|
29
|
-
| [
|
|
30
|
-
| [
|
|
21
|
+
<!-- ADAPTER-TABLE:START (auto-generated by scripts/generate-adapter-docs.ts — do not edit by hand) -->
|
|
22
|
+
| Language | Backend | Package |
|
|
23
|
+
|----------|---------|---------|
|
|
24
|
+
| TypeScript | [Hono](./adapters/hono-adapter.md) | `@barefootjs/hono` |
|
|
25
|
+
| Go | [html/template](./adapters/go-template-adapter.md) | `@barefootjs/go-template` |
|
|
26
|
+
| Perl | [Mojolicious](./adapters/perl-adapter.md) | `@barefootjs/mojolicious` |
|
|
27
|
+
| Perl | [Text::Xslate](./adapters/perl-adapter.md) | `@barefootjs/xslate` |
|
|
28
|
+
| Ruby | [ERB](./adapters/ruby-adapter.md) | `@barefootjs/erb` |
|
|
29
|
+
| Python | [Jinja2](./adapters/python-adapter.md) | `@barefootjs/jinja` |
|
|
30
|
+
| PHP | [Twig](./adapters/php-adapter.md) | `@barefootjs/twig` |
|
|
31
|
+
| PHP | [Laravel Blade](./adapters/php-adapter.md) | `@barefootjs/blade` |
|
|
32
|
+
| Rust | [minijinja](./adapters/rust-adapter.md) | `@barefootjs/rust` |
|
|
33
|
+
| — | [CSR (browser only)](./adapters/csr.md) | `@barefootjs/client` |
|
|
34
|
+
<!-- ADAPTER-TABLE:END -->
|
|
31
35
|
|
|
32
36
|
> CSR is not an IR→template adapter. It renders components directly in the browser using client-side template functions — use it when the server can't (or shouldn't) emit the initial HTML.
|
|
33
37
|
|
|
@@ -75,17 +75,32 @@ Files with reactive APIs but no `"use client"` emit **BF001**:
|
|
|
75
75
|
error[BF001]: 'use client' directive required for components with createSignal
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
### Props
|
|
78
|
+
### Destructured Props Stay Live
|
|
79
79
|
|
|
80
80
|
```tsx
|
|
81
|
-
// ⚠️ BF043: Destructuring captures values once — may lose reactivity
|
|
82
81
|
function Child({ count }: Props) { ... }
|
|
83
82
|
|
|
84
|
-
//
|
|
83
|
+
// Compiles identically to direct access:
|
|
85
84
|
function Child(props: Props) { ... }
|
|
86
85
|
```
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
Both forms record the same `propsParams` metadata in Phase 1. Phase 2 (client JS emission)
|
|
88
|
+
rewrites every value-position read of a name bound by the destructured PARAMETER — inside
|
|
89
|
+
effects, memos, handlers, text, reactive attributes, anywhere in the generated `init*` body —
|
|
90
|
+
to a live `_p.<key>` read, the same read `props.xxx` compiles to. See
|
|
91
|
+
`rewriteDestructuredPropReads` (`ir-to-client-js/rewrite-destructured-props.ts`) and
|
|
92
|
+
`livePropReadExpr` (`props-binding.ts`).
|
|
93
|
+
|
|
94
|
+
Destructuring in the function BODY (`const { count } = props`, or the equivalent `const count
|
|
95
|
+
= props.count`) is a pure single-prop alias — the analyzer's IR can't tell the two shapes
|
|
96
|
+
apart, and treats them identically — and is ALSO rewritten live, via the same
|
|
97
|
+
`rewriteDestructuredPropReads` door's `rewriteBodyAliasReads` branch
|
|
98
|
+
(`resolveBodyPropAliases`, `props-binding.ts`): the local's own extraction is deleted from the
|
|
99
|
+
emitted body (it would otherwise shadow the very references the rewrite is trying to make
|
|
100
|
+
live) and every reference to it becomes `_p.count`, same as the parameter form. This does NOT
|
|
101
|
+
apply once the local does a real computation of its own (`const doubled = count * 2` stays an
|
|
102
|
+
ordinary once-evaluated local) or is reassigned (`let { count } = props` is never treated as
|
|
103
|
+
a live alias).
|
|
89
104
|
|
|
90
105
|
---
|
|
91
106
|
|
|
@@ -317,64 +317,41 @@ See [JSX Compatibility](../rendering/jsx-compatibility.md) for the full worked e
|
|
|
317
317
|
|
|
318
318
|
---
|
|
319
319
|
|
|
320
|
-
## Component Errors (
|
|
320
|
+
## Component Errors (BF044–BF049)
|
|
321
321
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
```tsx
|
|
329
|
-
// ⚠️ BF043
|
|
330
|
-
function Child({ count }: Props) {
|
|
331
|
-
return <span>{count}</span> // count is captured once
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
```
|
|
336
|
-
warning[BF043]: Destructuring props in function parameters captures values once.
|
|
337
|
-
= help: Use `props.count` for reactive access, or suppress with // @bf-ignore props-destructuring
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
**Fix options:**
|
|
341
|
-
|
|
342
|
-
1. Use direct props access:
|
|
343
|
-
|
|
344
|
-
```tsx
|
|
345
|
-
function Child(props: Props) {
|
|
346
|
-
return <span>{props.count}</span> // Reactive
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
2. Suppress if intentional (static initial value):
|
|
351
|
-
|
|
352
|
-
```tsx
|
|
353
|
-
// @bf-ignore props-destructuring
|
|
354
|
-
function Child({ initialCount }: Props) {
|
|
355
|
-
const [count, setCount] = createSignal(initialCount)
|
|
356
|
-
return <span>{count()}</span>
|
|
357
|
-
}
|
|
358
|
-
```
|
|
322
|
+
<!--
|
|
323
|
+
BF043 (Props Destructuring warning) is retired: destructured props read
|
|
324
|
+
live, the same as `props.xxx` access, so there is no reactivity difference
|
|
325
|
+
left to warn about. See [Props Reactivity](../reactivity/props-reactivity.md).
|
|
326
|
+
-->
|
|
359
327
|
|
|
360
328
|
<a id="bf044"></a>
|
|
361
329
|
|
|
362
330
|
### BF044 — Signal/Memo Getter Not Called
|
|
363
331
|
|
|
364
|
-
**Trigger:** Signal/memo getter passed without calling it
|
|
332
|
+
**Trigger:** Signal/memo getter passed without calling it in a RENDERED
|
|
333
|
+
position — a DOM element attribute or a JSX text child, where the value
|
|
334
|
+
becomes literal output.
|
|
365
335
|
|
|
366
336
|
```tsx
|
|
367
337
|
// ❌ BF044
|
|
368
|
-
<
|
|
338
|
+
<div count={count} /> // Passing getter function, not the value
|
|
369
339
|
```
|
|
370
340
|
|
|
371
341
|
**Fix:**
|
|
372
342
|
|
|
373
343
|
```tsx
|
|
374
344
|
// ✅ Fixed
|
|
375
|
-
<
|
|
345
|
+
<div count={count()} />
|
|
376
346
|
```
|
|
377
347
|
|
|
348
|
+
**Not triggered on a component prop:** `<Child count={count} />` compiles —
|
|
349
|
+
a component prop is an opaque value handed to the child, not rendered
|
|
350
|
+
output, and passing a live getter there is this codebase's deliberate
|
|
351
|
+
Context-Provider idiom (the child calls it at its own read site). See
|
|
352
|
+
[`spec/compiler.md`'s BF044 section](https://github.com/piconic-ai/barefootjs/blob/main/spec/compiler.md#signalmemo-getter-not-called-bf044)
|
|
353
|
+
for the full rule.
|
|
354
|
+
|
|
378
355
|
<a id="bf049"></a>
|
|
379
356
|
|
|
380
357
|
### BF049 — Rich-Typed Prop Not Hydratable
|
|
@@ -457,25 +434,6 @@ export function Page() {
|
|
|
457
434
|
|
|
458
435
|
---
|
|
459
436
|
|
|
460
|
-
## Suppressing Warnings
|
|
461
|
-
|
|
462
|
-
Suppress with `@bf-ignore`:
|
|
463
|
-
|
|
464
|
-
```tsx
|
|
465
|
-
// @bf-ignore props-destructuring
|
|
466
|
-
function Component({ checked }: Props) {
|
|
467
|
-
// Warning suppressed
|
|
468
|
-
}
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
**Available rules:**
|
|
472
|
-
|
|
473
|
-
| Rule ID | Error Code | Description |
|
|
474
|
-
|---------|------------|-------------|
|
|
475
|
-
| `props-destructuring` | BF043 | Props destructuring in function parameters |
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
437
|
## Error Code Quick Reference
|
|
480
438
|
|
|
481
439
|
| Code | Severity | Description |
|
|
@@ -486,7 +444,6 @@ function Component({ checked }: Props) {
|
|
|
486
444
|
| BF013 | Error | Reactive primitive called through an unresolved namespace import |
|
|
487
445
|
| BF021 | Error | Unsupported JSX pattern for SSR |
|
|
488
446
|
| BF023 | Error | Missing key in list |
|
|
489
|
-
| BF043 | Warning | Props destructuring breaks reactivity |
|
|
490
447
|
| BF044 | Error | Signal/memo getter passed without calling it |
|
|
491
448
|
| BF049 | Error | Rich-typed prop read by client code cannot survive hydration |
|
|
492
449
|
| BF054 | Error | Built-in `<Async>` / `<Region>` used without `@barefootjs/client` import |
|
|
@@ -35,10 +35,11 @@ function Button(props: { variant?: 'default' | 'primary'; children?: Child }) {
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
For initial-value-only props, default parameter syntax works
|
|
38
|
+
For initial-value-only props, default parameter syntax works — destructuring is fully
|
|
39
|
+
reactive (see [Props Reactivity](../reactivity/props-reactivity.md)), so there is nothing to
|
|
40
|
+
suppress:
|
|
39
41
|
|
|
40
42
|
```tsx
|
|
41
|
-
// @bf-ignore props-destructuring
|
|
42
43
|
function Counter({ initial = 0 }: { initial?: number }) {
|
|
43
44
|
const [count, setCount] = createSignal(initial)
|
|
44
45
|
return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
|
|
@@ -13,19 +13,21 @@ BarefootJS compiles JSX at build time into your backend's native template format
|
|
|
13
13
|
JSX → IR (backend-agnostic) → Adapter → Template
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| TypeScript | [
|
|
20
|
-
| Go | [
|
|
21
|
-
| Perl | [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
| Rust | (
|
|
28
|
-
|
|
|
29
|
-
|
|
16
|
+
<!-- ADAPTER-TABLE:START (auto-generated by scripts/generate-adapter-docs.ts — do not edit by hand) -->
|
|
17
|
+
| Language | Backend | Package |
|
|
18
|
+
|----------|---------|---------|
|
|
19
|
+
| TypeScript | [Hono](../adapters/hono-adapter.md) | `@barefootjs/hono` |
|
|
20
|
+
| Go | [html/template](../adapters/go-template-adapter.md) | `@barefootjs/go-template` |
|
|
21
|
+
| Perl | [Mojolicious](../adapters/perl-adapter.md) | `@barefootjs/mojolicious` |
|
|
22
|
+
| Perl | [Text::Xslate](../adapters/perl-adapter.md) | `@barefootjs/xslate` |
|
|
23
|
+
| Ruby | [ERB](../adapters/ruby-adapter.md) | `@barefootjs/erb` |
|
|
24
|
+
| Python | [Jinja2](../adapters/python-adapter.md) | `@barefootjs/jinja` |
|
|
25
|
+
| PHP | [Twig](../adapters/php-adapter.md) | `@barefootjs/twig` |
|
|
26
|
+
| PHP | [Laravel Blade](../adapters/php-adapter.md) | `@barefootjs/blade` |
|
|
27
|
+
| Rust | [minijinja](../adapters/rust-adapter.md) | `@barefootjs/rust` |
|
|
28
|
+
| — | [CSR (browser only)](../adapters/csr.md) | `@barefootjs/client` |
|
|
29
|
+
<!-- ADAPTER-TABLE:END -->
|
|
30
|
+
|
|
31
|
+
TypeScript also has an [IR-based `TestAdapter`](https://github.com/piconic-ai/barefootjs/tree/main/packages/test) for browser-free component testing — it isn't a serving-time backend, so it's outside the table above. See [Adapters](../adapters.md) for the full picture, including which adapters share one engine-agnostic runtime.
|
|
30
32
|
|
|
31
33
|
The IR contract is stable. You can [write a custom adapter](../adapters/custom-adapter.md) for any backend.
|
|
@@ -88,7 +88,7 @@ export function Counter(props: CounterProps) {
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
See [Client Directive](./rendering/client-directive.md), [`createSignal`](./reactivity/create-signal.md), and [`createMemo`](./reactivity/create-memo.md) for what each piece does. Props are read via `props.initial
|
|
91
|
+
See [Client Directive](./rendering/client-directive.md), [`createSignal`](./reactivity/create-signal.md), and [`createMemo`](./reactivity/create-memo.md) for what each piece does. Props are read via `props.initial` here, but destructuring them in the function parameter (`function Counter({ initial }: CounterProps)`) works identically — both forms compile to the same live read, so pick whichever reads better. [Props Reactivity](./reactivity/props-reactivity.md) covers the full rule.
|
|
92
92
|
|
|
93
93
|
The Counter is mounted in `server.tsx`:
|
|
94
94
|
|
|
@@ -5,10 +5,13 @@ description: How prop access patterns determine whether reactive updates propaga
|
|
|
5
5
|
|
|
6
6
|
# Props Reactivity
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**`props.xxx` access, destructuring the props parameter, and destructuring the props object
|
|
9
|
+
inside the function body are all equally reactive.** The compiler wraps dynamic prop
|
|
10
|
+
expressions in getters, and every value-position read of a prop — written any of the three
|
|
11
|
+
ways — compiles to a live read of that getter.
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
## Direct Access
|
|
14
|
+
## Direct Access
|
|
12
15
|
|
|
13
16
|
`props.xxx` maintains reactivity. Each access calls the underlying getter:
|
|
14
17
|
|
|
@@ -22,55 +25,87 @@ function Display(props: { value: number }) {
|
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
## Destructuring
|
|
28
|
+
## Destructuring In The Parameter
|
|
26
29
|
|
|
27
|
-
Destructuring
|
|
30
|
+
Destructuring the props **parameter** is also fully reactive. Every reference to a
|
|
31
|
+
destructured prop name — in a `createEffect` body, a `createMemo` computation, an event
|
|
32
|
+
handler, a reactive attribute, plain text, anywhere — compiles to the same live read
|
|
33
|
+
`props.xxx` would:
|
|
28
34
|
|
|
29
35
|
```tsx
|
|
30
36
|
function Display({ value }: { value: number }) {
|
|
31
37
|
createEffect(() => {
|
|
32
|
-
console.log(value) //
|
|
38
|
+
console.log(value) // Re-runs when parent updates value
|
|
33
39
|
})
|
|
34
40
|
return <span>{value}</span>
|
|
35
41
|
}
|
|
36
42
|
```
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
There is no local variable holding a stale, captured-at-mount copy — `value` above compiles
|
|
45
|
+
to a live read of the parent's getter at every reference site, the same as `props.value`
|
|
46
|
+
would. A destructure default (`{ value = 0 }`) is evaluated live too: it re-applies on every
|
|
47
|
+
read, not just once at mount.
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
## Destructuring In The Body
|
|
51
|
+
|
|
52
|
+
Destructuring inside the function body is also fully reactive, as long as the destructured
|
|
53
|
+
name is a **pure alias** of a single prop — nothing computed from it:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
function Display(props: { value: number }) {
|
|
57
|
+
const { value } = props
|
|
58
|
+
createEffect(() => {
|
|
59
|
+
console.log(value) // Re-runs when parent updates value
|
|
60
|
+
})
|
|
61
|
+
return <span>{value}</span>
|
|
62
|
+
}
|
|
49
63
|
```
|
|
50
64
|
|
|
51
|
-
|
|
65
|
+
The compiler recognizes `const { value } = props` (and the equivalent `const value =
|
|
66
|
+
props.value`) as a pure passthrough, drops the local extraction entirely, and rewrites every
|
|
67
|
+
reference to `value` to a live `props.value` read instead — the same rewrite the parameter
|
|
68
|
+
form gets. A destructure default (`const { value = 0 } = props`) and a renamed binding
|
|
69
|
+
(`const { value: v } = props`) are both covered the same way.
|
|
70
|
+
|
|
71
|
+
This does NOT apply once the local does its own computation, or is reassigned:
|
|
52
72
|
|
|
53
73
|
```tsx
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
74
|
+
function Display(props: { value: number }) {
|
|
75
|
+
const { value } = props
|
|
76
|
+
const doubled = value * 2 // `doubled` is an ordinary once-evaluated local
|
|
77
|
+
let { count } = props
|
|
78
|
+
count += 1 // `count` is reassigned, so it can't be a live alias either
|
|
79
|
+
return <span>{doubled}</span>
|
|
58
80
|
}
|
|
59
81
|
```
|
|
60
82
|
|
|
83
|
+
`doubled` is a real computation, not a passthrough — it is an ordinary local, evaluated once
|
|
84
|
+
at its declaration (same as it would be with any other access pattern). A `let` binding is
|
|
85
|
+
never treated as a live alias, since rewriting a later assignment to it would mean silently
|
|
86
|
+
writing through to the caller's prop.
|
|
61
87
|
|
|
62
|
-
## When Destructuring Is Safe
|
|
63
88
|
|
|
64
|
-
|
|
89
|
+
## When To Prefer Which
|
|
90
|
+
|
|
91
|
+
The three reactive forms — `props.xxx`, parameter destructuring, and body destructuring —
|
|
92
|
+
behave identically at runtime, so the choice between them is style, not correctness:
|
|
93
|
+
|
|
94
|
+
- Destructuring (parameter or body) reads naturally and is usually the better default for
|
|
95
|
+
components with a handful of named props.
|
|
96
|
+
- `props.xxx` avoids repeating a long prop list at the call site, and is the natural fit for
|
|
97
|
+
a component that mostly forwards its props (`...rest`) rather than naming each one.
|
|
65
98
|
|
|
66
99
|
|
|
67
100
|
## Summary
|
|
68
101
|
|
|
69
|
-
| Pattern | Reactive? |
|
|
70
|
-
|
|
71
|
-
| `props.value` | Yes |
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
102
|
+
| Pattern | Reactive? |
|
|
103
|
+
|---------|-----------|
|
|
104
|
+
| `props.value` | Yes |
|
|
105
|
+
| `function C({ value }: Props)` — parameter destructuring | Yes |
|
|
106
|
+
| `const { value } = props` — body destructuring (pure alias) | Yes |
|
|
107
|
+
| `const doubled = value * 2` — a computation, not a pure alias | No, evaluated once at declaration (same as any other once-evaluated local) |
|
|
108
|
+
| `createSignal(props.value)` | `props.value` is reactive, the signal it seeds is independent thereafter |
|
|
74
109
|
|
|
75
110
|
|
|
76
111
|
## How It Works
|
|
@@ -85,7 +120,10 @@ The compiler transforms dynamic prop expressions into getters:
|
|
|
85
120
|
{ get value() { return count() } }
|
|
86
121
|
```
|
|
87
122
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
This is the same model as SolidJS
|
|
123
|
+
`props.value` calls the getter directly. A `value` destructured in the PARAMETER, or aliased
|
|
124
|
+
by a pure body destructure, compiles to the exact same getter call at every reference site —
|
|
125
|
+
the compiler rewrites each one, rather than binding a plain local that would only read the
|
|
126
|
+
getter once. This is the same reactive-getter model as SolidJS; unlike SolidJS, BarefootJS
|
|
127
|
+
performs that rewrite for both the parameter form and the body-alias form, so there is no
|
|
128
|
+
"don't destructure props" caveat left — only the ordinary rule that a real computation
|
|
129
|
+
(`value * 2`) is evaluated once, same as it would be anywhere else in the function.
|