@vincle/flow 0.9.0 → 0.10.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/CHANGELOG.md +24 -0
- package/README.md +27 -482
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +71 -13
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -1
- package/dist/context-CLpgp0eJ.js +1 -0
- package/dist/context.d.ts +11 -7
- package/dist/context.js +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/{index-M7xu3JXg.d.ts → index-CkR-2s9P.d.ts} +7 -5
- package/dist/index.d.ts +6 -16
- package/dist/index.js +1 -1
- package/dist/jsx-augment-Da8Va85F.d.ts +45 -0
- package/dist/render-BkqdRPBk.js +5 -0
- package/dist/utils-yayEbXmr.js +1 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +7 -5
- package/dist/context-DlzTTBsQ.js +0 -1
- package/dist/render-zCgGmdz0.js +0 -5
- package/dist/utils-DKRixAvn.js +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @vincle/flow
|
|
2
|
+
|
|
3
|
+
## 0.10.0 — 2026-10-05
|
|
4
|
+
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
- Remove `<Include>` and its error codes. Static `<Defer>` already emits a placeholder using `generatePath(id)`. Use `<Slot>` with `<Defer>` for named insertion points; arbitrary external includes must be handled by the application.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Own experimental Fragment Include template attributes and Turbo element types through module augmentation of Core's neutral interfaces. Make these augmentations reachable from published declarations and every public JSX entry point.
|
|
12
|
+
- Drain deferred fragments through a store cursor instead of maintaining a separate processed set.
|
|
13
|
+
- Refresh the README with concise usage and Bun, pnpm, and npm installation commands.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Combine the HTTP request's abort signal with the caller's signal in `serve()`, cancelling rendering when the client disconnects.
|
|
18
|
+
- Stop waiting for user work that ignores cancellation; call `return()` on unfinished iterators without blocking cancellation.
|
|
19
|
+
- Continue discovering nested `<Defer>` nodes inside retained fallbacks.
|
|
20
|
+
- Match a placeholder's closing `<?end>` among its siblings, preserving surrounding content and nested marker ranges.
|
|
21
|
+
- Keep unmatched patch templates in the DOM until they can be applied.
|
|
22
|
+
- Protect polyfill-owned patch templates from partially implemented browser-native patching and detect active fragment includes without treating comments, raw text, or inert templates as active content.
|
|
23
|
+
|
|
24
|
+
Changes since the npm release 0.9.0 (commit `02b6840`).
|
package/README.md
CHANGED
|
@@ -1,513 +1,58 @@
|
|
|
1
1
|
# @vincle/flow
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Use `@vincle/core` alone for SSG, emails, and pure SSR. Add `@vincle/flow` when you need **progressive enhancement**: the initial HTML loads fast with placeholders, and heavy or slow components are rendered separately and patched into the page without a full reload.
|
|
8
|
-
|
|
9
|
-
| `@vincle/core` | `@vincle/flow` |
|
|
10
|
-
| ------------------------- | -------------------------------------------------- |
|
|
11
|
-
| Renders JSX → HTML string | Adds deferred fragments + streaming patch delivery |
|
|
12
|
-
| Server-only, zero runtime | Emits adapter-specific markup for DOM updates |
|
|
13
|
-
| `renderToString()` | `renderToStream()` / `renderToStatic()` |
|
|
14
|
-
| N/A | Adapters: Turbo, HTMX, Native, WebPlatform, ESI |
|
|
15
|
-
|
|
16
|
-
## Why deferred regions in streaming SSR
|
|
17
|
-
|
|
18
|
-
A standard `renderToString` call is a serial pipeline: the server computes the full page before sending the first byte. With streaming, the shell (layout, navigation, above-the-fold content) goes to the browser immediately while heavy components are still rendering.
|
|
19
|
-
|
|
20
|
-
Deferred regions of the same **wave** render **in parallel** and independently: the slowest one does not block its wave-mates. Waves flush one after another, and streams run on their own separate schedule. The browser receives the shell, paints it, then receives patches as they arrive and applies them in-place.
|
|
21
|
-
|
|
22
|
-
- **TTFB / FCP**: users see content in one round-trip, not after all async work settles
|
|
23
|
-
- **No virtual DOM, no hydration**: patches are applied as plain HTML by the adapter's client mechanism (Turbo, HTMX) or a minimal polyfill
|
|
24
|
-
- **Fault isolation**: a failed fragment routes to `onError` (or logs) and is skipped; the rest of the page is unaffected
|
|
25
|
-
- **Memory**: fragments are streamed out as they render, not accumulated before flushing
|
|
26
|
-
|
|
27
|
-
## Why deferred regions in SSG
|
|
28
|
-
|
|
29
|
-
In static generation every page is a snapshot. Cache invalidation is the hard part: when a deeply nested component changes, you regenerate the entire page. Deferred fragments split that snapshot.
|
|
30
|
-
|
|
31
|
-
The shell (stable layout, navigation) is one file. Each fragment is a separate file at a predictable URL. Your build pipeline treats them independently:
|
|
32
|
-
|
|
33
|
-
- **Granular invalidation**: only fragments whose data changed need to be regenerated; the shell stays cached
|
|
34
|
-
- **Per-fragment TTL**: a "live prices" fragment can expire in 60 s while the surrounding page is cached for a day
|
|
35
|
-
- **CDN composition**: shell and fragments are plain HTML files; any CDN can serve them
|
|
36
|
-
- **Incremental builds**: on large sites, regenerating ten fragment files instead of ten thousand pages cuts build time
|
|
3
|
+
Deferred fragments and streaming for `@vincle/core`. Send the page shell first,
|
|
4
|
+
then patch in content as it becomes ready. Also supports static generation
|
|
5
|
+
with separate fragment files.
|
|
37
6
|
|
|
38
7
|
## Install
|
|
39
8
|
|
|
40
|
-
```
|
|
41
|
-
bun add @vincle/flow
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Components
|
|
45
|
-
|
|
46
|
-
@vincle/flow provides three declarative primitives for deferred content: a unified family built around **Slot** (the hole) and **Defer** (the deferred content that fills it). Each works with any adapter, in both streaming and static generation.
|
|
47
|
-
|
|
48
|
-
### `<Slot>`: a named insertion point with fallback content
|
|
49
|
-
|
|
50
|
-
Declares a named insertion point in the shell. Its `children` are rendered immediately as placeholder content: visible in the initial HTML until a `<Defer target="…">` overrides them.
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
import { Slot, Defer } from "@vincle/flow";
|
|
54
|
-
|
|
55
|
-
function Layout() {
|
|
56
|
-
return (
|
|
57
|
-
<html>
|
|
58
|
-
<body>
|
|
59
|
-
<nav>...</nav>
|
|
60
|
-
<main>
|
|
61
|
-
<Slot name="page">
|
|
62
|
-
<p>Loading…</p>
|
|
63
|
-
</Slot>
|
|
64
|
-
</main>
|
|
65
|
-
</body>
|
|
66
|
-
</html>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Elsewhere in the tree (or a different component):
|
|
71
|
-
function PageContent() {
|
|
72
|
-
return <Defer target="page">{() => <h1>Hello</h1>}</Defer>;
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
`Slot` registers nothing in the pending store. It is purely a passive hole. The `children` are the initial fallback, visible until a `<Defer>` pushes deferred content that patches the placeholder.
|
|
77
|
-
|
|
78
|
-
### `<Defer>`: deferred fragment (sync or lazy)
|
|
79
|
-
|
|
80
|
-
Defines deferred content targeting a DOM element by id. **Unified**: one component for both synchronous and deferred content. The behaviour depends on `children`:
|
|
81
|
-
|
|
82
|
-
| `children` type | `fallback` | Behaviour |
|
|
83
|
-
| ------------------- | ---------- | ------------------------------------------------- |
|
|
84
|
-
| `VNode` (plain JSX) | N/A | Registers content; renders a placeholder |
|
|
85
|
-
| `VNode` (plain JSX) | provided | Registers content; placeholder renders `fallback` |
|
|
86
|
-
| `(signal) => …` | N/A | Lazy content; renders a placeholder |
|
|
87
|
-
| `(signal) => …` | provided | Lazy content; placeholder renders `fallback` |
|
|
88
|
-
|
|
89
|
-
**Sync**: plain JSX:
|
|
90
|
-
|
|
91
|
-
```tsx
|
|
92
|
-
import { Defer } from "@vincle/flow";
|
|
93
|
-
|
|
94
|
-
<Defer target="page"><h1>Hello</h1></Defer>
|
|
95
|
-
<Defer target="comments"><Comments /></Defer>
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Lazy with placeholder**: factory + fallback:
|
|
99
|
-
|
|
100
|
-
```tsx
|
|
101
|
-
<Defer target="comments" fallback={<p>Loading…</p>}>
|
|
102
|
-
{() => <Comments />}
|
|
103
|
-
</Defer>
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**Cancellable**: factory receives `AbortSignal` (request lifetime + per-fragment timeout):
|
|
107
|
-
|
|
108
|
-
```tsx
|
|
109
|
-
// One-shot
|
|
110
|
-
<Defer target="page">{() => <h1>Hello</h1>}</Defer>
|
|
111
|
-
|
|
112
|
-
// Async
|
|
113
|
-
<Defer target="comments">{() => <Comments />}</Defer>
|
|
114
|
-
|
|
115
|
-
// Stream: each yield is a separate patch
|
|
116
|
-
<Defer target="feed" merge="append">
|
|
117
|
-
{() => liveRows()}
|
|
118
|
-
</Defer>
|
|
119
|
-
|
|
120
|
-
// With timeout: receives AbortSignal
|
|
121
|
-
<Defer target="dashboard" timeout={2000}>
|
|
122
|
-
{(signal) => <Dashboard signal={signal} />}
|
|
123
|
-
</Defer>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
The factory is invoked only when the fragment is about to render. It receives an `AbortSignal` for cancellation and must return a renderable JSX node (or a `Promise` / `AsyncIterable` thereof, which the runtime unwraps automatically).
|
|
127
|
-
|
|
128
|
-
`Defer` always renders a placeholder at its position in the shell: `fallback` is that placeholder's content (empty if none given), and the deferred content replaces it when it resolves. Rendering a `Defer` without any adapter throws at registration: there is no placeholder to emit and nothing to patch into.
|
|
129
|
-
|
|
130
|
-
### `<Include>`: client-side fetch only
|
|
131
|
-
|
|
132
|
-
Renders a placeholder with a `src` attribute: the browser fetches the fragment after the shell lands. No server-push, works with any static host.
|
|
133
|
-
|
|
134
|
-
```tsx
|
|
135
|
-
import { Include } from "@vincle/flow";
|
|
136
|
-
|
|
137
|
-
<Include src="/fragments/comments.html" />;
|
|
138
|
-
<Include src="/fragments/stats.html" fallback={<Spinner />} />;
|
|
139
|
-
|
|
140
|
-
// @ts-expect-error: dangerous schemes are rejected at compile time
|
|
141
|
-
<Include src="javascript:alert(1)" />;
|
|
9
|
+
```sh
|
|
10
|
+
bun add @vincle/core @vincle/flow
|
|
11
|
+
pnpm add @vincle/core @vincle/flow
|
|
12
|
+
npm install @vincle/core @vincle/flow
|
|
142
13
|
```
|
|
143
14
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Named after the [draft HTML `<include>` element](https://github.com/whatwg/html/issues/2791) and ESI `<esi:include>`: short, standard, self-explanatory.
|
|
147
|
-
|
|
148
|
-
### Content forms
|
|
149
|
-
|
|
150
|
-
`Defer` accepts content in either form:
|
|
151
|
-
|
|
152
|
-
| Child | Behaviour |
|
|
153
|
-
| ---------------------------- | -------------------------------------------------------------------- |
|
|
154
|
-
| JSX node (plain children) | Deferred automatically: executes when the fragment renders |
|
|
155
|
-
| `(signal: AbortSignal) => …` | **Cancellable**: receives `AbortSignal` (request cancel + `timeout`) |
|
|
156
|
-
|
|
157
|
-
`Slot` children are plain JSX (not deferred). They render immediately as the fallback placeholder in the shell.
|
|
158
|
-
|
|
159
|
-
### Common props
|
|
160
|
-
|
|
161
|
-
| Prop | Applies to | Meaning |
|
|
162
|
-
| ---------- | ---------- | ------------------------------------------------------------------ |
|
|
163
|
-
| `name` | `Slot` | id of the placeholder (required) |
|
|
164
|
-
| `target` | `Defer` | target DOM id to push content into |
|
|
165
|
-
| `fallback` | `Defer` | placeholder content shown in the shell (only for lazy factories) |
|
|
166
|
-
| `merge` | `Defer` | how content applies to its target (default `"replace"`): see below |
|
|
167
|
-
| `timeout` | `Defer` | per-fragment render timeout in ms |
|
|
168
|
-
| `onError` | `Defer` | per-fragment error handler, overriding the renderer's `onError` |
|
|
169
|
-
| `src` | `Include` | URL the browser fetches for the fragment content |
|
|
170
|
-
|
|
171
|
-
### Merge types
|
|
172
|
-
|
|
173
|
-
`merge` describes how content is applied **relative to the target DOM element identified by `id`**.
|
|
174
|
-
|
|
175
|
-
| `merge` | Effect |
|
|
176
|
-
| ----------- | -------------------------------------------------------------- |
|
|
177
|
-
| `"replace"` | Target element is replaced by content (default) |
|
|
178
|
-
| `"append"` | Content inserted as last child of target |
|
|
179
|
-
| `"prepend"` | Content inserted as first child of target |
|
|
180
|
-
| `"before"` | Content inserted as previous sibling of target |
|
|
181
|
-
| `"after"` | Content inserted as next sibling of target |
|
|
182
|
-
| `"morph"` | Target element is diffed against content, preserving DOM state |
|
|
183
|
-
|
|
184
|
-
The first five say **where** content goes; `"morph"` says **how** it is applied: the client diffs instead of swapping, so focus, scroll position and form state survive the update. It needs a diffing client: `TurboAdapter` (Turbo >= 8) and `HtmxAdapter` (htmx >= 4) only.
|
|
185
|
-
|
|
186
|
-
An adapter that cannot express a merge **rejects it at registration** with a clear error (see capabilities). `NativeAdapter` supports the five positions but not `"morph"`: diffing is far past a 550 B budget. `WebPlatformAdapter` and `EsiAdapter` support `"replace"` only.
|
|
187
|
-
|
|
188
|
-
## Adapters
|
|
189
|
-
|
|
190
|
-
Each adapter implements `Placeholder`/`Patch`/`Frame` (JSX), optional `transformShell`, and a `capabilities` descriptor. The streaming wire format (shell → fragments, `\n`-separated) is the primitive's own serialization in `renderToStream`: an adapter only declares it can stream (`capabilities.streaming: true`), it does not implement the wire. Adapters are **pure wire formats**: HTTP negotiation is a separate concern (see below).
|
|
191
|
-
|
|
192
|
-
| Adapter | `Placeholder` | `Patch` (streaming inline) | `Frame` (SSG lazy-load) |
|
|
193
|
-
| -------------------- | ---------------------- | ----------------------------------------------- | ----------------------- |
|
|
194
|
-
| `TurboAdapter` | `<turbo-frame>` | `<turbo-stream action="…">` | `<turbo-frame id="…">` |
|
|
195
|
-
| `HtmxAdapter` | `<div hx-get>` | `<div hx-swap-oob="…">` | `<div id="…">` |
|
|
196
|
-
| `NativeAdapter` | `<?start name>…<?end>` | `<template for>` (`data-merge` for non-replace) | `<template for="…">` |
|
|
197
|
-
| `WebPlatformAdapter` | `<?start name>…<?end>` | `<template for="…">` (`replace` only) | `<template for="…">` |
|
|
198
|
-
| `EsiAdapter` | `<esi:include src>` | `<esi:inline name fetchable>` (static only) | raw HTML |
|
|
199
|
-
|
|
200
|
-
- **`Patch`**: fragment delivered inline in the same HTTP response as the shell.
|
|
201
|
-
- **`Frame`**: fragment served as a standalone file fetched by the client (SSG).
|
|
202
|
-
- `NativeAdapter` injects a ~550 B polyfill for DOM patching. Pass `WebPlatformAdapter` for zero-JS output, or `EsiAdapter` for CDN-level composition without client JS. An adapter is **required**: `renderToStream` takes it as its second argument, and a `Defer` without one throws at registration.
|
|
203
|
-
|
|
204
|
-
### Capabilities
|
|
205
|
-
|
|
206
|
-
Every adapter declares what its wire format can express:
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
type AdapterCapabilities = {
|
|
210
|
-
streaming: boolean; // can carry a live FlowEvent stream
|
|
211
|
-
merges: readonly MergeType[]; // which merges it supports
|
|
212
|
-
};
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
This is surfaced in the type system. `renderToStream` / `serve` require a streaming adapter, so **`EsiAdapter` is rejected at compile time** there: ESI composition happens at the CDN, via `renderToStatic` + `emitFragments`. An unsupported `merge` fails fast at registration.
|
|
216
|
-
|
|
217
|
-
#### `NativeAdapter` (~550 B polyfill)
|
|
218
|
-
|
|
219
|
-
Uses the [Declarative Partial Updates](https://developer.chrome.com/blog/declarative-partial-updates) API plus a minimal polyfill injected via `transformShell`. All five merge positions (not `"morph"`), no external client library, works in modern browsers.
|
|
220
|
-
|
|
221
|
-
Every update is a **declarative `<template for>`**: the merge mode rides on `data-merge`, lazy client fetches on `data-src`. There are **no per-fragment inline scripts**; the only JS is a single static polyfill, which makes a strict CSP straightforward:
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
import { NativeAdapter, nativePolyfillHash, NATIVE_POLYFILL } from "@vincle/flow/adapters";
|
|
225
|
-
|
|
226
|
-
// Option A: keep the inline <script>, pin it by hash (static ⇒ cache/SSG-safe):
|
|
227
|
-
res.headers.set("Content-Security-Policy", `script-src 'self' '${await nativePolyfillHash()}'`);
|
|
228
|
-
|
|
229
|
-
// Option B: serve the polyfill from your origin under script-src 'self':
|
|
230
|
-
// write NATIVE_POLYFILL to e.g. /flow.js, then:
|
|
231
|
-
const selfHosted = {
|
|
232
|
-
...NativeAdapter,
|
|
233
|
-
transformShell: (shell: string) => injectIntoHead(shell, `<script src="/flow.js"></script>`),
|
|
234
|
-
};
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
A per-request **nonce** is intentionally not offered: it would break the static-cache/SSG story. A hash works because the script never changes.
|
|
238
|
-
|
|
239
|
-
#### `WebPlatformAdapter`
|
|
240
|
-
|
|
241
|
-
Emits the [WICG Declarative Partial Updates](https://developer.chrome.com/docs/web-platform/declarative-partial-updates) wire format without a client-side runtime. It supports `"replace"` only and requires native browser support, which is not universal. Use `NativeAdapter` for a polyfilled fallback.
|
|
242
|
-
|
|
243
|
-
#### `EsiAdapter`: CDN-level composition
|
|
244
|
-
|
|
245
|
-
For **SSG with a CDN ESI processor** (Varnish, Fastly, nginx ESI module). The shell contains `<esi:include src="…">` tags; the CDN fetches each fragment independently, applies separate TTLs, and assembles the final response before it reaches the browser. `"replace"` only; no client-side JS.
|
|
246
|
-
|
|
247
|
-
```tsx
|
|
248
|
-
// Defer with ESI: placeholder becomes esi:include, content renders the fragment
|
|
249
|
-
<Defer target="nav" fallback={<span>Loading nav…</span>}>
|
|
250
|
-
{(signal) => <Nav signal={signal} />}
|
|
251
|
-
</Defer>
|
|
252
|
-
<Defer target="feed" fallback={<span>Loading feed…</span>}>
|
|
253
|
-
{(signal) => <Feed signal={signal} />}
|
|
254
|
-
</Defer>
|
|
255
|
-
// Plain JSX also works when no signal is needed:
|
|
256
|
-
<Defer target="footer" fallback={<span>Loading…</span>}>
|
|
257
|
-
<Footer />
|
|
258
|
-
</Defer>
|
|
259
|
-
```
|
|
15
|
+
Configure JSX with `jsx: "react-jsx"` and `jsxImportSource: "@vincle/core"`.
|
|
260
16
|
|
|
261
17
|
## Usage
|
|
262
18
|
|
|
263
|
-
### Streaming (server pushes fragments)
|
|
264
|
-
|
|
265
19
|
```tsx
|
|
266
|
-
import {
|
|
20
|
+
import { Defer, renderToStream } from "@vincle/flow";
|
|
267
21
|
import { NativeAdapter } from "@vincle/flow/adapters";
|
|
268
22
|
|
|
23
|
+
async function Content() {
|
|
24
|
+
return <p>Deferred content</p>;
|
|
25
|
+
}
|
|
26
|
+
|
|
269
27
|
const stream = renderToStream(
|
|
270
28
|
() => (
|
|
271
29
|
<html>
|
|
272
30
|
<body>
|
|
273
|
-
<
|
|
274
|
-
<Defer target="
|
|
275
|
-
<
|
|
31
|
+
<h1>Ready immediately</h1>
|
|
32
|
+
<Defer target="content" fallback={<p>Loading…</p>}>
|
|
33
|
+
<Content />
|
|
276
34
|
</Defer>
|
|
277
35
|
</body>
|
|
278
36
|
</html>
|
|
279
37
|
),
|
|
280
38
|
NativeAdapter,
|
|
281
39
|
);
|
|
282
|
-
// → ReadableStream<string>. Pipe it to the HTTP response.
|
|
283
|
-
// Swap the adapter (TurboAdapter, HtmxAdapter, …) to change wire format.
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Static generation, pure-static (no deferred content)
|
|
287
|
-
|
|
288
|
-
```tsx
|
|
289
|
-
import { renderToStatic } from "@vincle/flow";
|
|
290
40
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const html = await ctx.renderPage(() => page.component({ currentPage: page.url }));
|
|
294
|
-
await Bun.write(page.outPath, "<!DOCTYPE html>\n" + html);
|
|
295
|
-
}
|
|
41
|
+
const response = new Response(stream.pipeThrough(new TextEncoderStream()), {
|
|
42
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
296
43
|
});
|
|
297
44
|
```
|
|
298
45
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
```tsx
|
|
304
|
-
import { renderToStatic } from "@vincle/flow";
|
|
305
|
-
import { NativeAdapter } from "@vincle/flow/adapters";
|
|
306
|
-
|
|
307
|
-
await renderToStatic(
|
|
308
|
-
async (ctx) => {
|
|
309
|
-
for (const page of pages) {
|
|
310
|
-
const html = await ctx.renderPage(() => page.component({ currentPage: page.url }));
|
|
311
|
-
await Bun.write(page.outPath, "<!DOCTYPE html>\n" + html);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Each fragment is already wrapped in adapter.Frame and rendered,
|
|
315
|
-
// `html` is ready to write, no raw()/renderToString needed.
|
|
316
|
-
await ctx.emitFragments((id, url, html) => Bun.write("./out" + url, html));
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
adapter: NativeAdapter,
|
|
320
|
-
generatePath: (id) => `/fragments/${id}.html`,
|
|
321
|
-
},
|
|
322
|
-
);
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### On-demand fragment regeneration
|
|
326
|
-
|
|
327
|
-
When one piece of data changes, `renderFragment` writes just its fragment: no site rebuild:
|
|
46
|
+
`Defer` emits a placeholder and schedules its content. `Slot` declares a named
|
|
47
|
+
insertion point. Use `serve` from `@vincle/flow/http` for HTTP responses, or
|
|
48
|
+
`renderToStatic` for static generation.
|
|
328
49
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
import { NativeAdapter } from "@vincle/flow/adapters";
|
|
332
|
-
|
|
333
|
-
const { url, html } = await renderFragment("price-AAPL", <span>{price}</span>, {
|
|
334
|
-
adapter: NativeAdapter,
|
|
335
|
-
});
|
|
336
|
-
// `url` matches what the full build wrote: upload `html` there (blob store,
|
|
337
|
-
// on-demand revalidation, CDN purge + PUT…). The shell that includes it is untouched.
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
This is host-agnostic on purpose. It fits Netlify's on-demand functions or Vercel's on-demand revalidation, but doesn't depend on either.
|
|
341
|
-
|
|
342
|
-
### HTTP responses with negotiation
|
|
343
|
-
|
|
344
|
-
Negotiation is **opt-in and decoupled from the adapter**: pass a `negotiate` function (e.g. `negotiateHtmx`, or your own) to extract per-request hints and headers. Without it, the full page is rendered and the client library extracts its own target.
|
|
345
|
-
|
|
346
|
-
```tsx
|
|
347
|
-
import { serve, negotiateHtmx } from "@vincle/flow/http";
|
|
348
|
-
import { WebPlatformAdapter } from "@vincle/flow/adapters";
|
|
349
|
-
|
|
350
|
-
Bun.serve({
|
|
351
|
-
fetch(req) {
|
|
352
|
-
return serve(req, (n) => <App target={n.target} />, WebPlatformAdapter, {
|
|
353
|
-
negotiate: negotiateHtmx,
|
|
354
|
-
// `mode: "fragment"` (shell suppressed) is an explicit opt-in and only
|
|
355
|
-
// produces output when the targeted content is expressed as deferred fragments.
|
|
356
|
-
});
|
|
357
|
-
},
|
|
358
|
-
});
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Composing shell transforms
|
|
362
|
-
|
|
363
|
-
`composeShell` chains several `transformShell` functions (e.g. to inject `<title>`, asset links) into one: falsy entries are skipped, so an adapter's own transform splices in cleanly:
|
|
364
|
-
|
|
365
|
-
```tsx
|
|
366
|
-
import { NativeAdapter, createAdapter } from "@vincle/flow/adapters";
|
|
367
|
-
import { composeShell, injectIntoHead } from "@vincle/flow/utils";
|
|
368
|
-
|
|
369
|
-
const metadata = () => (html: string) => injectIntoHead(html, "<title>Home</title>");
|
|
370
|
-
|
|
371
|
-
const MyAdapter = createAdapter({
|
|
372
|
-
...NativeAdapter,
|
|
373
|
-
transformShell: composeShell(NativeAdapter.transformShell, metadata()),
|
|
374
|
-
});
|
|
375
|
-
```
|
|
50
|
+
Adapters are exported from `@vincle/flow/adapters`: Native (automatic polyfill),
|
|
51
|
+
Turbo, HTMX, WebPlatform (requires browser support) and ESI (static only).
|
|
376
52
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
```ts
|
|
382
|
-
interface ShellContext {
|
|
383
|
-
fragments: { size: number };
|
|
384
|
-
}
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
The fragment count is the number of registered `<Defer>` / `<Slot>` entries after the shell rendered. Use it to conditionally inject client runtime only when fragments exist:
|
|
388
|
-
|
|
389
|
-
```ts
|
|
390
|
-
const SmartAdapter = {
|
|
391
|
-
...NativeAdapter,
|
|
392
|
-
transformShell: (shell: string, ctx) => (ctx.fragments.size > 0 ? injectPolyfill(shell) : shell),
|
|
393
|
-
};
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### Fragment timeouts
|
|
397
|
-
|
|
398
|
-
`renderToStream`, `serve`, and `renderToStatic` accept `defaultTimeout` on their options:
|
|
399
|
-
|
|
400
|
-
```ts
|
|
401
|
-
renderToStream(() => <Page />, NativeAdapter, { defaultTimeout: 5000 });
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
It is a fallback for any `<Defer>` that has no `timeout` prop. A fragment that exceeds its timeout aborts and routes to `onError` (or logs and skips). Per-fragment `timeout` overrides the default:
|
|
405
|
-
|
|
406
|
-
```tsx
|
|
407
|
-
<Defer target="page" timeout={2000}>
|
|
408
|
-
{/* This fragment has a 2s timeout, not the 5s default */}
|
|
409
|
-
<SlowComponent />
|
|
410
|
-
</Defer>
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### Custom negotiation
|
|
414
|
-
|
|
415
|
-
Write a custom `negotiate` function for any client library that uses custom headers:
|
|
416
|
-
|
|
417
|
-
```ts
|
|
418
|
-
import type { Negotiate } from "@vincle/flow";
|
|
419
|
-
|
|
420
|
-
const negotiateMyLib: Negotiate = (req) => {
|
|
421
|
-
const fragment = req.headers.get("X-Fragment-Id") ?? undefined;
|
|
422
|
-
return {
|
|
423
|
-
headers: { Vary: "X-Fragment-Id" },
|
|
424
|
-
target: fragment,
|
|
425
|
-
};
|
|
426
|
-
};
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
The `Vary` header is **unioned** with existing `Vary` values (not overwritten), so multiple negotiators or manual headers never corrupt shared-cache keys.
|
|
430
|
-
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
## API
|
|
434
|
-
|
|
435
|
-
All exports are importable from `@vincle/flow` unless noted otherwise.
|
|
436
|
-
|
|
437
|
-
### Components
|
|
438
|
-
|
|
439
|
-
| Export | Import path | Description |
|
|
440
|
-
| --------- | ------------------------- | ------------------------------------------------------------------------------ |
|
|
441
|
-
| `Slot` | `@vincle/flow` | Named insertion point with optional fallback children; renders a placeholder |
|
|
442
|
-
| `Defer` | `@vincle/flow` | Push deferred content into a target DOM id: sync (plain JSX) or lazy (factory) |
|
|
443
|
-
| `Include` | `@vincle/flow` | Client-side fetch placeholder: no server deferral |
|
|
444
|
-
| `Style` | `@vincle/flow/components` | Named, deduplicated `<style>` tag |
|
|
445
|
-
| `Script` | `@vincle/flow/components` | Named, deduplicated `<script>` tag |
|
|
446
|
-
|
|
447
|
-
### Renderers
|
|
448
|
-
|
|
449
|
-
| Export | Import path | Description |
|
|
450
|
-
| -------------------- | ------------------- | --------------------------------------------------------------------------------------------- |
|
|
451
|
-
| `renderToStream` | `@vincle/flow` | Streams shell + fragments as a `ReadableStream<string>` via the given `adapter` |
|
|
452
|
-
| `renderToFlowEvents` | `@vincle/flow` | Lower level: `ReadableStream<FlowEvent>` (semantic events) with backpressure and cancellation |
|
|
453
|
-
| `renderToStatic` | `@vincle/flow` | Runs `handler` in a static render scope |
|
|
454
|
-
| `renderFragment` | `@vincle/flow` | Renders one fragment on demand, outside a full build: for on-demand regeneration |
|
|
455
|
-
| `serve` | `@vincle/flow/http` | Full HTTP `Response` builder |
|
|
456
|
-
|
|
457
|
-
### Negotiation
|
|
458
|
-
|
|
459
|
-
| Export | Import path | Description |
|
|
460
|
-
| --------------- | ------------------- | ------------------------------------------------ |
|
|
461
|
-
| `negotiateHtmx` | `@vincle/flow/http` | HTMX negotiation: reads `HX-Target`, sets `Vary` |
|
|
462
|
-
| `Negotiate` | `@vincle/flow/http` | Type: `(req: Request) => Negotiation` |
|
|
463
|
-
|
|
464
|
-
### Adapters
|
|
465
|
-
|
|
466
|
-
| Export | Import path | Description |
|
|
467
|
-
| -------------------- | ----------------------- | ------------------------------------------------------ |
|
|
468
|
-
| `NativeAdapter` | `@vincle/flow/adapters` | Declarative Partial Updates + polyfill: no `morph` |
|
|
469
|
-
| `TurboAdapter` | `@vincle/flow/adapters` | Hotwire Turbo Streams: all merge types |
|
|
470
|
-
| `HtmxAdapter` | `@vincle/flow/adapters` | HTMX OOB swaps: all merge types |
|
|
471
|
-
| `WebPlatformAdapter` | `@vincle/flow/adapters` | Pure WICG spec, zero JS: `replace` only |
|
|
472
|
-
| `EsiAdapter` | `@vincle/flow/adapters` | CDN-level ESI composition: `replace` only, static only |
|
|
473
|
-
| `createAdapter` | `@vincle/flow/adapters` | Build a custom adapter |
|
|
474
|
-
| `NATIVE_POLYFILL` | `@vincle/flow/adapters` | Native adapter polyfill as a JS string |
|
|
475
|
-
| `nativePolyfillHash` | `@vincle/flow/adapters` | `() => Promise<string>`: CSP hash for the polyfill |
|
|
476
|
-
|
|
477
|
-
### Types
|
|
478
|
-
|
|
479
|
-
| Export | Import path | Description |
|
|
480
|
-
| --------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
481
|
-
| `Adapter` | `@vincle/flow/adapters` | `{ Placeholder, Patch, Frame, capabilities, transformShell? }` |
|
|
482
|
-
| `StreamingAdapter` | `@vincle/flow/adapters` | `Adapter` with `capabilities.streaming: true` |
|
|
483
|
-
| `AdapterCapabilities` | `@vincle/flow/adapters` | `{ streaming: boolean; merges: readonly MergeType[] }` |
|
|
484
|
-
| `ShellContext` | `@vincle/flow/adapters` | What `transformShell` receives: `{ fragments: { size } }` |
|
|
485
|
-
| `PureStaticContext` | `@vincle/flow` | Static generation context (no `emitFragments`) |
|
|
486
|
-
| `StaticContext` | `@vincle/flow` | Static generation context with `emitFragments` |
|
|
487
|
-
| `MergeType` | `@vincle/flow` | `"replace" \| "append" \| "prepend" \| "before" \| "after" \| "morph"` |
|
|
488
|
-
| `FlowEvent` | `@vincle/flow` | `{ type: "shell" \| "fragment" \| "close", … }` |
|
|
489
|
-
| `Negotiation` | `@vincle/flow` | `{ headers?, mode?, target? }` |
|
|
490
|
-
| `DeferContent` | `@vincle/flow` | `JSX.Element \| string \| ((signal: AbortSignal) => JSX.Element) \| AsyncIterable<JSX.Element> \| ((signal: AbortSignal) => AsyncIterable<JSX.Element>)` |
|
|
491
|
-
|
|
492
|
-
### Utilities
|
|
493
|
-
|
|
494
|
-
| Export | Import path | Description |
|
|
495
|
-
| ---------------- | -------------------- | -------------------------------------------------------------------- |
|
|
496
|
-
| `composeShell` | `@vincle/flow/utils` | Compose several `transformShell` (string→string) into one |
|
|
497
|
-
| `injectIntoHead` | `@vincle/flow/utils` | Insert markup before `</head>` (building block for shell transforms) |
|
|
498
|
-
|
|
499
|
-
### Flow context
|
|
500
|
-
|
|
501
|
-
| Export | Import path | Description |
|
|
502
|
-
| ------------- | ---------------------- | --------------------------------------------------------------------------------------- |
|
|
503
|
-
| `Flow` | `@vincle/flow/context` | The `Scope` key for the per-render flow context: `Scope.get(Flow)` from within a render |
|
|
504
|
-
| `FlowContext` | `@vincle/flow/context` | The per-render context: `config`, `registerFragment`, `nextId`, asset state |
|
|
505
|
-
|
|
506
|
-
> `<Style>` / `<Script>` emit their tag directly, deduplicating as the walk
|
|
507
|
-
> reaches them: no post-render pass, so nothing has to re-derive the document
|
|
508
|
-
> order the engine already guarantees.
|
|
509
|
-
|
|
510
|
-
## License
|
|
53
|
+
[Streaming](https://vincle.cjean.fr/integration/streaming) ·
|
|
54
|
+
[Static generation](https://vincle.cjean.fr/integration/static) ·
|
|
55
|
+
[Adapters](https://vincle.cjean.fr/integration/adapters) ·
|
|
56
|
+
[API](https://vincle.cjean.fr/api/flow/renderToStream)
|
|
511
57
|
|
|
512
58
|
MIT © Christophe Jean
|
|
513
|
-
```
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as withPolyfill, c as TurboAdapter, d as createAdapter, i as WebPlatformAdapter, l as Adapter, n as HtmxAdapter, o as NATIVE_POLYFILL, r as NativeAdapter, s as nativePolyfillHash, t as EsiAdapter, u as ShellContext } from "../index-
|
|
1
|
+
import { a as withPolyfill, c as TurboAdapter, d as createAdapter, i as WebPlatformAdapter, l as Adapter, n as HtmxAdapter, o as NATIVE_POLYFILL, r as NativeAdapter, s as nativePolyfillHash, t as EsiAdapter, u as ShellContext } from "../index-CkR-2s9P.js";
|
|
2
2
|
export { type Adapter, EsiAdapter, HtmxAdapter, NATIVE_POLYFILL, NativeAdapter, type ShellContext, TurboAdapter, WebPlatformAdapter, createAdapter, nativePolyfillHash, withPolyfill };
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,19 +1,77 @@
|
|
|
1
|
-
import{a as e,r as t,u as n}from"../utils-
|
|
1
|
+
import{a as e,r as t,u as n}from"../utils-yayEbXmr.js";import{raw as r,renderToString as i}from"@vincle/core";import{Fragment as a,jsx as o,jsxs as s}from"@vincle/core/jsx-runtime";import{escapeAttr as c}from"@vincle/core/html";const l={streaming:!0,merges:n};function u(t){let n={...t,capabilities:t.capabilities??l};return e(n,`createAdapter`),n}const d={replace:`replace`,append:`append`,prepend:`prepend`,before:`before`,after:`after`,morph:`replace`},f=u({Placeholder:function({id:e,src:t,children:n}){return t?/* @__PURE__ */ o(`turbo-frame`,{id:e,src:t,children:n}):/* @__PURE__ */ o(`turbo-frame`,{id:e,children:n})},Patch:({id:e,children:t,merge:n})=>/* @__PURE__ */ o(`turbo-stream`,{action:d[n],method:n===`morph`?`morph`:void 0,target:e,children:/* @__PURE__ */ o(`template`,{children:t})}),Frame:({id:e,children:t})=>/* @__PURE__ */ o(`turbo-frame`,{id:e,children:t})}),p=`(function(){
|
|
2
2
|
var ADJ={append:"beforeend",prepend:"afterbegin",before:"beforebegin",after:"afterend"};
|
|
3
|
+
var states=new WeakMap(),pending=new Set(),complete=new WeakSet();
|
|
4
|
+
var ready=document.readyState!=="loading";
|
|
5
|
+
function marker(node){
|
|
6
|
+
return node.nodeType===7?"?"+node.target+(node.data?" "+node.data:""):node.nodeValue;
|
|
7
|
+
}
|
|
3
8
|
function fill(name,frag){
|
|
4
|
-
var it=document.createNodeIterator(document.
|
|
5
|
-
while((nd=it.nextNode())){
|
|
6
|
-
if(
|
|
9
|
+
var it=document.createNodeIterator(document.documentElement,192),nd,start=null,end=null,depth=0;
|
|
10
|
+
while((nd=it.nextNode())){
|
|
11
|
+
if(marker(nd)==='?marker name="'+name+'"'){nd.before(frag);return true;}
|
|
12
|
+
if(marker(nd)==='?start name="'+name+'"'){start=nd;break;}
|
|
13
|
+
}
|
|
14
|
+
if(!start)return false;
|
|
15
|
+
for(nd=start.nextSibling;nd;nd=nd.nextSibling){
|
|
16
|
+
if(nd.nodeType!==8&&nd.nodeType!==7)continue;
|
|
17
|
+
if(marker(nd)==='?end'){if(!depth){end=nd;break;}depth--;}
|
|
18
|
+
else if(marker(nd).indexOf('?start ')===0)depth++;
|
|
19
|
+
}
|
|
20
|
+
if(!end)return false;
|
|
21
|
+
while(start.nextSibling!==end)start.nextSibling.remove();
|
|
22
|
+
end.before(frag);return true;
|
|
23
|
+
}
|
|
24
|
+
function apply(t,s){
|
|
25
|
+
if(!t.isConnected){if(s.controller)s.controller.abort();pending.delete(t);states.delete(t);return;}
|
|
26
|
+
if(s.wait||(!ready&&!complete.has(t)&&!t.nextSibling))return;
|
|
27
|
+
var name=(t.getAttribute("data-for")??t.getAttribute("for")),frag=s.fragment||t.content.cloneNode(true),merge=t.getAttribute("data-merge");
|
|
28
|
+
if(name){
|
|
29
|
+
if(merge&&merge!=="replace"){
|
|
30
|
+
var el=document.getElementById(name),position=ADJ[merge];
|
|
31
|
+
if(!el||!position)return;
|
|
32
|
+
if(position==="beforeend")el.append(frag);
|
|
33
|
+
else if(position==="afterbegin")el.prepend(frag);
|
|
34
|
+
else if(position==="beforebegin")el.before(frag);
|
|
35
|
+
else el.after(frag);
|
|
36
|
+
}else if(!fill(name,frag))return;
|
|
37
|
+
}else t.before(frag);
|
|
38
|
+
s.done=true;pending.delete(t);t.remove();
|
|
7
39
|
}
|
|
40
|
+
function flush(){pending.forEach(function(t){apply(t,states.get(t));});}
|
|
41
|
+
function fail(t,s){s.failed=true;if(states.get(t)===s)pending.delete(t);}
|
|
8
42
|
function run(t){
|
|
9
|
-
|
|
10
|
-
var
|
|
11
|
-
if(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
43
|
+
if(!t.isConnected)return;
|
|
44
|
+
var s=states.get(t);
|
|
45
|
+
if(s){if(!s.done&&!s.failed)apply(t,s);return;}
|
|
46
|
+
s={};states.set(t,s);pending.add(t);
|
|
47
|
+
var src=t.getAttribute("src");
|
|
48
|
+
if(src!==null){
|
|
49
|
+
try{var url=new URL(src,document.baseURI);if(src==="#blocked"||(url.protocol!=="http:"&&url.protocol!=="https:")){fail(t,s);return;}}
|
|
50
|
+
catch(e){fail(t,s);return;}
|
|
51
|
+
s.wait=true;s.controller=new AbortController();
|
|
52
|
+
var opts={signal:s.controller.signal},cross=t.getAttribute("crossorigin"),policy=t.getAttribute("referrerpolicy");
|
|
53
|
+
if(cross!==null){opts.mode="cors";opts.credentials=cross==="use-credentials"?"include":"same-origin";}
|
|
54
|
+
if(policy!==null)opts.referrerPolicy=policy;
|
|
55
|
+
fetch(src,opts).then(function(r){if(!r.ok){fail(t,s);return null;}return r.text();}).then(function(h){
|
|
56
|
+
if(h===null||states.get(t)!==s)return;
|
|
57
|
+
if(!t.isConnected){s.controller.abort();pending.delete(t);states.delete(t);return;}
|
|
58
|
+
var x=document.createElement("template");x.innerHTML=h;
|
|
59
|
+
x.content.querySelectorAll("template[for],template[data-for],template[src]").forEach(function(n){
|
|
60
|
+
if(n.hasAttribute("for")){n.setAttribute("data-for",n.getAttribute("for"));n.removeAttribute("for");}
|
|
61
|
+
complete.add(n);
|
|
62
|
+
});
|
|
63
|
+
s.fragment=x.content;s.wait=false;apply(t,s);
|
|
64
|
+
}).catch(function(){fail(t,s);});
|
|
65
|
+
}else apply(t,s);
|
|
66
|
+
}
|
|
67
|
+
function scan(root){
|
|
68
|
+
if(root.nodeName==="TEMPLATE"&&(root.hasAttribute("for")||root.hasAttribute("data-for")||root.hasAttribute("src")))run(root);
|
|
69
|
+
if(root.querySelectorAll)root.querySelectorAll("template[for],template[data-for],template[src]").forEach(run);
|
|
15
70
|
}
|
|
16
|
-
|
|
71
|
+
new MutationObserver(function(ms){
|
|
72
|
+
ms.forEach(function(m){m.addedNodes.forEach(scan);});flush();
|
|
73
|
+
}).observe(document.documentElement,{childList:true,subtree:true});
|
|
74
|
+
document.addEventListener("DOMContentLoaded",function(){ready=true;scan(document);flush();},{once:true});
|
|
17
75
|
scan(document);
|
|
18
|
-
new
|
|
19
|
-
|
|
76
|
+
})()`;async function m(){let e=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(p));return`sha256-${btoa(String.fromCharCode(...new Uint8Array(e)))}`}function h(e){let t=e.toLowerCase(),n=0,r=0,i=!1,a=[];for(;n<e.length;){let o=t.indexOf(`<`,n);if(o===-1)break;if(t.startsWith(`<!--`,o)){let e=t.indexOf(`-->`,o+4);if(e===-1)break;n=e+3;continue}let s=o+1,c=e[s]===`/`;c&&s++;let l=s;for(;s<e.length&&!g(e[s])&&e[s]!==`>`&&e[s]!==`/`;)s++;let u=t.slice(l,s),d=!1;for(;s<e.length&&e[s]!==`>`;){if(g(e[s])||e[s]===`/`){s++;continue}let n=s;for(;s<e.length&&!g(e[s])&&e[s]!==`=`&&e[s]!==`>`&&e[s]!==`/`;)s++;let r=t.slice(n,s);for((r===`src`||r===`for`||r===`data-for`)&&(d=!0),u===`template`&&!c&&r===`for`&&a.push(n);s<e.length&&g(e[s]);)s++;if(e[s]===`=`){for(s++;s<e.length&&g(e[s]);)s++;let t=e[s];if(t===`"`||t===`'`){let n=e.indexOf(t,s+1);if(n===-1)break;s=n+1}else for(;s<e.length&&!g(e[s])&&e[s]!==`>`;)s++}}if(n=s+1,u===`template`)c?r=Math.max(0,r-1):(r===0&&d&&(i=!0),r++);else if(!c&&[`script`,`style`,`textarea`,`title`,`xmp`,`iframe`,`noembed`,`noframes`,`noscript`].includes(u)){let e=t.indexOf(`</${u}`,n);for(;e!==-1;){let n=t[e+u.length+2];if(n===`>`||n===`/`||n!==void 0&&g(n))break;e=t.indexOf(`</${u}`,e+u.length+2)}if(e===-1)break;n=e}else if(!c&&u===`plaintext`)break}let o=0,s=[];for(let t of a)s.push(e.slice(o,t),`data-`),o=t;return s.push(e.slice(o)),{html:s.join(``),active:i}}const g=e=>e===` `||e===`
|
|
77
|
+
`||e===`\r`||e===` `||e===`\f`,_=u({capabilities:{streaming:!0,merges:[`replace`]},Placeholder:function({id:e,src:t,children:n}){let i=c(e);return t?/* @__PURE__ */ s(a,{children:[r(`<?start name="${i}">`),n,r(`<?end>`),/* @__PURE__ */ o(`template`,{for:e,src:t})]}):/* @__PURE__ */ s(a,{children:[r(`<?start name="${i}">`),n,r(`<?end>`)]})},Patch:({id:e,children:t,merge:n})=>n===`replace`?/* @__PURE__ */ o(`template`,{for:e,children:t}):/* @__PURE__ */ o(`template`,{for:e,"data-merge":n,children:t}),Frame:({children:e})=>/* @__PURE__ */ o(a,{children:e})}),v=[`replace`,`append`,`prepend`,`before`,`after`];function y(e){let n=async e=>r(h(await i(e)).html);return{...e,Placeholder:t=>n(e.Placeholder(t)),Patch:t=>n(e.Patch(t)),Frame:t=>n(e.Frame(t)),capabilities:{streaming:e.capabilities.streaming,merges:v},transformShell:(n,r)=>{let i=e.transformShell?e.transformShell(n,r):n,a=h(i);return r.fragments.size===0&&!a.active?i:t(a.html,String(/* @__PURE__ */ o(`script`,{children:p})))}}}const b=y(_),x={replace:`outerHTML`,append:`beforeend`,prepend:`afterbegin`,before:`beforebegin`,after:`afterend`,morph:`outerMorph`},S=u({Placeholder:function({id:e,src:t,children:n}){return t?/* @__PURE__ */ o(`div`,{id:e,"hx-get":t,"hx-trigger":`load`,"hx-swap":`outerHTML`,children:n}):/* @__PURE__ */ o(`div`,{id:e,children:n})},Patch:({id:e,children:t,merge:n})=>/* @__PURE__ */ o(`div`,{id:e,"hx-swap-oob":x[n],children:t}),Frame:({id:e,children:t})=>/* @__PURE__ */ o(`div`,{id:e,children:t})}),C=u({capabilities:{streaming:!1,merges:[`replace`]},Placeholder:({src:e,children:t})=>e?/* @__PURE__ */ o(a,{children:r(`<esi:include src="${c(e)}" />`)}):/* @__PURE__ */ o(a,{children:t}),Patch:({id:e,children:t})=>/* @__PURE__ */ s(a,{children:[r(`<esi:inline name="${c(e)}" fetchable="yes">`),t,r(`</esi:inline>`)]}),Frame:({children:e})=>/* @__PURE__ */ o(a,{children:e})});export{C as EsiAdapter,S as HtmxAdapter,p as NATIVE_POLYFILL,b as NativeAdapter,f as TurboAdapter,_ as WebPlatformAdapter,u as createAdapter,m as nativePolyfillHash,y as withPolyfill};
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e,t,u as n}from"../context-
|
|
1
|
+
import{l as e,t,u as n}from"../context-CLpgp0eJ.js";import{Scope as r}from"@vincle/core";import{jsx as i}from"@vincle/core/jsx-runtime";const a=e=>typeof e==`function`?e():e;function o(o){let{assets:s}=r.get(t),{name:c,media:l,children:u}=o,d={};return l!=null&&(d.media=l),n(s,c,{type:`style`,content:u,attrs:d}),e(s,c)?/* @__PURE__ */ i(`style`,{"data-name":c,...d,children:a(u)}):null}function s(o){let{assets:s}=r.get(t),{name:c,src:l,module:u,defer:d,children:f}=o,p={};l!=null&&(p.src=l),u&&(p.type=`module`),d&&(p.defer=!0);let m=f??``;return n(s,c,{type:`script`,content:m,attrs:p}),e(s,c)?/* @__PURE__ */ i(`script`,{"data-name":c,...p,children:a(m)}):null}export{s as Script,o as Style};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as e,c as t,f as n,i as r,m as i,o as a,p as o,t as s}from"./utils-yayEbXmr.js";import{Scope as c,renderToString as l}from"@vincle/core";function u(){return{entries:/* @__PURE__ */ new Map,emitted:/* @__PURE__ */ new Set,suppressed:!1}}function d(){return{entries:/* @__PURE__ */ new Map,emitted:/* @__PURE__ */ new Set,suppressed:!0}}function f(e,t,n){if(e.entries.has(t)){if(process.env.NODE_ENV!==`production`){let r=e.entries.get(t);(r.type!==n.type||JSON.stringify(r.attrs)!==JSON.stringify(n.attrs))&&console.warn(`[vincle/flow] Asset "${t}" is declared multiple times with different attributes. The first declaration wins.`)}return}e.entries.set(t,n)}function p(e,t){return e.suppressed||e.emitted.has(t)?!1:(e.emitted.add(t),!0)}function m(a){let c=/* @__PURE__ */ new Map,l=a.adapter?.capabilities.merges??[];return{register(u,d){if(s(u,`Defer`),t(d.timeout,`<Defer target="${u}">`),!a.adapter)throw e(`${r} <Defer target="${u}">: Defer requires an adapter: without one there is no placeholder to render and no patch to emit. Pass { adapter: ... } to renderToStatic, or render through renderToStream/serve with an adapter (TurboAdapter, NativeAdapter, HtmxAdapter, WebPlatformAdapter, EsiAdapter).`,i);if(!l.includes(d.merge)){let t=l.length>0?`it supports: ${l.join(`, `)}`:`it supports no merges (static output only)`;throw e(`${r} <Defer target="${u}" merge="${d.merge}">: merge="${d.merge}" is not supported by this adapter: ${t}. Pick one of those, or use an adapter that supports "${d.merge}".`,o)}if(c.has(u))throw e(`${r} <Defer target="${u}">: target is already registered: a target is registered once per render. Two <Defer> on the same target would emit two placeholders with the same DOM id, and only the second fragment would be patched. Give the second fragment a different target, or omit target to get a generated one.`,n);c.set(u,d)},get(e){return c.get(e)},outstanding(e){let t=[];for(let[n,r]of c)e.has(n)||t.push([n,r]);return t},hasOutstanding(e){for(let t of c.keys())if(!e.has(t))return!0;return!1},cursor(){let e=c.entries(),t=0;return{outstanding(){let n=[],r=c.size;for(;t<r;){let r=e.next();if(r.done)break;n.push(r.value),t++}return n},hasOutstanding:()=>t<c.size}},get size(){return c.size},clear(){c.clear()}}}const h=c.key(`@vincle/flow:flow`);function g(t,n,a){let{config:o}=c.get(h);if(!o.adapter)throw e(`${r} renderPlaceholder("${t}"): no adapter configured: a placeholder needs an adapter to emit its deferred-fragment markup. Pass { adapter: ... } to renderToStatic, or render through renderToStream() with an adapter.`,i);let s=a??(o.mode===`static`?o.generatePath(t):null);return o.adapter.Placeholder({id:t,src:s,children:n??null})}const _=()=>({names:/* @__PURE__ */ new Set,mark:void 0});function v(e,t){return e.mark??=`vincle-defer-${crypto.randomUUID()}`,[`<!--${e.mark}:${t}-->`,`<!--/${e.mark}:${t}-->`]}async function y(e){let t=await l(e);if(!t.includes(`<!--vincle-defer-`))return t;let{slots:n}=c.get(h),r=`<!--${n.mark}:`,i=[],a=[],o=0,s=t.indexOf(r,o);for(;;){let e=a.at(-1);if(e&&(s===-1||s>=e.start)){i.push(t.slice(o,e.start)),o=e.end,a.pop();continue}if(s===-1)break;let c=t.indexOf(`-->`,s),l=t.slice(s+r.length,c),u=`<!--/${n.mark}:${l}-->`,d=t.indexOf(u,c);if(d===-1)break;i.push(t.slice(o,s)),n.names.has(l)?o=d+u.length:(a.push({start:d,end:d+u.length}),o=c+3),s=t.indexOf(r,o)}return o===0?t:(i.push(t.slice(o)),i.join(``))}function b(e){a(e);let t=0,n=m(e),r=u();c.set(h,{config:e,fragments:n,assets:r,slots:_(),nextId:()=>`${e.idPrefix??`fragment-`}${++t}`,registerFragment(e,t){n.register(e,t)}})}function x(){let e=c.get(h);c.set(h,{...e,assets:u(),slots:_()})}function S(){let e=c.get(h);c.set(h,{...e,assets:d()})}function C(e,t){return c.with(async function(){return b(t),e(c.get(h))})}export{y as a,C as c,v as i,p as l,b as n,g as o,x as r,S as s,h as t,f as u};
|
package/dist/context.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as OnError, m as FlowConfig, p as DeferContent, y as MergeType } from "./index-
|
|
1
|
+
import { S as OnError, m as FlowConfig, p as DeferContent, y as MergeType } from "./index-CkR-2s9P.js";
|
|
2
2
|
import { JSX, ScopeKey, renderToString } from "@vincle/core";
|
|
3
3
|
//#region src/assets.d.ts
|
|
4
4
|
type AssetType = "style" | "script";
|
|
@@ -52,6 +52,11 @@ type FragmentStore = {
|
|
|
52
52
|
outstanding(processed: Set<string>): Array<[string, FragmentEntry]>;
|
|
53
53
|
/** True when at least one entry is not in `processed`. */
|
|
54
54
|
hasOutstanding(processed: Set<string>): boolean;
|
|
55
|
+
/** A per-drain cursor, valid until clear(): visits each registration once, including later generations. */
|
|
56
|
+
cursor(): {
|
|
57
|
+
outstanding(): Array<[string, FragmentEntry]>;
|
|
58
|
+
hasOutstanding(): boolean;
|
|
59
|
+
};
|
|
55
60
|
/** Total registered entries (including processed ones). */
|
|
56
61
|
readonly size: number;
|
|
57
62
|
/** Purge all entries to eagerly release closures and references. */
|
|
@@ -77,15 +82,14 @@ export interface FlowContext {
|
|
|
77
82
|
}
|
|
78
83
|
export declare const Flow: ScopeKey<FlowContext>;
|
|
79
84
|
/**
|
|
80
|
-
* The single adapter negotiation for deferred-fragment placeholders. Defer
|
|
81
|
-
*
|
|
82
|
-
* only their *policies* differ (what to register,
|
|
83
|
-
*
|
|
85
|
+
* The single adapter negotiation for deferred-fragment placeholders. Defer
|
|
86
|
+
* and Slot both end in `adapter.Placeholder({ id, src, children })`;
|
|
87
|
+
* only their *policies* differ (what to register, whether a missing adapter
|
|
88
|
+
* is an error). Everything about the negotiation lives here:
|
|
84
89
|
*
|
|
85
90
|
* - a missing adapter is an error (callers that want to tolerate it, e.g.
|
|
86
91
|
* Defer in pure-static mode, check `config.adapter` themselves first);
|
|
87
|
-
* - in static mode `src` defaults to `generatePath(id)`;
|
|
88
|
-
* their own URL (Include) pass it explicitly;
|
|
92
|
+
* - in static mode `src` defaults to `generatePath(id)`;
|
|
89
93
|
* - children are normalized to `null`.
|
|
90
94
|
*/
|
|
91
95
|
export declare function renderPlaceholder(id: string, children?: JSX.Element | null, src?: string): JSX.Element;
|
package/dist/context.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./context-
|
|
1
|
+
import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./context-CLpgp0eJ.js";export{s as Flow,r as initFlow,a as initFlowAssets,n as pendingMarks,e as renderFlow,i as renderPlaceholder,o as suppressFlowAssets,t as withFlow};
|
package/dist/http.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as FlowOptions, b as Negotiate, w as StreamingAdapter, x as Negotiation } from "./index-
|
|
1
|
+
import { _ as FlowOptions, b as Negotiate, w as StreamingAdapter, x as Negotiation } from "./index-CkR-2s9P.js";
|
|
2
2
|
import { JSX } from "@vincle/core";
|
|
3
3
|
//#region src/http.d.ts
|
|
4
4
|
/**
|
package/dist/http.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./render-
|
|
1
|
+
import{n as e}from"./render-BkqdRPBk.js";function t(e,t){let n=/* @__PURE__ */ new Set,r=[],i=e=>{if(e)for(let t of e.split(`,`)){let e=t.trim(),i=e.toLowerCase();e&&!n.has(i)&&(n.add(i),r.push(e))}};i(e.get(`vary`)),i(t),e.set(`vary`,r.join(`, `))}function n(e,n,r){let i=new Headers(e);for(let e of[n,r])for(let[n,r]of new Headers(e??{}))n===`vary`?t(i,r):i.set(n,r);return i}async function r(t,r,i,a){let o=a?.negotiate?.(t)??{},s=e(()=>r(o),i,{...a,signal:a?.signal?AbortSignal.any([t.signal,a.signal]):t.signal,mode:a?.mode??o.mode}).pipeThrough(new TextEncoderStream),c=n({"content-type":`text/html; charset=utf-8`},a?.headers,o.headers);return new Response(s,{...a,headers:c})}function i(e){return{headers:{Vary:`HX-Target`},target:e.headers.get(`HX-Target`)??void 0}}export{i as negotiateHtmx,r as serve};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./jsx-augment-Da8Va85F.js";
|
|
1
2
|
import { Awaitable, JSX, RawString } from "@vincle/core";
|
|
2
3
|
//#region src/types.d.ts
|
|
3
4
|
declare const ALL_MERGES: readonly ["replace", "append", "prepend", "before", "after", "morph"];
|
|
@@ -125,7 +126,8 @@ declare const TurboAdapter: Adapter & {
|
|
|
125
126
|
};
|
|
126
127
|
//#endregion
|
|
127
128
|
//#region src/adapters/native-polyfill.d.ts
|
|
128
|
-
|
|
129
|
+
/** Shared runtime for native-format includes and streamed patches. */
|
|
130
|
+
declare const NATIVE_POLYFILL = "(function(){\nvar ADJ={append:\"beforeend\",prepend:\"afterbegin\",before:\"beforebegin\",after:\"afterend\"};\nvar states=new WeakMap(),pending=new Set(),complete=new WeakSet();\nvar ready=document.readyState!==\"loading\";\nfunction marker(node){\n return node.nodeType===7?\"?\"+node.target+(node.data?\" \"+node.data:\"\"):node.nodeValue;\n}\nfunction fill(name,frag){\n var it=document.createNodeIterator(document.documentElement,192),nd,start=null,end=null,depth=0;\n while((nd=it.nextNode())){\n if(marker(nd)==='?marker name=\"'+name+'\"'){nd.before(frag);return true;}\n if(marker(nd)==='?start name=\"'+name+'\"'){start=nd;break;}\n }\n if(!start)return false;\n for(nd=start.nextSibling;nd;nd=nd.nextSibling){\n if(nd.nodeType!==8&&nd.nodeType!==7)continue;\n if(marker(nd)==='?end'){if(!depth){end=nd;break;}depth--;}\n else if(marker(nd).indexOf('?start ')===0)depth++;\n }\n if(!end)return false;\n while(start.nextSibling!==end)start.nextSibling.remove();\n end.before(frag);return true;\n}\nfunction apply(t,s){\n if(!t.isConnected){if(s.controller)s.controller.abort();pending.delete(t);states.delete(t);return;}\n if(s.wait||(!ready&&!complete.has(t)&&!t.nextSibling))return;\n var name=(t.getAttribute(\"data-for\")??t.getAttribute(\"for\")),frag=s.fragment||t.content.cloneNode(true),merge=t.getAttribute(\"data-merge\");\n if(name){\n if(merge&&merge!==\"replace\"){\n var el=document.getElementById(name),position=ADJ[merge];\n if(!el||!position)return;\n if(position===\"beforeend\")el.append(frag);\n else if(position===\"afterbegin\")el.prepend(frag);\n else if(position===\"beforebegin\")el.before(frag);\n else el.after(frag);\n }else if(!fill(name,frag))return;\n }else t.before(frag);\n s.done=true;pending.delete(t);t.remove();\n}\nfunction flush(){pending.forEach(function(t){apply(t,states.get(t));});}\nfunction fail(t,s){s.failed=true;if(states.get(t)===s)pending.delete(t);}\nfunction run(t){\n if(!t.isConnected)return;\n var s=states.get(t);\n if(s){if(!s.done&&!s.failed)apply(t,s);return;}\n s={};states.set(t,s);pending.add(t);\n var src=t.getAttribute(\"src\");\n if(src!==null){\n try{var url=new URL(src,document.baseURI);if(src===\"#blocked\"||(url.protocol!==\"http:\"&&url.protocol!==\"https:\")){fail(t,s);return;}}\n catch(e){fail(t,s);return;}\n s.wait=true;s.controller=new AbortController();\n var opts={signal:s.controller.signal},cross=t.getAttribute(\"crossorigin\"),policy=t.getAttribute(\"referrerpolicy\");\n if(cross!==null){opts.mode=\"cors\";opts.credentials=cross===\"use-credentials\"?\"include\":\"same-origin\";}\n if(policy!==null)opts.referrerPolicy=policy;\n fetch(src,opts).then(function(r){if(!r.ok){fail(t,s);return null;}return r.text();}).then(function(h){\n if(h===null||states.get(t)!==s)return;\n if(!t.isConnected){s.controller.abort();pending.delete(t);states.delete(t);return;}\n var x=document.createElement(\"template\");x.innerHTML=h;\n x.content.querySelectorAll(\"template[for],template[data-for],template[src]\").forEach(function(n){\n if(n.hasAttribute(\"for\")){n.setAttribute(\"data-for\",n.getAttribute(\"for\"));n.removeAttribute(\"for\");}\n complete.add(n);\n });\n s.fragment=x.content;s.wait=false;apply(t,s);\n }).catch(function(){fail(t,s);});\n }else apply(t,s);\n}\nfunction scan(root){\n if(root.nodeName===\"TEMPLATE\"&&(root.hasAttribute(\"for\")||root.hasAttribute(\"data-for\")||root.hasAttribute(\"src\")))run(root);\n if(root.querySelectorAll)root.querySelectorAll(\"template[for],template[data-for],template[src]\").forEach(run);\n}\nnew MutationObserver(function(ms){\n ms.forEach(function(m){m.addedNodes.forEach(scan);});flush();\n}).observe(document.documentElement,{childList:true,subtree:true});\ndocument.addEventListener(\"DOMContentLoaded\",function(){ready=true;scan(document);flush();},{once:true});\nscan(document);\n})()";
|
|
129
131
|
declare function nativePolyfillHash(): Promise<string>;
|
|
130
132
|
//#endregion
|
|
131
133
|
//#region src/adapters/native.d.ts
|
|
@@ -150,9 +152,9 @@ declare const WebPlatformAdapter: Adapter & {
|
|
|
150
152
|
*/
|
|
151
153
|
declare const POLYFILL_MERGES: readonly ["replace", "append", "prepend", "before", "after"];
|
|
152
154
|
/**
|
|
153
|
-
* Decorate
|
|
154
|
-
*
|
|
155
|
-
*
|
|
155
|
+
* Decorate a WICG-format adapter with the inline polyfill. Its templates use
|
|
156
|
+
* `data-for` so partially implemented native patching cannot consume them first.
|
|
157
|
+
* The polyfill is injected when fragments or active templates are present.
|
|
156
158
|
*
|
|
157
159
|
* Useful when you want to use `WebPlatformAdapter` in browsers that do
|
|
158
160
|
* not yet support `<template for>` natively.
|
|
@@ -163,7 +165,7 @@ declare function withPolyfill<T extends Adapter>(adapter: T): Omit<T, "capabilit
|
|
|
163
165
|
merges: typeof POLYFILL_MERGES;
|
|
164
166
|
};
|
|
165
167
|
};
|
|
166
|
-
/** Default Native adapter: WICG format + inline polyfill
|
|
168
|
+
/** Default Native adapter: WICG format + inline polyfill. */
|
|
167
169
|
declare const NativeAdapter: Omit<Adapter & {
|
|
168
170
|
capabilities: {
|
|
169
171
|
readonly streaming: true;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import "./jsx-augment-Da8Va85F.js";
|
|
2
|
+
import { C as Shell, S as OnError, _ as FlowOptions, b as Negotiate, f as AdapterCapabilities, g as FlowEvent, h as FlowErrorInfo, l as Adapter, m as FlowConfig, p as DeferContent, v as Fragment, w as StreamingAdapter, x as Negotiation, y as MergeType } from "./index-CkR-2s9P.js";
|
|
3
|
+
import { JSX } from "@vincle/core";
|
|
3
4
|
//#region src/components/Slot.d.ts
|
|
4
5
|
interface SlotProps {
|
|
5
6
|
name: string;
|
|
@@ -18,17 +19,6 @@ interface DeferProps {
|
|
|
18
19
|
}
|
|
19
20
|
export declare function Defer(props: DeferProps): JSX.Element | null;
|
|
20
21
|
//#endregion
|
|
21
|
-
//#region src/components/Include.d.ts
|
|
22
|
-
type SchemeOf<S extends string> = S extends `${infer Head}:${string}` ? Head extends `${string}${"/" | "?" | "#"}${string}` ? null : Head extends "" ? null : Head : null;
|
|
23
|
-
type FetchUrl<S extends string> = SchemeOf<S> extends null ? S : Lowercase<SchemeOf<S> & string> extends "http" | "https" ? S : {
|
|
24
|
-
__error: "Include needs an HTML URL: only http(s): or a relative path";
|
|
25
|
-
};
|
|
26
|
-
interface IncludeProps<S extends string = string> {
|
|
27
|
-
src: S & FetchUrl<S>;
|
|
28
|
-
fallback?: VNode;
|
|
29
|
-
}
|
|
30
|
-
export declare function Include<const S extends string>(props: IncludeProps<S>): JSX.Element;
|
|
31
|
-
//#endregion
|
|
32
22
|
//#region src/render.d.ts
|
|
33
23
|
/**
|
|
34
24
|
* Return a `ReadableStream<FlowEvent>` with proper backpressure and cancellation.
|
|
@@ -108,8 +98,8 @@ interface RenderFragmentOptions {
|
|
|
108
98
|
/**
|
|
109
99
|
* Render a single fragment on demand, outside a full site build: given
|
|
110
100
|
* fresh content, produces the exact bytes `emitFragments` would have written
|
|
111
|
-
* for this `id` during a full build, at the same URL. The page
|
|
112
|
-
*
|
|
101
|
+
* for this `id` during a full build, at the same URL. The page whose
|
|
102
|
+
* placeholder points at it never needs rebuilding.
|
|
113
103
|
*
|
|
114
104
|
* Framework- and host-agnostic: write `html` to `url` yourself, or hand both
|
|
115
105
|
* to whatever partial-update mechanism your host offers (Netlify Blobs,
|
|
@@ -120,4 +110,4 @@ export declare function renderFragment(id: string, content: DeferContent, opts:
|
|
|
120
110
|
html: string;
|
|
121
111
|
}>;
|
|
122
112
|
//#endregion
|
|
123
|
-
export type { AdapterCapabilities, DeferContent, DeferProps, FlowConfig, FlowErrorInfo, FlowEvent, FlowOptions, Fragment,
|
|
113
|
+
export type { AdapterCapabilities, DeferContent, DeferProps, FlowConfig, FlowErrorInfo, FlowEvent, FlowOptions, Fragment, MergeType, Negotiate, Negotiation, OnError, PureStaticContext, RenderFragmentOptions, Shell, SlotProps, StaticContext, StaticOptions, StreamingAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,o as r,r as i,s as a,t as o}from"./context-
|
|
1
|
+
import{a as e,c as t,i as n,o as r,r as i,s as a,t as o}from"./context-CLpgp0eJ.js";import{_ as s,a as c,d as l,h as u,i as d,l as f,m as p,s as m}from"./utils-yayEbXmr.js";import{n as h,r as g,t as _}from"./render-BkqdRPBk.js";import{Scope as v,raw as y,renderToString as b,snapshotContext as x}from"@vincle/core";import{Fragment as S,jsx as C,jsxs as w}from"@vincle/core/jsx-runtime";function T(e){let{name:t,children:n}=e;return v.get(o).slots.names.add(t),r(t,n)}function E(e){let{registerFragment:t,nextId:i,slots:a}=v.get(o),{children:s,merge:c,timeout:l,onError:u,fallback:d}=e,f=e.target??i();if(t(f,{content:s,treeScope:x(),merge:c??`replace`,timeout:l,onError:u,fallback:d}),a.names.has(f))return null;let p=r(f,d);if(e.target===void 0)return p;let[m,h]=n(a,f);return/* @__PURE__ */ w(S,{children:[y(m),p,y(h)]})}const D=e=>`/fragments/${e}.html`;function O(t,n,r){return{renderPage:r=>v.with(async()=>{i();let a=await e(r());return n?.transformShell?n.transformShell(a,t):a},v.snapshot()),emitFragments:async e=>{if(!n)throw s(`${d} emitFragments(): emitFragments requires an adapter: fragments cannot be framed into standalone files without one. Pass { adapter: ... } to renderToStatic. Example: renderToStatic(handler, { adapter: NativeAdapter })`,p);await v.with(async()=>{a(),await g(t,async t=>{if(t.type===`fragment`){let i=await b(n.Frame({id:t.id,children:y(t.html)}));await e(t.id,r(t.id),i)}})},v.snapshot()),t.fragments.clear()}}}async function k(e,n){let r=n?.adapter,i=n?.generatePath??D;if(c(r,`renderToStatic`),n?.generatePath!==void 0&&typeof n.generatePath!=`function`)throw s(`${d} renderToStatic: generatePath must be a function (id) => string, got ${f(n.generatePath)}. Example: (id) => \`/fragments/\${id}.html\`.`,l);return t(t=>e(O(t,r,i)),{adapter:r,mode:`static`,generatePath:i})}async function A(e,t,n){if(n.adapter===void 0)throw s(`${d} renderFragment: opts.adapter is required: pass the same adapter the site's full build uses (e.g. TurboAdapter).`,p);c(n.adapter,`renderFragment`),m(n,`renderFragment`);let r;if(await k(async i=>{await i.renderPage(()=>/* @__PURE__ */ C(E,{target:e,timeout:n.timeout,onError:n.onError,children:t})),await i.emitFragments((t,n,i)=>{t===e&&(r={url:n,html:i})})},{adapter:n.adapter,generatePath:n.generatePath}),!r)throw s(`${d} renderFragment("${e}"): produced no output for this id. The fragment was never registered or rendered: its content may have thrown or timed out (check the onError/console log), or the id differs from the <Defer target> that was rendered. Verify the id matches and that the content renders.`,u);return r}export{E as Defer,T as Slot,A as renderFragment,_ as renderToFlowEvents,k as renderToStatic,h as renderToStream};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Awaitable, JSX, RawString, RawUrl, Renderable } from "@vincle/core";
|
|
2
|
+
//#region src/jsx-augment.d.ts
|
|
3
|
+
/** Experimental WICG Fragment Include attributes, enabled by Flow. */
|
|
4
|
+
interface FragmentIncludeAttributes {
|
|
5
|
+
src?: Awaitable<string | undefined | RawString | RawUrl>;
|
|
6
|
+
buffer?: Awaitable<boolean | undefined>;
|
|
7
|
+
sanitize?: Awaitable<boolean | "" | "unsafe" | undefined>;
|
|
8
|
+
crossOrigin?: JSX.ImgHTMLAttributes["crossOrigin"];
|
|
9
|
+
crossorigin?: JSX.ImgHTMLAttributes["crossorigin"];
|
|
10
|
+
referrerPolicy?: JSX.ImgHTMLAttributes["referrerPolicy"];
|
|
11
|
+
referrerpolicy?: JSX.ImgHTMLAttributes["referrerpolicy"];
|
|
12
|
+
}
|
|
13
|
+
interface FlowIntrinsicElements {
|
|
14
|
+
"turbo-frame": {
|
|
15
|
+
id?: Awaitable<string | number> | undefined;
|
|
16
|
+
src?: Awaitable<string> | undefined;
|
|
17
|
+
target?: Awaitable<string> | undefined;
|
|
18
|
+
children?: Renderable;
|
|
19
|
+
};
|
|
20
|
+
"turbo-stream": {
|
|
21
|
+
action?: Awaitable<string> | undefined;
|
|
22
|
+
method?: Awaitable<string> | undefined;
|
|
23
|
+
target?: Awaitable<string> | undefined;
|
|
24
|
+
children?: Renderable;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
declare module "@vincle/core" {
|
|
28
|
+
namespace JSX {
|
|
29
|
+
interface TemplateHTMLAttributes extends FragmentIncludeAttributes {}
|
|
30
|
+
interface IntrinsicElements extends FlowIntrinsicElements {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
declare module "@vincle/core/jsx-runtime" {
|
|
34
|
+
namespace JSX {
|
|
35
|
+
interface TemplateHTMLAttributes extends FragmentIncludeAttributes {}
|
|
36
|
+
interface IntrinsicElements extends FlowIntrinsicElements {}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
declare module "@vincle/core/jsx-dev-runtime" {
|
|
40
|
+
namespace JSX {
|
|
41
|
+
interface TemplateHTMLAttributes extends FragmentIncludeAttributes {}
|
|
42
|
+
interface IntrinsicElements extends FlowIntrinsicElements {}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{a as e,c as t}from"./context-CLpgp0eJ.js";import{_ as n,a as r,g as i,i as a,s as o}from"./utils-yayEbXmr.js";import{raw as s,renderToString as c}from"@vincle/core";import{isAsyncIterable as l}from"@vincle/core/html";function u(e,t){let n=new AbortController,r=t?.signal?AbortSignal.any([t.signal,n.signal]):n.signal,i,a=[],o=()=>{for(let e of a.splice(0))e()};r.addEventListener(`abort`,o,{once:!0});let s=async e=>{if(!r.aborted){for(;(i.desiredSize??1)<=0&&!r.aborted;)await new Promise(e=>a.push(e));r.aborted||i.enqueue(e)}};return new ReadableStream({start(t){i=t,e(s,r).then(()=>{try{t.close()}catch{}},e=>{try{t.error(e)}catch{}})},pull(){o()},cancel(e){n.abort(e)}})}function d(e,t,n){let r=e;if(r==null)return{signal:t??new AbortController().signal,cleanup:()=>{}};let i=new AbortController,o=setTimeout(()=>i.abort(/* @__PURE__ */ Error(`${a} Fragment "${n}" timed out after ${r}ms: its content did not finish in time. Increase the timeout (the fragment's timeout prop or defaultTimeout), or make the content finish faster, e.g. by forwarding the abort signal to fetch().`)),r);return{signal:t?AbortSignal.any([t,i.signal]):i.signal,cleanup:()=>clearTimeout(o)}}const f=e=>typeof e==`function`;function p(e,t){try{let n=f(e.content)?e.content(t):e.content;return l(n)?{kind:`stream`,iterable:n}:{kind:`value`,value:n}}catch(e){return{kind:`sync-error`,error:e}}}async function m(e,t){let n,r=new Promise((e,r)=>{n=()=>r(t.reason),t.aborted?n():t.addEventListener(`abort`,n,{once:!0})});try{return await Promise.race([e,r])}finally{t.removeEventListener(`abort`,n)}}async function h(t,n,r,i,a){console.error(`[vincle/flow] Error rendering ${i} "${r}"`,a);let o=n?.(a,{id:r,kind:i});o!=null&&await t({type:`fragment`,id:r,html:await e(o),merge:`replace`})}async function g(e,t,n,r,i){try{await h(e,t,n,r,i)}catch{throw i}}function _(e,t,n,r){return t.treeScope?t.treeScope(()=>v(e,t,n,r)):v(e,t,n,r)}function v(e,t,n,r){let i=t.onError??r.onError,{signal:a,cleanup:o}=d(t.timeout??r.defaultTimeout,r.signal,e),s=p(t,a);switch(s.kind){case`sync-error`:return o(),{isStreaming:!1,done:h(n,i,e,`fragment`,s.error)};case`stream`:return{isStreaming:!0,done:b(e,s.iterable,t.merge,n,i,a).finally(o)};case`value`:return{isStreaming:!1,done:y(e,s.value,t.merge,n,i,a,o)}}}async function y(t,n,r,i,a,o,s){let c;try{c=await m(e(n),o)}catch(e){await g(i,a,t,`fragment`,e);return}finally{s()}if(o.aborted){await g(i,a,t,`fragment`,o.reason);return}try{await i({type:`fragment`,id:t,html:c,merge:r})}catch(e){throw console.error(`[vincle/flow] Failed to emit fragment "${t}"`,e),e}}async function b(t,n,r,i,a,o){let s=n[Symbol.asyncIterator](),c=!1,l=!1;try{for(;!o.aborted;){let n=await m(Promise.resolve(s.next()),o);if(n.done){l=!0;break}let u;try{u=await m(e(n.value),o)}catch(e){if(await g(i,a,t,`stream`,e),o.aborted)break;continue}try{await i({type:`fragment`,id:t,html:u,merge:r})}catch(e){throw console.error(`[vincle/flow] Failed to emit stream chunk for "${t}"`,e),c=!0,e}}}catch(e){if(c)throw e;await g(i,a,t,`stream`,e)}finally{if(!l)try{let e=Promise.resolve(s.return?.(void 0)).catch(()=>{});o.aborted||await e}catch{}}}async function x(e){if(e.length===0)return;let t=await Promise.allSettled(e);for(let e of t)if(e.status===`rejected`)throw e.reason}function S(e){let t=Promise.resolve();return{emit:n=>{let r=t.then(()=>e(n));return t=r.catch(()=>{}),r},drain:()=>t}}async function C(e,t,n={}){let r=e.fragments.cursor(),i=[],a=S(t);for(;!n.signal?.aborted;){let e=r.outstanding();if(e.length>0){let t=[];for(let[r,o]of e){let{isStreaming:e,done:s}=_(r,o,a.emit,n);(e?i:t).push(s)}await x(t);continue}if(i.length===0||(await x(i),i.length=0,!r.hasOutstanding()))break}i.length>0&&await x(i),await a.drain()}function w(e){let t=e.trimEnd();if(!t.endsWith(`</html>`))return{body:e,closingTag:``};let n=t.slice(0,-7).trimEnd(),r=n.endsWith(`</body>`)?n.length-7:n.length;return{body:e.slice(0,r),closingTag:e.slice(r)}}function T(e){let t=!1;return new TransformStream({async transform(n,r){if(n.type===`shell`){t=!0,r.enqueue(n.html);return}let i=``;if(t&&(t=!1,i=`
|
|
2
|
+
`),n.type===`fragment`){let t=await c(e.Patch({id:n.id,children:s(n.html),merge:n.merge}));r.enqueue(i+t+`
|
|
3
|
+
`)}else r.enqueue(i+n.html+`
|
|
4
|
+
`)},flush(e){t&&e.enqueue(`
|
|
5
|
+
`)}})}async function E(t,n,r){let{body:i,closingTag:a}=w(await e(t()));return{shellBody:n.transformShell?n.transformShell(i,r):i,closingTag:a}}async function D(e,n,r,i,a){await t(async t=>{let{fragments:o}=t;try{if(n.aborted)return;let{shellBody:s,closingTag:c}=await E(r,i,t);a.mode!==`fragment`&&s!==``&&await e({type:`shell`,html:s}),await C({fragments:o},e,{...a,signal:n}),a.mode!==`fragment`&&c&&await e({type:`close`,html:c})}finally{o.clear()}},{adapter:i,mode:`streaming`})}function O(e,t,n){o(n,e),r(t,e)}function k(e,t,n={}){return O(`renderToFlowEvents`,t,n),u((r,i)=>D(r,i,e,t,n),{signal:n.signal})}function A(e,t,r){if(O(`renderToStream`,t,r),t.capabilities.streaming!==!0)throw n(`[vincle/flow] renderToStream(): this adapter does not stream: capabilities.streaming is false, so it can only produce static output. Use renderToStatic() with this adapter, or pass a streaming adapter (TurboAdapter, NativeAdapter, HtmxAdapter, WebPlatformAdapter).`,i);return k(e,t,r).pipeThrough(T(t))}export{A as n,C as r,k as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=`ERR_VINCLE_FLOW_CONFIG`,t=`ERR_VINCLE_FLOW_NO_ADAPTER`,n=`ERR_VINCLE_FLOW_MERGE_UNSUPPORTED`,r=`ERR_VINCLE_FLOW_NO_FRAGMENT`,i=`ERR_VINCLE_FLOW_NO_STREAMING`,a=`ERR_VINCLE_FLOW_DUP_FRAGMENT`;function o(e,t){let n=Error(e);return n.code=t,Error.captureStackTrace?.(n,o),n}const s=[`replace`,`append`,`prepend`,`before`,`after`,`morph`],c=`[vincle/flow]`;function l(e){if(e===null)return`null`;if(typeof e==`string`)return JSON.stringify(e.length>80?e.slice(0,80)+`…`:e);if(typeof e==`number`||typeof e==`bigint`||typeof e==`boolean`)return String(e);if(typeof e==`function`)return`function ${e.name||`<anonymous>`}`;if(e instanceof Error)return`${e.name}: ${e.message}`;try{let t=JSON.stringify(e);return t===void 0?String(e):t.length>100?t.slice(0,100)+`…`:t}catch{return String(e)}}function u(e){return e instanceof AbortSignal||typeof e==`object`&&!!e&&typeof e.aborted==`boolean`&&typeof e.addEventListener==`function`&&typeof e.onabort==`object`}function d(t,n){if(t!=null){if(typeof t!=`object`)throw o(`${c} ${n}: options must be an object, got ${l(t)}. Example: { defaultTimeout: 5000, onError, signal }`,e);if(t.defaultTimeout!==void 0&&(!Number.isFinite(t.defaultTimeout)||t.defaultTimeout<0))throw o(`${c} ${n}: defaultTimeout must be a number of milliseconds >= 0, got ${l(t.defaultTimeout)}. Pass e.g. defaultTimeout: 5000, or omit it for no limit.`,e);if(t.onError!==void 0&&typeof t.onError!=`function`)throw o(`${c} ${n}: onError must be a function (error, { id, kind }) => JSX.Element | void, got ${l(t.onError)}. Omit it to log errors to the console instead.`,e);if(t.signal!==void 0&&!u(t.signal))throw o(`${c} ${n}: signal must be an AbortSignal, got ${l(t.signal)}. Example: const controller = new AbortController(); … { signal: controller.signal }`,e)}}const f=[`Placeholder`,`Patch`,`Frame`];function p(t,n){if(t===void 0)return;if(typeof t!=`object`||!t)throw o(`${c} ${n}: adapter must be an adapter object, got ${l(t)}. Use a built-in (TurboAdapter, NativeAdapter, HtmxAdapter, WebPlatformAdapter, EsiAdapter) or createAdapter().`,e);let r=t,i=f.filter(e=>typeof r[e]!=`function`);if(i.length>0)throw o(`${c} ${n}: the adapter is missing ${i.map(e=>`"${e}"`).join(`, `)}: an adapter needs Placeholder, Patch and Frame. Use createAdapter() or a built-in adapter.`,e);let a=r.capabilities;if(typeof a!=`object`||!a)throw o(`${c} ${n}: the adapter is missing capabilities: declare { streaming: boolean, merges: MergeType[] } (see createAdapter()).`,e);if(typeof a.streaming!=`boolean`)throw o(`${c} ${n}: adapter.capabilities.streaming must be a boolean, got ${l(a.streaming)}.`,e);let u=a.merges;if(!(Array.isArray(u)&&u.every(e=>typeof e==`string`&&s.includes(e))))throw o(`${c} ${n}: adapter.capabilities.merges must be an array of merge types (${s.join(`, `)}), got ${l(u)}.`,e);if(r.transformShell!==void 0&&typeof r.transformShell!=`function`)throw o(`${c} ${n}: adapter.transformShell must be a function (shell, ctx) => string, got ${l(r.transformShell)}.`,e)}function m(t){if(typeof t!=`object`||!t)throw o(`${c} FlowConfig: the flow config must be an object, got ${l(t)}. Example: { adapter: TurboAdapter, mode: "streaming" }`,e);let n=t,r=n.mode;if(r!==`streaming`&&r!==`static`)throw o(`${c} FlowConfig.mode must be "streaming" or "static", got ${l(r)}.`,e);let i=n.idPrefix;if(i!==void 0&&typeof i!=`string`)throw o(`${c} FlowConfig.idPrefix must be a string, got ${l(i)}. Default: "fragment-".`,e);let a=n.generatePath;if(a!==void 0&&typeof a!=`function`)throw o(`${c} FlowConfig.generatePath must be a function (id) => string, got ${l(a)}. Example: (id) => \`/fragments/\${id}.html\`.`,e);if(r===`static`){if(a===void 0)throw o(`${c} FlowConfig: static mode requires generatePath: (id) => string. Example: generatePath: (id) => \`/fragments/\${id}.html\`.`,e)}else if(a!==void 0)throw o(`${c} FlowConfig: generatePath is only used in static mode (renderToStatic): remove it from the streaming config, or drop mode: "streaming" if you meant static generation.`,e);p(n.adapter,`FlowConfig`)}function h(t,n){if(t!==void 0&&(!Number.isFinite(t)||t<0))throw o(`${c} ${n}: timeout must be a number of milliseconds >= 0, got ${l(t)}. Pass e.g. timeout={5000}, or omit it to use defaultTimeout.`,e)}const g=/^[a-zA-Z][a-zA-Z0-9_-]*$/;function _(e,t){if(!g.test(e))throw o(`${c} ${t}: "${e}" is not a valid fragment id: ids must start with a letter and contain only letters, digits, hyphens and underscores (e.g. "price-aapl"). Fragment ids become DOM ids and URL segments.`,`ERR_VINCLE_FLOW_FRAGMENT_ID`)}function v(e,t){let n=e.match(/<\/head\s*>/i);if(n){let r=n.index;return e.slice(0,r)+t+e.slice(r)}let r=`<head>${t}</head>`,i=e.match(/<html\b[^>]*>/i);if(i)return y(e,i.index+i[0].length,r);let a=e.match(/^\s*<!doctype\b[^>]*>/i);return a?y(e,a[0].length,r):r+e}const y=(e,t,n)=>e.slice(0,t)+n+e.slice(t);function b(...e){let t=e.filter(Boolean);return(e,n)=>t.reduce((e,t)=>t(e,n),e)}export{o as _,p as a,h as c,e as d,a as f,i as g,r as h,c as i,l,t as m,b as n,m as o,n as p,v as r,d as s,_ as t,s as u};
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,r as t,t as n}from"./utils-
|
|
1
|
+
import{n as e,r as t,t as n}from"./utils-yayEbXmr.js";export{n as assertFragmentId,e as composeShell,t as injectIntoHead};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincle/flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "HTML document orchestration layer for @vincle/core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deferred",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
30
|
"README.md",
|
|
31
|
-
"LICENSE"
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"CHANGELOG.md"
|
|
32
33
|
],
|
|
33
34
|
"type": "module",
|
|
34
35
|
"sideEffects": false,
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
},
|
|
65
66
|
"scripts": {
|
|
66
67
|
"build": "tsdown",
|
|
67
|
-
"check": "tsc --noEmit",
|
|
68
|
+
"check": "tsc --noEmit && tsc --noEmit -p tsconfig.tests.json",
|
|
68
69
|
"coverage": "bun test --coverage --coverage-reporter=lcov",
|
|
69
70
|
"format": "oxfmt",
|
|
70
71
|
"mutation": "stryker run",
|
|
@@ -73,13 +74,14 @@
|
|
|
73
74
|
"devDependencies": {
|
|
74
75
|
"@stryker-mutator/core": "^10.0.0",
|
|
75
76
|
"@types/bun": "^1.4.2",
|
|
76
|
-
"@vincle/core": "0.9.
|
|
77
|
+
"@vincle/core": "0.9.1",
|
|
78
|
+
"happy-dom": "^20.14.5",
|
|
77
79
|
"oxc-parser": "^0.150.0",
|
|
78
80
|
"tsdown": "^0.23.0",
|
|
79
81
|
"typescript": "^6.0.3"
|
|
80
82
|
},
|
|
81
83
|
"peerDependencies": {
|
|
82
|
-
"@vincle/core": "^0.9.
|
|
84
|
+
"@vincle/core": "^0.9.1"
|
|
83
85
|
},
|
|
84
86
|
"engines": {
|
|
85
87
|
"node": ">=22.0.0"
|
package/dist/context-DlzTTBsQ.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{c as e,f as t,h as n,i as r,m as i,o as a,t as o,v as s}from"./utils-DKRixAvn.js";import{Scope as c,renderToString as l}from"@vincle/core";function u(){return{entries:/* @__PURE__ */ new Map,emitted:/* @__PURE__ */ new Set,suppressed:!1}}function d(){return{entries:/* @__PURE__ */ new Map,emitted:/* @__PURE__ */ new Set,suppressed:!0}}function f(e,t,n){if(e.entries.has(t)){if(process.env.NODE_ENV!==`production`){let r=e.entries.get(t);(r.type!==n.type||JSON.stringify(r.attrs)!==JSON.stringify(n.attrs))&&console.warn(`[vincle/flow] Asset "${t}" is declared multiple times with different attributes. The first declaration wins.`)}return}e.entries.set(t,n)}function p(e,t){return e.suppressed||e.emitted.has(t)?!1:(e.emitted.add(t),!0)}function m(a){let c=/* @__PURE__ */ new Map,l=a.adapter?.capabilities.merges??[];return{register(u,d){if(o(u,`Defer`),e(d.timeout,`<Defer target="${u}">`),!a.adapter)throw s(`${r} <Defer target="${u}">: Defer requires an adapter: without one there is no placeholder to render and no patch to emit. Pass { adapter: ... } to renderToStatic, or render through renderToStream/serve with an adapter (TurboAdapter, NativeAdapter, HtmxAdapter, WebPlatformAdapter, EsiAdapter).`,n);if(!l.includes(d.merge)){let e=l.length>0?`it supports: ${l.join(`, `)}`:`it supports no merges (static output only)`;throw s(`${r} <Defer target="${u}" merge="${d.merge}">: merge="${d.merge}" is not supported by this adapter: ${e}. Pick one of those, or use an adapter that supports "${d.merge}".`,i)}if(c.has(u))throw s(`${r} <Defer target="${u}">: target is already registered: a target is registered once per render. Two <Defer> on the same target would emit two placeholders with the same DOM id, and only the second fragment would be patched. Give the second fragment a different target, or omit target to get a generated one.`,t);c.set(u,d)},get(e){return c.get(e)},outstanding(e){let t=[];for(let[n,r]of c)e.has(n)||t.push([n,r]);return t},hasOutstanding(e){for(let t of c.keys())if(!e.has(t))return!0;return!1},get size(){return c.size},clear(){c.clear()}}}const h=c.key(`@vincle/flow:flow`);function g(e,t,i){let{config:a}=c.get(h);if(!a.adapter)throw s(`${r} renderPlaceholder("${e}"): no adapter configured: a placeholder needs an adapter to emit its deferred-fragment markup. Pass { adapter: ... } to renderToStatic, or render through renderToStream() with an adapter.`,n);let o=i??(a.mode===`static`?a.generatePath(e):null);return a.adapter.Placeholder({id:e,src:o,children:t??null})}const _=()=>({names:/* @__PURE__ */ new Set,mark:void 0});function v(e,t){return e.mark??=`vincle-defer-${crypto.randomUUID()}`,[`<!--${e.mark}:${t}-->`,`<!--/${e.mark}:${t}-->`]}async function y(e){let t=await l(e);if(!t.includes(`<!--vincle-defer-`))return t;let{slots:n}=c.get(h),r=`<!--${n.mark}:`,i=0;for(;;){let e=t.indexOf(r,i);if(e===-1)return t;let a=t.indexOf(`-->`,e),o=t.slice(e+r.length,a),s=`<!--/${n.mark}:${o}-->`,c=t.indexOf(s,a);if(c===-1)return t;let l=n.names.has(o)?``:t.slice(a+3,c);t=t.slice(0,e)+l+t.slice(c+s.length),i=e}}function b(e){a(e);let t=0,n=m(e),r=u();c.set(h,{config:e,fragments:n,assets:r,slots:_(),nextId:()=>`${e.idPrefix??`fragment-`}${++t}`,registerFragment(e,t){n.register(e,t)}})}function x(){let e=c.get(h);c.set(h,{...e,assets:u(),slots:_()})}function S(){let e=c.get(h);c.set(h,{...e,assets:d()})}function C(e,t){return c.with(async function(){return b(t),e(c.get(h))})}export{y as a,C as c,v as i,p as l,b as n,g as o,x as r,S as s,h as t,f as u};
|
package/dist/render-zCgGmdz0.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{a as e,c as t}from"./context-DlzTTBsQ.js";import{_ as n,a as r,i,s as a,v as o}from"./utils-DKRixAvn.js";import{raw as s,renderToString as c}from"@vincle/core";import{isAsyncIterable as l}from"@vincle/core/html";function u(e,t){let n=new AbortController,r=t?.signal?AbortSignal.any([t.signal,n.signal]):n.signal,i,a=[],o=()=>{for(let e of a.splice(0))e()};r.addEventListener(`abort`,o,{once:!0});let s=async e=>{if(!r.aborted){for(;(i.desiredSize??1)<=0&&!r.aborted;)await new Promise(e=>a.push(e));r.aborted||i.enqueue(e)}};return new ReadableStream({start(t){i=t,e(s,r).then(()=>{try{t.close()}catch{}},e=>{try{t.error(e)}catch{}})},pull(){o()},cancel(e){n.abort(e)}})}function d(e,t,n){let r=e;if(r==null)return{signal:t??new AbortController().signal,cleanup:()=>{}};let a=new AbortController,o=setTimeout(()=>a.abort(/* @__PURE__ */ Error(`${i} Fragment "${n}" timed out after ${r}ms: its content did not finish in time. Increase the timeout (the fragment's timeout prop or defaultTimeout), or make the content finish faster, e.g. by forwarding the abort signal to fetch().`)),r);return{signal:t?AbortSignal.any([t,a.signal]):a.signal,cleanup:()=>clearTimeout(o)}}const f=e=>typeof e==`function`;function p(e,t){try{let n=f(e.content)?e.content(t):e.content;return l(n)?{kind:`stream`,iterable:n}:{kind:`value`,value:n}}catch(e){return{kind:`sync-error`,error:e}}}async function m(t,n,r,i,a){console.error(`[vincle/flow] Error rendering ${i} "${r}"`,a);let o=n?.(a,{id:r,kind:i});o!=null&&await t({type:`fragment`,id:r,html:await e(o),merge:`replace`})}async function h(e,t,n,r,i){try{await m(e,t,n,r,i)}catch{throw i}}function g(e,t,n,r){return t.treeScope?t.treeScope(()=>_(e,t,n,r)):_(e,t,n,r)}function _(e,t,n,r){let i=t.onError??r.onError,{signal:a,cleanup:o}=d(t.timeout??r.defaultTimeout,r.signal,e),s=p(t,a);switch(s.kind){case`sync-error`:return o(),{isStreaming:!1,done:m(n,i,e,`fragment`,s.error)};case`stream`:return{isStreaming:!0,done:y(e,s.iterable,t.merge,n,i,a).finally(o)};case`value`:return{isStreaming:!1,done:v(e,s.value,t.merge,n,i,a,o)}}}async function v(t,n,r,i,a,o,s){let c;try{c=await e(n)}catch(e){await h(i,a,t,`fragment`,e);return}finally{s()}if(o.aborted){await h(i,a,t,`fragment`,o.reason);return}try{await i({type:`fragment`,id:t,html:c,merge:r})}catch(e){throw console.error(`[vincle/flow] Failed to emit fragment "${t}"`,e),e}}async function y(t,n,r,i,a,o){let s=n[Symbol.asyncIterator](),c=new Promise(e=>{let t=()=>e({done:!0,value:void 0});o.aborted?t():o.addEventListener(`abort`,t,{once:!0})}),l=!1;try{for(;;){let n=Promise.resolve(s.next());n.catch(()=>{});let o=await Promise.race([n,c]);if(o.done)break;let u;try{u=await e(o.value)}catch(e){await h(i,a,t,`stream`,e);continue}try{await i({type:`fragment`,id:t,html:u,merge:r})}catch(e){throw console.error(`[vincle/flow] Failed to emit stream chunk for "${t}"`,e),l=!0,e}}}catch(e){if(l)throw e;await h(i,a,t,`stream`,e)}finally{o.aborted&&await s.return?.(void 0).catch(()=>{})}}async function b(e){if(e.length===0)return;let t=await Promise.allSettled(e);for(let e of t)if(e.status===`rejected`)throw e.reason}function x(e){let t=Promise.resolve();return{emit:n=>{let r=t.then(()=>e(n));return t=r.catch(()=>{}),r},drain:()=>t}}async function S(e,t,n={}){let r=/* @__PURE__ */ new Set,i=[],a=x(t);for(;!n.signal?.aborted;){let t=e.fragments.outstanding(r);if(t.length>0){let e=[];for(let[o,s]of t){r.add(o);let{isStreaming:t,done:c}=g(o,s,a.emit,n);(t?i:e).push(c)}await b(e);continue}if(i.length===0||(await b(i),i.length=0,!e.fragments.hasOutstanding(r)))break}i.length>0&&await b(i),await a.drain()}function C(e){let t=e.trimEnd();if(!t.endsWith(`</html>`))return{body:e,closingTag:``};let n=t.slice(0,-7).trimEnd(),r=n.endsWith(`</body>`)?n.length-7:n.length;return{body:e.slice(0,r),closingTag:e.slice(r)}}function w(e){let t=!1;return new TransformStream({async transform(n,r){if(n.type===`shell`){t=!0,r.enqueue(n.html);return}let i=``;if(t&&(t=!1,i=`
|
|
2
|
-
`),n.type===`fragment`){let t=await c(e.Patch({id:n.id,children:s(n.html),merge:n.merge}));r.enqueue(i+t+`
|
|
3
|
-
`)}else r.enqueue(i+n.html+`
|
|
4
|
-
`)},flush(e){t&&e.enqueue(`
|
|
5
|
-
`)}})}async function T(t,n,r){let{body:i,closingTag:a}=C(await e(t()));return{shellBody:n.transformShell?n.transformShell(i,r):i,closingTag:a}}async function E(e,n,r,i,a){await t(async t=>{let{fragments:o}=t;try{if(n.aborted)return;let{shellBody:s,closingTag:c}=await T(r,i,t);a.mode!==`fragment`&&s!==``&&await e({type:`shell`,html:s}),await S({fragments:o},e,{...a,signal:n}),a.mode!==`fragment`&&c&&await e({type:`close`,html:c})}finally{o.clear()}},{adapter:i,mode:`streaming`})}function D(e,t,n){a(n,e),r(t,e)}function O(e,t,n={}){return D(`renderToFlowEvents`,t,n),u((r,i)=>E(r,i,e,t,n),{signal:n.signal})}function k(e,t,r){if(D(`renderToStream`,t,r),t.capabilities.streaming!==!0)throw o(`[vincle/flow] renderToStream(): this adapter does not stream: capabilities.streaming is false, so it can only produce static output. Use renderToStatic() with this adapter, or pass a streaming adapter (TurboAdapter, NativeAdapter, HtmxAdapter, WebPlatformAdapter).`,n);return O(e,t,r).pipeThrough(w(t))}export{k as n,S as r,O as t};
|
package/dist/utils-DKRixAvn.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=`ERR_VINCLE_FLOW_CONFIG`,t=`ERR_VINCLE_FLOW_NO_ADAPTER`,n=`ERR_VINCLE_FLOW_MERGE_UNSUPPORTED`,r=`ERR_VINCLE_FLOW_NO_FRAGMENT`,i=`ERR_VINCLE_FLOW_NO_STREAMING`,a=`ERR_VINCLE_FLOW_FORBIDDEN_SCHEME`,o=`ERR_VINCLE_FLOW_DUP_FRAGMENT`;function s(e,t){let n=Error(e);return n.code=t,Error.captureStackTrace?.(n,s),n}const c=[`replace`,`append`,`prepend`,`before`,`after`,`morph`],l=`[vincle/flow]`;function u(e){if(e===null)return`null`;if(typeof e==`string`)return JSON.stringify(e.length>80?e.slice(0,80)+`…`:e);if(typeof e==`number`||typeof e==`bigint`||typeof e==`boolean`)return String(e);if(typeof e==`function`)return`function ${e.name||`<anonymous>`}`;if(e instanceof Error)return`${e.name}: ${e.message}`;try{let t=JSON.stringify(e);return t===void 0?String(e):t.length>100?t.slice(0,100)+`…`:t}catch{return String(e)}}function d(e){return e instanceof AbortSignal||typeof e==`object`&&!!e&&typeof e.aborted==`boolean`&&typeof e.addEventListener==`function`&&typeof e.onabort==`object`}function f(t,n){if(t!=null){if(typeof t!=`object`)throw s(`${l} ${n}: options must be an object, got ${u(t)}. Example: { defaultTimeout: 5000, onError, signal }`,e);if(t.defaultTimeout!==void 0&&(!Number.isFinite(t.defaultTimeout)||t.defaultTimeout<0))throw s(`${l} ${n}: defaultTimeout must be a number of milliseconds >= 0, got ${u(t.defaultTimeout)}. Pass e.g. defaultTimeout: 5000, or omit it for no limit.`,e);if(t.onError!==void 0&&typeof t.onError!=`function`)throw s(`${l} ${n}: onError must be a function (error, { id, kind }) => JSX.Element | void, got ${u(t.onError)}. Omit it to log errors to the console instead.`,e);if(t.signal!==void 0&&!d(t.signal))throw s(`${l} ${n}: signal must be an AbortSignal, got ${u(t.signal)}. Example: const controller = new AbortController(); … { signal: controller.signal }`,e)}}const p=[`Placeholder`,`Patch`,`Frame`];function m(t,n){if(t===void 0)return;if(typeof t!=`object`||!t)throw s(`${l} ${n}: adapter must be an adapter object, got ${u(t)}. Use a built-in (TurboAdapter, NativeAdapter, HtmxAdapter, WebPlatformAdapter, EsiAdapter) or createAdapter().`,e);let r=t,i=p.filter(e=>typeof r[e]!=`function`);if(i.length>0)throw s(`${l} ${n}: the adapter is missing ${i.map(e=>`"${e}"`).join(`, `)}: an adapter needs Placeholder, Patch and Frame. Use createAdapter() or a built-in adapter.`,e);let a=r.capabilities;if(typeof a!=`object`||!a)throw s(`${l} ${n}: the adapter is missing capabilities: declare { streaming: boolean, merges: MergeType[] } (see createAdapter()).`,e);if(typeof a.streaming!=`boolean`)throw s(`${l} ${n}: adapter.capabilities.streaming must be a boolean, got ${u(a.streaming)}.`,e);let o=a.merges;if(!(Array.isArray(o)&&o.every(e=>typeof e==`string`&&c.includes(e))))throw s(`${l} ${n}: adapter.capabilities.merges must be an array of merge types (${c.join(`, `)}), got ${u(o)}.`,e);if(r.transformShell!==void 0&&typeof r.transformShell!=`function`)throw s(`${l} ${n}: adapter.transformShell must be a function (shell, ctx) => string, got ${u(r.transformShell)}.`,e)}function h(t){if(typeof t!=`object`||!t)throw s(`${l} FlowConfig: the flow config must be an object, got ${u(t)}. Example: { adapter: TurboAdapter, mode: "streaming" }`,e);let n=t,r=n.mode;if(r!==`streaming`&&r!==`static`)throw s(`${l} FlowConfig.mode must be "streaming" or "static", got ${u(r)}.`,e);let i=n.idPrefix;if(i!==void 0&&typeof i!=`string`)throw s(`${l} FlowConfig.idPrefix must be a string, got ${u(i)}. Default: "fragment-".`,e);let a=n.generatePath;if(a!==void 0&&typeof a!=`function`)throw s(`${l} FlowConfig.generatePath must be a function (id) => string, got ${u(a)}. Example: (id) => \`/fragments/\${id}.html\`.`,e);if(r===`static`){if(a===void 0)throw s(`${l} FlowConfig: static mode requires generatePath: (id) => string. Example: generatePath: (id) => \`/fragments/\${id}.html\`.`,e)}else if(a!==void 0)throw s(`${l} FlowConfig: generatePath is only used in static mode (renderToStatic): remove it from the streaming config, or drop mode: "streaming" if you meant static generation.`,e);m(n.adapter,`FlowConfig`)}function g(t,n){if(t!==void 0&&(!Number.isFinite(t)||t<0))throw s(`${l} ${n}: timeout must be a number of milliseconds >= 0, got ${u(t)}. Pass e.g. timeout={5000}, or omit it to use defaultTimeout.`,e)}const _=/^[a-zA-Z][a-zA-Z0-9_-]*$/;function v(e,t){if(!_.test(e))throw s(`${l} ${t}: "${e}" is not a valid fragment id: ids must start with a letter and contain only letters, digits, hyphens and underscores (e.g. "price-aapl"). Fragment ids become DOM ids and URL segments.`,`ERR_VINCLE_FLOW_FRAGMENT_ID`)}function y(e,t){let n=e.match(/<\/head\s*>/i);if(n){let r=n.index;return e.slice(0,r)+t+e.slice(r)}let r=`<head>${t}</head>`,i=e.match(/<html\b[^>]*>/i);if(i)return b(e,i.index+i[0].length,r);let a=e.match(/^\s*<!doctype\b[^>]*>/i);return a?b(e,a[0].length,r):r+e}const b=(e,t,n)=>e.slice(0,t)+n+e.slice(t);function x(...e){let t=e.filter(Boolean);return(e,n)=>t.reduce((e,t)=>t(e,n),e)}export{i as _,m as a,g as c,e as d,o as f,r as g,t as h,l as i,u as l,n as m,x as n,h as o,a as p,y as r,f as s,v as t,c as u,s as v};
|