@barefootjs/cli 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/core/{README.md → README.mdx} +7 -12
- package/dist/docs/core/components/{component-authoring.md → component-authoring.mdx} +9 -5
- package/dist/docs/core/components/context-api.md +7 -0
- package/dist/docs/core/components.md +1 -1
- package/dist/docs/core/core-concepts/ai-native.md +19 -0
- package/dist/docs/core/core-concepts/{how-it-works.md → how-it-works.mdx} +8 -4
- package/dist/docs/core/{introduction.md → introduction.mdx} +6 -5
- package/dist/docs/core/llms.txt +1 -0
- package/dist/docs/core/quick-start.mdx +1 -0
- package/dist/docs/core/reactivity/shared-state.md +233 -0
- package/dist/docs/core/reactivity.md +1 -0
- package/dist/index.js +8943 -7960
- package/package.json +2 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Table of Contents
|
|
4
4
|
|
|
5
|
-
### 1. [Introduction](./introduction.
|
|
5
|
+
### 1. [Introduction](./introduction.mdx)
|
|
6
6
|
|
|
7
7
|
- What is BarefootJS?
|
|
8
8
|
- Why BarefootJS?
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
- [Backend Freedom](./core-concepts/backend-freedom.md) — How adapters let the same JSX run on any server
|
|
20
20
|
- [MPA-style Development](./core-concepts/mpa-style.md) — Server-rendering by default, JS only where marked
|
|
21
21
|
- [Fine-grained Reactivity](./core-concepts/reactivity.md) — Signals, effects, memos — no virtual DOM needed
|
|
22
|
-
- [AI-native Development](./core-concepts/ai-native.md) — Testable IR, CLI discovery, AI-assisted workflows
|
|
23
|
-
- [How It Works](./core-concepts/how-it-works.
|
|
22
|
+
- [AI-native Development](./core-concepts/ai-native.md) — Testable IR, CLI discovery, agent skill (Claude Code / Codex), AI-assisted workflows
|
|
23
|
+
- [How It Works](./core-concepts/how-it-works.mdx) — Two-phase compilation, hydration markers, clean overrides
|
|
24
24
|
|
|
25
25
|
### 4. [Reactivity](./reactivity.md)
|
|
26
26
|
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
- [`onCleanup`](./reactivity/on-cleanup.md) — Register cleanup for effects and lifecycle
|
|
32
32
|
- [`untrack`](./reactivity/untrack.md) — Read signals without tracking dependencies
|
|
33
33
|
- [Props Reactivity](./reactivity/props-reactivity.md) — Gotchas with destructuring
|
|
34
|
+
- [Shared State Patterns](./reactivity/shared-state.md) — Cross-file state sharing: consolidation, custom events, server props
|
|
34
35
|
|
|
35
36
|
### 5. [Templates & Rendering](./rendering.md)
|
|
36
37
|
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
|
|
41
42
|
### 6. [Components](./components.md)
|
|
42
43
|
|
|
43
|
-
- [Component Authoring](./components/component-authoring.
|
|
44
|
+
- [Component Authoring](./components/component-authoring.mdx) — Server components, client components, and the compilation model
|
|
44
45
|
- [Props & Type Safety](./components/props-type-safety.md) — Typing props, defaults, and rest spreading
|
|
45
46
|
- [Children & Slots](./components/children-slots.md) — Children prop, the `Slot` component, and the `asChild` pattern
|
|
46
47
|
- [Context API](./components/context-api.md) — Sharing state with `createContext` / `useContext`
|
|
@@ -69,16 +70,10 @@ Code examples use **switchable tabs** for adapter output and package manager com
|
|
|
69
70
|
|
|
70
71
|
**Adapter** — Hono (default) or Go Template:
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
- Hono (default)
|
|
74
|
-
- Go Template
|
|
73
|
+
<Tabs id="adapter" labels="Hono (default),Go Template" />
|
|
75
74
|
|
|
76
75
|
**Package Manager** — npm (default), bun, pnpm, or yarn:
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
- npm (default)
|
|
80
|
-
- bun
|
|
81
|
-
- pnpm
|
|
82
|
-
- yarn
|
|
77
|
+
<Tabs id="pm" labels="npm (default),bun,pnpm,yarn" />
|
|
83
78
|
|
|
84
79
|
> Sections marked with 💡 explain JSX and TypeScript concepts for developers from Go, Python, or other backend languages.
|
|
@@ -40,7 +40,7 @@ export function Counter() {
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
The compiler produces a **marked template** (server HTML with `bf-*` attributes) and **client JS** (signals, effects, event handlers). See [How It Works](../core-concepts/how-it-works.
|
|
43
|
+
The compiler produces a **marked template** (server HTML with `bf-*` attributes) and **client JS** (signals, effects, event handlers). See [How It Works](../core-concepts/how-it-works.mdx#two-phase-compilation) for details.
|
|
44
44
|
|
|
45
45
|
### When `"use client"` Is Required
|
|
46
46
|
|
|
@@ -92,8 +92,9 @@ export function Toggle() {
|
|
|
92
92
|
|
|
93
93
|
**Marked template:**
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
<Tabs id="adapter" default="Hono">
|
|
96
|
+
<Tab label="Hono" />
|
|
97
|
+
|
|
97
98
|
```tsx
|
|
98
99
|
export function Toggle({ __instanceId, ... }) {
|
|
99
100
|
const __scopeId = __instanceId || `Toggle_${...}`
|
|
@@ -107,7 +108,9 @@ export function Toggle({ __instanceId, ... }) {
|
|
|
107
108
|
)
|
|
108
109
|
}
|
|
109
110
|
```
|
|
110
|
-
|
|
111
|
+
|
|
112
|
+
<Tab label="Go Template" />
|
|
113
|
+
|
|
111
114
|
```go-template
|
|
112
115
|
{{define "Toggle"}}
|
|
113
116
|
<button bf-s="{{bfScopeAttr .}}" bf="s1">
|
|
@@ -116,7 +119,8 @@ export function Toggle({ __instanceId, ... }) {
|
|
|
116
119
|
</button>
|
|
117
120
|
{{end}}
|
|
118
121
|
```
|
|
119
|
-
|
|
122
|
+
|
|
123
|
+
</Tabs>
|
|
120
124
|
|
|
121
125
|
**Client JS:**
|
|
122
126
|
|
|
@@ -234,3 +234,10 @@ const theme = useContext(ThemeContext)
|
|
|
234
234
|
```
|
|
235
235
|
|
|
236
236
|
Use for optional contexts with a sensible fallback.
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
## Cross-File Context Does Not Work
|
|
240
|
+
|
|
241
|
+
`createContext()` and all components that use it must be in the **same file**. Importing a context from another file does not work — the compiler does not currently support cross-file context sharing.
|
|
242
|
+
|
|
243
|
+
This is why compound components (Accordion, Dialog, Select, Tabs) define all sub-components in a single file. For sharing state across components in separate files, see [Shared State Patterns](../reactivity/shared-state.md).
|
|
@@ -11,7 +11,7 @@ For the `"use client"` directive and the server/client boundary, see [Backend Fr
|
|
|
11
11
|
|
|
12
12
|
| Topic | Description |
|
|
13
13
|
|-------|-------------|
|
|
14
|
-
| [Component Authoring](./components/component-authoring.
|
|
14
|
+
| [Component Authoring](./components/component-authoring.mdx) | Server components, client components, and the compilation model |
|
|
15
15
|
| [Props & Type Safety](./components/props-type-safety.md) | Typing props, defaults, and rest spreading |
|
|
16
16
|
| [Children & Slots](./components/children-slots.md) | Children prop, the `Slot` component, and the `asChild` pattern |
|
|
17
17
|
| [Context API](./components/context-api.md) | Sharing state across compound components with `createContext` / `useContext` |
|
|
@@ -66,6 +66,25 @@ When nothing in the registry fits, `bf gen component <name> <comps...>` scaffold
|
|
|
66
66
|
|
|
67
67
|
**Visual preview**: hosted previews live at [ui.barefootjs.dev/components/<name>](https://ui.barefootjs.dev). Standalone `bf preview` for npm-installed projects is tracked in [#885](https://github.com/piconic-ai/barefootjs/issues/885).
|
|
68
68
|
|
|
69
|
+
## Agent Skill
|
|
70
|
+
|
|
71
|
+
BarefootJS publishes an agent skill that gives AI deep knowledge of the compiler, IR, CLI, and component system. Install it and the agent can build, test, and debug BarefootJS projects autonomously.
|
|
72
|
+
|
|
73
|
+
**Claude Code:**
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
/plugin marketplace add piconic-ai/barefootjs
|
|
77
|
+
/plugin install barefootjs@barefootjs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Codex:**
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
install the barefootjs skill from piconic-ai/barefootjs
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
With the skill active, the agent understands `bf` CLI commands, IR test patterns, signal graphs, adapter output, and error codes — no manual prompting needed.
|
|
87
|
+
|
|
69
88
|
## Agent Loop
|
|
70
89
|
|
|
71
90
|
The same workflow driven by an AI agent — say, "add a settings form":
|
|
@@ -51,8 +51,9 @@ The IR records:
|
|
|
51
51
|
|
|
52
52
|
Marked template (Phase 2a):
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
<Tabs id="adapter" default="Hono">
|
|
55
|
+
<Tab label="Hono" />
|
|
56
|
+
|
|
56
57
|
```tsx
|
|
57
58
|
export function Counter({ __instanceId, ... }) {
|
|
58
59
|
const __scopeId = __instanceId || `Counter_${Math.random().toString(36).slice(2, 8)}`
|
|
@@ -65,7 +66,9 @@ export function Counter({ __instanceId, ... }) {
|
|
|
65
66
|
)
|
|
66
67
|
}
|
|
67
68
|
```
|
|
68
|
-
|
|
69
|
+
|
|
70
|
+
<Tab label="Go Template" />
|
|
71
|
+
|
|
69
72
|
```go-template
|
|
70
73
|
{{define "Counter"}}
|
|
71
74
|
<button bf-s="{{bfScopeAttr .}}" bf="s1">
|
|
@@ -73,7 +76,8 @@ export function Counter({ __instanceId, ... }) {
|
|
|
73
76
|
</button>
|
|
74
77
|
{{end}}
|
|
75
78
|
```
|
|
76
|
-
|
|
79
|
+
|
|
80
|
+
</Tabs>
|
|
77
81
|
|
|
78
82
|
Client JS (Phase 2b):
|
|
79
83
|
|
|
@@ -28,8 +28,9 @@ export function Counter() {
|
|
|
28
28
|
|
|
29
29
|
This single file compiles into two outputs:
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
<Tabs id="adapter" default="Hono">
|
|
32
|
+
<Tab label="Hono" />
|
|
33
|
+
|
|
33
34
|
**Marked template** — Renders static HTML with hydration markers:
|
|
34
35
|
|
|
35
36
|
```tsx
|
|
@@ -45,7 +46,8 @@ export function Counter({ __instanceId, ... }) {
|
|
|
45
46
|
}
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
<Tab label="Go Template" />
|
|
50
|
+
|
|
49
51
|
**Marked template** — Go `html/template` with hydration markers:
|
|
50
52
|
|
|
51
53
|
```go-template
|
|
@@ -56,7 +58,7 @@ export function Counter({ __instanceId, ... }) {
|
|
|
56
58
|
{{end}}
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
</Tabs>
|
|
60
62
|
|
|
61
63
|
**Client script** — Wires up only the interactive parts:
|
|
62
64
|
|
|
@@ -84,4 +86,3 @@ hydrate('Counter', {
|
|
|
84
86
|
template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${(0)}<!--/--></button>`
|
|
85
87
|
})
|
|
86
88
|
```
|
|
87
|
-
|
package/dist/docs/core/llms.txt
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- [onCleanup](https://barefootjs.dev/docs/reactivity/on-cleanup.md): Registers a cleanup function that runs when the owning effect re-runs or the component is destroyed.
|
|
19
19
|
- [onMount](https://barefootjs.dev/docs/reactivity/on-mount.md): Runs a callback once when the component initializes, without tracking signal dependencies.
|
|
20
20
|
- [Props Reactivity](https://barefootjs.dev/docs/reactivity/props-reactivity.md): How prop access patterns determine whether reactive updates propagate in BarefootJS components.
|
|
21
|
+
- [Shared State Patterns](https://barefootjs.dev/docs/reactivity/shared-state.md): Patterns for sharing reactive state between components in separate files, and when to use each.
|
|
21
22
|
- [untrack](https://barefootjs.dev/docs/reactivity/untrack.md): Executes a function without tracking signal dependencies in the current reactive context.
|
|
22
23
|
|
|
23
24
|
## Rendering
|
|
@@ -147,6 +147,7 @@ This runs `bf build`, generates the final `uno.css`, and calls `wrangler deploy`
|
|
|
147
147
|
|
|
148
148
|
## Next steps
|
|
149
149
|
|
|
150
|
+
- **[Agent Skill](./core-concepts/ai-native.md#agent-skill)** — Install the BarefootJS skill for Claude Code or Codex and let the agent build components, write IR tests, and debug signals for you.
|
|
150
151
|
- **[Core Concepts](./core-concepts.md)** — the four design principles behind BarefootJS: backend freedom, MPA-style rendering, fine-grained reactivity, and AI-native workflows.
|
|
151
152
|
- **[`createSignal`](./reactivity/create-signal.md)** and **[`createMemo`](./reactivity/create-memo.md)** — the reactivity primitives you just used.
|
|
152
153
|
- **[Client Directive](./rendering/client-directive.md)** — exactly what `"use client"` does and when to reach for it.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shared State Patterns
|
|
3
|
+
description: Patterns for sharing reactive state between components in separate files, and when to use each.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Shared State Patterns
|
|
7
|
+
|
|
8
|
+
The [Context API](../components/context-api.md) shares state between components in the **same file** (compound components like Accordion, Dialog, Tabs). But the compilation model — one `.tsx` file produces one `.client.js` bundle — means context **cannot cross file boundaries**.
|
|
9
|
+
|
|
10
|
+
This guide covers patterns for sharing reactive state between components in **separate files**.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Why Context Doesn't Work Across Files
|
|
14
|
+
|
|
15
|
+
Each `.client.js` bundle is independent. When the compiler processes two files that both reference the same `createContext()`, each bundle gets its own call — producing a unique `Symbol` id. The provider's context and the consumer's context are different objects:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
PlaybackProvider.client.js → createContext() → Symbol(#1)
|
|
19
|
+
Player.client.js → createContext() → Symbol(#2)
|
|
20
|
+
↑ different identity
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`useContext` in Player walks the DOM looking for `Symbol(#2)`, but PlaybackProvider set `Symbol(#1)`. No match — the value is never found.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Pattern 1: Consolidate Into One File
|
|
27
|
+
|
|
28
|
+
The simplest solution. If components are tightly coupled, put them in the same file:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
"use client"
|
|
32
|
+
import { createContext, useContext, createSignal, createEffect } from '@barefootjs/client'
|
|
33
|
+
|
|
34
|
+
interface PlaybackContextValue {
|
|
35
|
+
elapsedMs: () => number
|
|
36
|
+
playing: () => boolean
|
|
37
|
+
seek: (ms: number) => void
|
|
38
|
+
toggle: () => void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const PlaybackContext = createContext<PlaybackContextValue>()
|
|
42
|
+
|
|
43
|
+
export function PlaybackProvider(props: { children?: unknown }) {
|
|
44
|
+
const [elapsedMs, setElapsedMs] = createSignal(0)
|
|
45
|
+
const [playing, setPlaying] = createSignal(false)
|
|
46
|
+
|
|
47
|
+
const seek = (ms: number) => setElapsedMs(ms)
|
|
48
|
+
const toggle = () => setPlaying(p => !p)
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<PlaybackContext.Provider value={{ elapsedMs, playing, seek, toggle }}>
|
|
52
|
+
{props.children}
|
|
53
|
+
</PlaybackContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function Player(props: { children?: unknown }) {
|
|
58
|
+
const handleMount = (el: HTMLElement) => {
|
|
59
|
+
const ctx = useContext(PlaybackContext)
|
|
60
|
+
createEffect(() => {
|
|
61
|
+
el.textContent = `${Math.floor(ctx.elapsedMs() / 1000)}s`
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return <div ref={handleMount}>{props.children}</div>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function TimelineBar(props: { duration: number }) {
|
|
68
|
+
const handleMount = (el: HTMLElement) => {
|
|
69
|
+
const ctx = useContext(PlaybackContext)
|
|
70
|
+
el.addEventListener('click', (e) => {
|
|
71
|
+
const rect = el.getBoundingClientRect()
|
|
72
|
+
const ratio = (e.clientX - rect.left) / rect.width
|
|
73
|
+
ctx.seek(ratio * props.duration)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
return <div ref={handleMount} className="timeline" />
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**When to use:** Components share a tight contract and are always used together (like Select + SelectTrigger + SelectContent).
|
|
81
|
+
|
|
82
|
+
| Pros | Cons |
|
|
83
|
+
|------|------|
|
|
84
|
+
| Full reactivity via signals | All components in one file |
|
|
85
|
+
| Type-safe | File grows with component count |
|
|
86
|
+
| Works with Context API | Not suitable for loosely coupled components |
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Pattern 2: Custom DOM Events
|
|
90
|
+
|
|
91
|
+
Use the browser's native event system. One component dispatches events, others listen. No shared module state needed.
|
|
92
|
+
|
|
93
|
+
Player (`components/Player.tsx`):
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
"use client"
|
|
97
|
+
import { createSignal, createEffect, onCleanup } from '@barefootjs/client'
|
|
98
|
+
|
|
99
|
+
export function Player() {
|
|
100
|
+
const [elapsedMs, setElapsedMs] = createSignal(0)
|
|
101
|
+
|
|
102
|
+
const handleMount = (el: HTMLElement) => {
|
|
103
|
+
createEffect(() => {
|
|
104
|
+
el.dispatchEvent(new CustomEvent('playback:timeupdate', {
|
|
105
|
+
bubbles: true,
|
|
106
|
+
detail: { elapsedMs: elapsedMs() },
|
|
107
|
+
}))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const onSeek = ((e: CustomEvent) => {
|
|
111
|
+
setElapsedMs(e.detail.ms)
|
|
112
|
+
}) as EventListener
|
|
113
|
+
|
|
114
|
+
document.addEventListener('playback:seek', onSeek)
|
|
115
|
+
onCleanup(() => document.removeEventListener('playback:seek', onSeek))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return <div ref={handleMount} />
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
TimelineBar (`components/TimelineBar.tsx`):
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
"use client"
|
|
126
|
+
import { onCleanup } from '@barefootjs/client'
|
|
127
|
+
|
|
128
|
+
export function TimelineBar(props: { duration: number }) {
|
|
129
|
+
const handleMount = (el: HTMLElement) => {
|
|
130
|
+
const onTimeUpdate = ((e: CustomEvent) => {
|
|
131
|
+
const ratio = e.detail.elapsedMs / props.duration
|
|
132
|
+
el.style.setProperty('--progress', String(ratio))
|
|
133
|
+
}) as EventListener
|
|
134
|
+
|
|
135
|
+
document.addEventListener('playback:timeupdate', onTimeUpdate)
|
|
136
|
+
onCleanup(() => document.removeEventListener('playback:timeupdate', onTimeUpdate))
|
|
137
|
+
|
|
138
|
+
el.addEventListener('click', (e) => {
|
|
139
|
+
const rect = el.getBoundingClientRect()
|
|
140
|
+
const ratio = (e.clientX - rect.left) / rect.width
|
|
141
|
+
el.dispatchEvent(new CustomEvent('playback:seek', {
|
|
142
|
+
bubbles: true,
|
|
143
|
+
detail: { ms: ratio * props.duration },
|
|
144
|
+
}))
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return <div ref={handleMount} className="timeline" />
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**When to use:** Components are in separate files and communicate through a shared parent in the DOM.
|
|
153
|
+
|
|
154
|
+
| Pros | Cons |
|
|
155
|
+
|------|------|
|
|
156
|
+
| Works across any file boundary | Not reactive — imperative dispatch/listen |
|
|
157
|
+
| No shared imports needed | No type safety on event payloads (use a shared type file to mitigate) |
|
|
158
|
+
| Familiar browser API | Requires manual cleanup |
|
|
159
|
+
| SSR-safe (listeners only run on client) | Event naming conventions needed |
|
|
160
|
+
|
|
161
|
+
### Type-safe event helpers
|
|
162
|
+
|
|
163
|
+
Define event types in a shared `src/` file to get type safety without sharing runtime state:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
export interface PlaybackTimeUpdateDetail {
|
|
167
|
+
elapsedMs: number
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface PlaybackSeekDetail {
|
|
171
|
+
ms: number
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function dispatchTimeUpdate(el: Element, detail: PlaybackTimeUpdateDetail) {
|
|
175
|
+
el.dispatchEvent(new CustomEvent('playback:timeupdate', { bubbles: true, detail }))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function dispatchSeek(el: Element, detail: PlaybackSeekDetail) {
|
|
179
|
+
el.dispatchEvent(new CustomEvent('playback:seek', { bubbles: true, detail }))
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`src/` utility files are inlined at compile time. Type definitions and `CustomEvent` constructors don't carry mutable state, so inlining is safe — each bundle gets its own copy of the helper functions, but they produce identical events.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Pattern 3: Server-Mediated Props
|
|
187
|
+
|
|
188
|
+
For state that originates on the server (database, session, URL params), pass it as props from the server route. No client-side sharing needed:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { Player } from '@/components/Player'
|
|
192
|
+
import { TimelineBar } from '@/components/TimelineBar'
|
|
193
|
+
|
|
194
|
+
app.get('/player/:id', async (c) => {
|
|
195
|
+
const track = await db.getTrack(c.req.param('id'))
|
|
196
|
+
return c.render(
|
|
197
|
+
<main>
|
|
198
|
+
<Player trackId={track.id} initialPosition={track.lastPosition} />
|
|
199
|
+
<TimelineBar duration={track.durationMs} />
|
|
200
|
+
</main>
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Each component hydrates independently with its own signals. User interactions that need to cross components use Pattern 2 (custom events).
|
|
206
|
+
|
|
207
|
+
**When to use:** Initial state comes from the server; components only need to sync on user-driven actions.
|
|
208
|
+
|
|
209
|
+
| Pros | Cons |
|
|
210
|
+
|------|------|
|
|
211
|
+
| SSR-friendly — state is in HTML | Real-time sync still needs custom events |
|
|
212
|
+
| No shared client state | Server round-trip for state changes |
|
|
213
|
+
| Each component is independently testable | |
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
## Choosing a Pattern
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
Is all state server-derived?
|
|
220
|
+
└─ Yes → Pattern 3 (Server-Mediated Props)
|
|
221
|
+
└─ No
|
|
222
|
+
├─ Are components tightly coupled (always used together)?
|
|
223
|
+
│ └─ Yes → Pattern 1 (Consolidate Into One File)
|
|
224
|
+
└─ No → Pattern 2 (Custom DOM Events)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| | Context (same file) | Consolidate | Custom Events | Server Props |
|
|
228
|
+
|---|---|---|---|---|
|
|
229
|
+
| Cross-file | No | No (one file) | Yes | Yes |
|
|
230
|
+
| Reactive | Yes | Yes | No | No (initial only) |
|
|
231
|
+
| Type-safe | Yes | Yes | With helpers | Yes |
|
|
232
|
+
| SSR-safe | Yes | Yes | Yes | Yes |
|
|
233
|
+
| Testable | IR test | IR test | E2E | IR test + E2E |
|
|
@@ -26,3 +26,4 @@ import { createSignal, createEffect, createMemo, onMount, onCleanup, untrack } f
|
|
|
26
26
|
## Guides
|
|
27
27
|
|
|
28
28
|
- [Props Reactivity](./reactivity/props-reactivity.md) — How props stay reactive, and when destructuring breaks it
|
|
29
|
+
- [Shared State Patterns](./reactivity/shared-state.md) — Sharing state across components in separate files
|