@farm.js/islx 0.0.1 → 0.0.3
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 +2 -1
- package/SPEC.md +4 -3
- package/dist/_chunks/{actions-CA_K-In7.mjs → actions-2sTj0A2T.mjs} +4 -8
- package/dist/_chunks/{actions-C4dZ1-XD.mjs → actions-B8dJ8f7L.mjs} +1 -1
- package/dist/_chunks/{codec-BTDkzzPP.mjs → codec-DqfVYKsx.mjs} +11 -3
- package/dist/_chunks/{tree-fID6sz25.d.mts → tree-B4RMU2UX.d.mts} +38 -1
- package/dist/_chunks/{tree-BEV73f_8.mjs → tree-BvYR6-PF.mjs} +166 -53
- package/dist/client.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react/client.d.mts +2 -2
- package/dist/react/client.mjs +65 -12
- package/dist/react/server.d.mts +2 -2
- package/dist/react/server.mjs +4 -4
- package/dist/react.d.mts +2 -2
- package/dist/react.mjs +1 -1
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.3
|
|
4
|
+
|
|
5
|
+
Server layouts as trees.
|
|
6
|
+
|
|
7
|
+
- A server layout around a client provider renders as a server tree, so a framework no longer
|
|
8
|
+
ships the layout's module to hydrate it. On the server, `renderServerTree(layout, { outlets,
|
|
9
|
+
scope })` with `<Outlet />` where the page goes; in the browser, `createServerTreeLayout(source,
|
|
10
|
+
scope, { load, ServerHtml, onUpdate })` and `layout.render({ children: page })`. Rendering it
|
|
11
|
+
again with another page keeps the provider's state.
|
|
12
|
+
- A layout's tree names its payload, chunks and server HTML by its scope (`f/chunk name="/:0"`),
|
|
13
|
+
so it and the page's tree at its outlet never read each other's parts. New tag `L` (outlet).
|
|
14
|
+
|
|
15
|
+
## 0.0.2
|
|
16
|
+
|
|
17
|
+
Streaming and hydration fixes.
|
|
18
|
+
|
|
19
|
+
- Islands inside Suspense content that streams in after the browser entry has run now hydrate.
|
|
20
|
+
While the document streams, the runtime watches it: an island hydrates once React moves it out
|
|
21
|
+
of its hidden staging `<div>`, an island React removes (a replaced fallback) is released, and
|
|
22
|
+
watching stops once the document is parsed and every staged boundary is revealed. React 19.2
|
|
23
|
+
can reveal boundaries after parsing ends.
|
|
24
|
+
- Islands that share a value decode it once and share the result. Props and values without tags
|
|
25
|
+
skip decoding, and decoded objects are built with plain assignment (`__proto__` stays data).
|
|
26
|
+
|
|
3
27
|
## 0.0.1
|
|
4
28
|
|
|
5
29
|
The first release: the islx wire format, server actions, React islands, and the Vite plugin.
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ islx started as [Farm.js](https://github.com/farming-labs/farm.js)'s islands mod
|
|
|
13
13
|
replaces React Server Components' Flight payload. On a 300-page docs app it ships 26% less
|
|
14
14
|
JavaScript, and a client navigation sends 2.5 KB instead of 12 KB.
|
|
15
15
|
|
|
16
|
-
> Status: 0.0.
|
|
16
|
+
> Status: 0.0.3. React and Vite are supported; other renderers and framework
|
|
17
17
|
> integrations follow. The wire format is in [SPEC.md](./SPEC.md), changes in
|
|
18
18
|
> [CHANGELOG.md](./CHANGELOG.md).
|
|
19
19
|
|
|
@@ -180,6 +180,7 @@ A framework that renders pages itself (Farm does) uses the pieces directly inste
|
|
|
180
180
|
| Render islands in its own render | wrap the page in `<IslandsProvider held={parseHeldValues(request)}>` |
|
|
181
181
|
| A layout that hydrates as one root | `withinClientRoot(layout)`; its server-HTML page: `isolateIslands(page)` |
|
|
182
182
|
| An async page under such a layout | `renderServerTree(page, { held, payloadOnly })` on the server; `createServerTreeElement(source, { load, ServerHtml })` in the browser |
|
|
183
|
+
| A server layout around a client provider, without shipping the layout | `renderServerTree(<Layout><Outlet /></Layout>, { outlets: { children: page }, scope })` on the server; `createServerTreeLayout(source, scope, { load, ServerHtml, onUpdate })` in the browser, then `layout.render({ children: page })` |
|
|
183
184
|
| Hydrate | `createHydrationRuntime({ load, schedule?, wrap?, report? })`, `createServerHtml(runtime)` |
|
|
184
185
|
| Hold early clicks | inline `PREHYDRATION_SCRIPT` in the page |
|
|
185
186
|
| Actions | `handleAction(request, { allowedOrigins, bodySizeLimit, run, mapError, renderFormState })`; `setActionNavigator({ refresh, navigate })` |
|
package/SPEC.md
CHANGED
|
@@ -32,9 +32,9 @@ Anything that writes and reads it, in any language or framework, is a islx imple
|
|
|
32
32
|
| `<script type="f/props" for>` | An island's props, as JSON. `for` names the island's `props` id. |
|
|
33
33
|
| `<script type="f/value" name>` | A value sent once. `name` is its key. Written before the first island that uses it. |
|
|
34
34
|
| `<f-slot name>` | Server output passed to an island as children. It stays server HTML; the island renders it in place. |
|
|
35
|
-
| `<script type="f/tree" [partial]>` | A
|
|
36
|
-
| `<script type="f/chunk" name>` | A part of a server tree that streamed in after the tree (a resolved Suspense boundary). |
|
|
37
|
-
| `<f-html name>` | Server HTML inside a server tree. |
|
|
35
|
+
| `<script type="f/tree" [name] [partial]>` | A server tree, so the browser can rebuild the page inside an existing root without its HTML. A page's tree has no `name`; a layout's carries its scope and sits at its `children` outlet, or after the layout when it has none. `partial` marks a navigation that sent only the tree. |
|
|
36
|
+
| `<script type="f/chunk" name>` | A part of a server tree that streamed in after the tree (a resolved Suspense boundary). `name` is its number, after `scope:` in a layout's tree. |
|
|
37
|
+
| `<f-html name>` | Server HTML inside a server tree, named like a chunk. |
|
|
38
38
|
|
|
39
39
|
A value script's and props script's JSON escapes `<`, U+2028 and U+2029.
|
|
40
40
|
|
|
@@ -59,6 +59,7 @@ with any other keys is data.
|
|
|
59
59
|
| `R` | `RegExp` | `[source, flags]` |
|
|
60
60
|
| `Y` | bytes | `[type, base64]`; type is `ArrayBuffer`, `DataView` or a typed array name |
|
|
61
61
|
| `p` | a Suspense boundary inside a server tree | |
|
|
62
|
+
| `L` | a layout tree's outlet, where the page renders | `n`: the outlet's name, `children` for the page |
|
|
62
63
|
| `F` | a `FormData` action argument | its index among the call's attachments |
|
|
63
64
|
| `K` | a `Blob` or `File` action argument | its index among the call's attachments |
|
|
64
65
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { A as VALUE_SCRIPT_TYPE, N as getTag, O as VALUES_HEADER, j as attachmentField, k as VALUE_KEY_PATTERN, o as BOUND_FIELD, s as CALL_FIELD, t as ACTION_HEADER } from "./wire-CXOodE06.mjs";
|
|
2
|
-
import { a as encode, r as decode } from "./codec-
|
|
2
|
+
import { a as encode, r as decode, u as setOwn } from "./codec-DqfVYKsx.mjs";
|
|
3
3
|
// src/client/value-store.ts
|
|
4
4
|
/**
|
|
5
5
|
* The browser's store of values sent once. It keeps every value it has read, parses each once,
|
|
@@ -21,7 +21,8 @@ function createValueStore() {
|
|
|
21
21
|
if (entry.state === "resolving") throw new Error(`islx value ${key} refers to itself`);
|
|
22
22
|
entry.state = "resolving";
|
|
23
23
|
try {
|
|
24
|
-
|
|
24
|
+
const parsed = JSON.parse(entry.json);
|
|
25
|
+
entry.value = entry.json.includes(`"\$"`) ? resolve(parsed) : parsed;
|
|
25
26
|
} catch (error) {
|
|
26
27
|
entry.state = "raw";
|
|
27
28
|
throw error;
|
|
@@ -44,12 +45,7 @@ function createValueStore() {
|
|
|
44
45
|
};
|
|
45
46
|
const resolveEntries = (object) => {
|
|
46
47
|
const resolved = {};
|
|
47
|
-
for (const key of Object.keys(object))
|
|
48
|
-
value: resolve(object[key]),
|
|
49
|
-
enumerable: true,
|
|
50
|
-
writable: true,
|
|
51
|
-
configurable: true
|
|
52
|
-
});
|
|
48
|
+
for (const key of Object.keys(object)) setOwn(resolved, key, resolve(object[key]));
|
|
53
49
|
return resolved;
|
|
54
50
|
};
|
|
55
51
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as formFieldName, O as VALUES_HEADER, d as FORM_REF_PREFIX, k as VALUE_KEY_PATTERN } from "./wire-CXOodE06.mjs";
|
|
2
|
-
import { a as encode, c as needsObjectTag, o as encodeLeaf, r as decode, t as EncodeError } from "./codec-
|
|
2
|
+
import { a as encode, c as needsObjectTag, o as encodeLeaf, r as decode, t as EncodeError } from "./codec-DqfVYKsx.mjs";
|
|
3
3
|
import { t as getBuildDefaults } from "./build-COIcs7wm.mjs";
|
|
4
4
|
// src/server/values.ts
|
|
5
5
|
/**
|
|
@@ -88,13 +88,21 @@ const NOT_A_VALUE = Symbol.for("islx.codec.not-a-value");
|
|
|
88
88
|
/** A plain object from entries; `defineProperty`, so a "__proto__" key stays an ordinary key. */
|
|
89
89
|
function plainObject(entries) {
|
|
90
90
|
const object = {};
|
|
91
|
-
for (const [key, value] of entries)
|
|
91
|
+
for (const [key, value] of entries) setOwn(object, key, value);
|
|
92
|
+
return object;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Sets an own property. Plain assignment keeps the object fast; only a `"__proto__"` key needs
|
|
96
|
+
* `defineProperty`, which assignment would turn into a prototype change.
|
|
97
|
+
*/
|
|
98
|
+
function setOwn(object, key, value) {
|
|
99
|
+
if (key === "__proto__") Object.defineProperty(object, key, {
|
|
92
100
|
value,
|
|
93
101
|
enumerable: true,
|
|
94
102
|
writable: true,
|
|
95
103
|
configurable: true
|
|
96
104
|
});
|
|
97
|
-
|
|
105
|
+
else object[key] = value;
|
|
98
106
|
}
|
|
99
107
|
/**
|
|
100
108
|
* The value a tagged object stands for, decoding children (a `Map`'s or `Set`'s entries, an `o`
|
|
@@ -268,4 +276,4 @@ function decode(value, options = {}) {
|
|
|
268
276
|
};
|
|
269
277
|
return visit(value);
|
|
270
278
|
}
|
|
271
|
-
export { encode as a, needsObjectTag as c,
|
|
279
|
+
export { encode as a, needsObjectTag as c, tagged as d, toBase64 as f, decodeTagged as i, plainObject as l, NOT_A_VALUE as n, encodeLeaf as o, decode as r, fromBase64 as s, EncodeError as t, setOwn as u };
|
|
@@ -58,6 +58,13 @@ declare function createIsland<P extends object>(Component: React.ComponentType<P
|
|
|
58
58
|
* page in its layout's React root and never shows its HTML, so the server leaves the HTML out.
|
|
59
59
|
*/
|
|
60
60
|
declare const PAYLOAD_ONLY_ATTRIBUTE = "partial";
|
|
61
|
+
/**
|
|
62
|
+
* Where a layout rendered as a server tree places the page (or another outlet, by `name`). The
|
|
63
|
+
* tree records the outlet, and the server and the browser render what they are given there.
|
|
64
|
+
*/
|
|
65
|
+
declare function Outlet(_props: {
|
|
66
|
+
name?: string;
|
|
67
|
+
}): null;
|
|
61
68
|
interface ServerTreeRenderOptions {
|
|
62
69
|
/**
|
|
63
70
|
* Renders a part the resolver cannot represent, as server HTML whose client components are
|
|
@@ -71,6 +78,17 @@ interface ServerTreeRenderOptions {
|
|
|
71
78
|
* streams and the server HTML parts it cannot represent, without the page's rendered HTML.
|
|
72
79
|
*/
|
|
73
80
|
payloadOnly?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* A layout rendered as a server tree: what each `<Outlet>` renders, by name. The payload script
|
|
83
|
+
* then sits at the `children` outlet, inside the layout's markup, so a layout that renders the
|
|
84
|
+
* whole document keeps it in the body.
|
|
85
|
+
*/
|
|
86
|
+
outlets?: Record<string, React.ReactNode>;
|
|
87
|
+
/**
|
|
88
|
+
* Names this tree's payload, chunks and server HTML parts, so a layout's tree and the trees at
|
|
89
|
+
* its outlet do not read each other's. A page's tree has none.
|
|
90
|
+
*/
|
|
91
|
+
scope?: string;
|
|
74
92
|
}
|
|
75
93
|
/**
|
|
76
94
|
* Resolves a server page and returns what renders in its place: the resolved tree and its
|
|
@@ -99,4 +117,23 @@ interface ServerTreeElementOptions {
|
|
|
99
117
|
* navigation), after loading its client modules. Returns null for a page without a tree.
|
|
100
118
|
*/
|
|
101
119
|
declare function createServerTreeElement(source: ParentNode | string, options: ServerTreeElementOptions): Promise<React.ReactElement | null>;
|
|
102
|
-
|
|
120
|
+
/** A layout rebuilt from its server tree. */
|
|
121
|
+
interface ServerTreeLayout {
|
|
122
|
+
/** The layout around these outlets: plain elements, the same tree the server rendered. */
|
|
123
|
+
render(outlets: Record<string, React.ReactNode>): React.ReactNode;
|
|
124
|
+
/** Stops following hot module updates. */
|
|
125
|
+
dispose(): void;
|
|
126
|
+
}
|
|
127
|
+
interface ServerTreeLayoutOptions extends Omit<ServerTreeElementOptions, "key"> {
|
|
128
|
+
/** Runs after a hot update to one of the layout's client modules: render the route again. */
|
|
129
|
+
onUpdate?(): void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Rebuilds a layout the server rendered with `renderServerTree(..., { outlets, scope })`, from
|
|
133
|
+
* its server HTML or a navigation's HTML. The layout's own module never reaches the browser: its
|
|
134
|
+
* client components load from the tree, and `render(outlets)` places the page at its outlet.
|
|
135
|
+
* Rendering it with the same component types at the same place keeps its client state across
|
|
136
|
+
* navigations. Returns null when `source` has no tree for `scope`.
|
|
137
|
+
*/
|
|
138
|
+
declare function createServerTreeLayout(source: ParentNode | string, scope: string, options: ServerTreeLayoutOptions): Promise<ServerTreeLayout | null>;
|
|
139
|
+
export { setRenderStateResolver as _, ServerTreeLayoutOptions as a, createServerTreeLayout as c, ClientReference as d, IslandRenderState as f, isolateIslands as g, getClientReference as h, ServerTreeLayout as i, renderServerTree as l, createIsland as m, PAYLOAD_ONLY_ATTRIBUTE as n, ServerTreeRenderOptions as o, IslandsProvider as p, ServerTreeElementOptions as r, createServerTreeElement as s, Outlet as t, updateServerTrees as u, withinClientRoot as v };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-
|
|
1
|
+
import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-DqfVYKsx.mjs";
|
|
2
2
|
import { A as VALUE_SCRIPT_TYPE, D as TREE_SCRIPT_TYPE, S as SLOT_ELEMENT, c as CHUNK_SCRIPT_TYPE, h as ISLAND_ELEMENT, p as HTML_ELEMENT, y as PROPS_SCRIPT_TYPE } from "./wire-CXOodE06.mjs";
|
|
3
|
-
import { l as getValueStore, t as createServerReference } from "./actions-
|
|
3
|
+
import { l as getValueStore, t as createServerReference } from "./actions-2sTj0A2T.mjs";
|
|
4
4
|
import { t as getBuildDefaults } from "./build-COIcs7wm.mjs";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import * as ReactDOM from "react-dom";
|
|
@@ -228,6 +228,15 @@ const SERVER_HTML_TAG = HTML_ELEMENT;
|
|
|
228
228
|
*/
|
|
229
229
|
const PAYLOAD_ONLY_ATTRIBUTE = "partial";
|
|
230
230
|
const SERVER_HTML_ATTRIBUTE = "name";
|
|
231
|
+
const OUTLET = Symbol.for("islx.react.outlet");
|
|
232
|
+
/**
|
|
233
|
+
* Where a layout rendered as a server tree places the page (or another outlet, by `name`). The
|
|
234
|
+
* tree records the outlet, and the server and the browser render what they are given there.
|
|
235
|
+
*/
|
|
236
|
+
function Outlet(_props) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
Outlet[OUTLET] = true;
|
|
231
240
|
const PARSER_SENSITIVE_TAGS = /* @__PURE__ */ new Set([
|
|
232
241
|
"caption",
|
|
233
242
|
"colgroup",
|
|
@@ -414,6 +423,10 @@ async function resolveElement(state, element, context) {
|
|
|
414
423
|
i: id
|
|
415
424
|
});
|
|
416
425
|
}
|
|
426
|
+
if (typeof type === "function" && type[OUTLET]) return withKey({
|
|
427
|
+
$: "L",
|
|
428
|
+
n: String(props.name ?? "children")
|
|
429
|
+
});
|
|
417
430
|
const clientReference = getClientReference(type);
|
|
418
431
|
if (clientReference) {
|
|
419
432
|
const { children, ...rest } = props;
|
|
@@ -511,6 +524,7 @@ function createTreeValue(value, resolver) {
|
|
|
511
524
|
fallback: createTreeValue(value.f, resolver)
|
|
512
525
|
}, React.createElement(getChunkComponent(), { chunk: resolver.chunk(value.i) }));
|
|
513
526
|
case "x": return resolver.serverHtml(value.i, typeof value.k === "string" ? value.k : null);
|
|
527
|
+
case "L": return resolver.outlet(value.n);
|
|
514
528
|
case "n": return Number(value.v);
|
|
515
529
|
case "a": return resolver.action(value.v, typeof value.b === "string" ? value.b : void 0);
|
|
516
530
|
case "P": return fulfilledThenable(createTreeValue(value.v, resolver));
|
|
@@ -571,6 +585,22 @@ async function createPayloadOnlyPage(state, payload, encode, resolver) {
|
|
|
571
585
|
return React.createElement(React.Fragment, { key: `c${id}` }, createTreeScript(CHUNK_SCRIPT_TYPE, String(id), chunk.json, chunk.values));
|
|
572
586
|
}));
|
|
573
587
|
}
|
|
588
|
+
/** The name of a tree part: its number, after the tree's scope. */
|
|
589
|
+
function scopedName(scope, id) {
|
|
590
|
+
return scope ? `${scope}:${id}` : String(id);
|
|
591
|
+
}
|
|
592
|
+
function hasChildrenOutlet(value) {
|
|
593
|
+
if (value === null || typeof value !== "object") return false;
|
|
594
|
+
if (Array.isArray(value)) return value.some(hasChildrenOutlet);
|
|
595
|
+
if (value.$ === "L") return value.n === "children";
|
|
596
|
+
return Object.values(value).some(hasChildrenOutlet);
|
|
597
|
+
}
|
|
598
|
+
/** What renders at a layout's outlet: its content, after the layout's payload at `children`. */
|
|
599
|
+
function outletContent(outlets, name, script) {
|
|
600
|
+
const content = outlets?.[name] ?? null;
|
|
601
|
+
if (name !== "children" || !script) return content;
|
|
602
|
+
return React.createElement(React.Fragment, null, script, content);
|
|
603
|
+
}
|
|
574
604
|
/**
|
|
575
605
|
* Resolves a server page and returns what renders in its place: the resolved tree and its
|
|
576
606
|
* payload. Errors from the page's own code (a framework's redirect or not-found) propagate
|
|
@@ -588,6 +618,7 @@ async function renderServerTree(element, options = {}) {
|
|
|
588
618
|
chunks: /* @__PURE__ */ new Map(),
|
|
589
619
|
actions: /* @__PURE__ */ new Map()
|
|
590
620
|
};
|
|
621
|
+
const scope = options.scope;
|
|
591
622
|
const tree = await resolveValue(state, element, { parserSensitive: false }, /* @__PURE__ */ new Set());
|
|
592
623
|
const held = options.held && options.held.size > 0 ? options.held : null;
|
|
593
624
|
const encode = (payload) => {
|
|
@@ -597,6 +628,7 @@ async function renderServerTree(element, options = {}) {
|
|
|
597
628
|
values: addressed.values.map((value) => [value.key, hook.escape(value.json)])
|
|
598
629
|
};
|
|
599
630
|
};
|
|
631
|
+
let layoutScript = null;
|
|
600
632
|
const resolver = {
|
|
601
633
|
component(reference, exportName) {
|
|
602
634
|
const Component = state.components.get(`${reference}#${exportName}`);
|
|
@@ -610,7 +642,7 @@ async function renderServerTree(element, options = {}) {
|
|
|
610
642
|
status: "pending",
|
|
611
643
|
promise: entry.encoded.then((encoded) => {
|
|
612
644
|
const payload = encode({ t: encoded });
|
|
613
|
-
chunk.value = React.createElement(React.Fragment, null, createTreeValue(encoded, resolver), createTreeScript(CHUNK_SCRIPT_TYPE,
|
|
645
|
+
chunk.value = React.createElement(React.Fragment, null, createTreeValue(encoded, resolver), createTreeScript(CHUNK_SCRIPT_TYPE, scopedName(scope, id), payload.json, payload.values));
|
|
614
646
|
chunk.status = "fulfilled";
|
|
615
647
|
return chunk.value;
|
|
616
648
|
}, (error) => {
|
|
@@ -626,10 +658,13 @@ async function renderServerTree(element, options = {}) {
|
|
|
626
658
|
serverHtml(id, key) {
|
|
627
659
|
return React.createElement(SERVER_HTML_TAG, {
|
|
628
660
|
...key === null ? {} : { key },
|
|
629
|
-
[SERVER_HTML_ATTRIBUTE]:
|
|
661
|
+
[SERVER_HTML_ATTRIBUTE]: scopedName(scope, id),
|
|
630
662
|
style: { display: "contents" }
|
|
631
663
|
}, isolate(state.serverHtml.get(id)));
|
|
632
664
|
},
|
|
665
|
+
outlet(name) {
|
|
666
|
+
return outletContent(options.outlets, name, layoutScript);
|
|
667
|
+
},
|
|
633
668
|
action(id) {
|
|
634
669
|
return state.actions.get(id);
|
|
635
670
|
}
|
|
@@ -638,6 +673,16 @@ async function renderServerTree(element, options = {}) {
|
|
|
638
673
|
v: 1,
|
|
639
674
|
t: tree
|
|
640
675
|
});
|
|
676
|
+
if (options.outlets) {
|
|
677
|
+
const script = createTreeScript(TREE_SCRIPT_TYPE, scope ?? "", payload.json, payload.values);
|
|
678
|
+
const atOutlet = hasChildrenOutlet(tree);
|
|
679
|
+
if (atOutlet) layoutScript = script;
|
|
680
|
+
const layout = createTreeValue(tree, resolver);
|
|
681
|
+
const content = atOutlet ? layout : React.createElement(React.Fragment, null, layout, script);
|
|
682
|
+
return React.createElement(function ServerTreeLayout() {
|
|
683
|
+
return content;
|
|
684
|
+
});
|
|
685
|
+
}
|
|
641
686
|
const page = options.payloadOnly && await createPayloadOnlyPage(state, payload, encode, resolver) || createTreePage(tree, payload.json, resolver, void 0, payload.values);
|
|
642
687
|
return React.createElement(function ServerTreePage() {
|
|
643
688
|
return page;
|
|
@@ -673,14 +718,10 @@ function exportOf(module, src, exportName) {
|
|
|
673
718
|
if (typeof Component !== "function" && (typeof Component !== "object" || Component === null)) throw new Error(`${src} has no export ${exportName}`);
|
|
674
719
|
return Component;
|
|
675
720
|
}
|
|
676
|
-
/**
|
|
677
|
-
|
|
678
|
-
* navigation), after loading its client modules. Returns null for a page without a tree.
|
|
679
|
-
*/
|
|
680
|
-
async function createServerTreeElement(source, options) {
|
|
721
|
+
/** Reads a tree's payload, chunks and server HTML from `root` and loads its client modules. */
|
|
722
|
+
async function prepareTree(root, scope, options) {
|
|
681
723
|
const values = options.values ?? getValueStore();
|
|
682
|
-
const
|
|
683
|
-
const script = root.querySelector(`script[type="${TREE_SCRIPT_TYPE}"]`);
|
|
724
|
+
const script = Array.from(root.querySelectorAll(`script[type="${TREE_SCRIPT_TYPE}"]`)).find((candidate) => (candidate.getAttribute("name") ?? void 0) === scope);
|
|
684
725
|
if (!script) return null;
|
|
685
726
|
const json = script.textContent ?? "";
|
|
686
727
|
let ingested = false;
|
|
@@ -696,10 +737,19 @@ async function createServerTreeElement(source, options) {
|
|
|
696
737
|
const payload = parsePayload(json);
|
|
697
738
|
if (payload.v !== 1) throw new Error("unsupported server tree payload version");
|
|
698
739
|
const scripts = readTreeValueScripts(script);
|
|
740
|
+
const partId = (name) => {
|
|
741
|
+
if (name === null) return null;
|
|
742
|
+
const prefix = scope === void 0 ? "" : `${scope}:`;
|
|
743
|
+
if (!name.startsWith(prefix)) return null;
|
|
744
|
+
const id = name.slice(prefix.length);
|
|
745
|
+
return /^\d+$/.test(id) ? Number(id) : null;
|
|
746
|
+
};
|
|
699
747
|
const chunks = /* @__PURE__ */ new Map();
|
|
700
748
|
for (const chunkScript of root.querySelectorAll(`script[type="${CHUNK_SCRIPT_TYPE}"]`)) {
|
|
749
|
+
const id = partId(chunkScript.getAttribute("name"));
|
|
750
|
+
if (id === null) continue;
|
|
701
751
|
const chunkJson = chunkScript.textContent ?? "";
|
|
702
|
-
chunks.set(
|
|
752
|
+
chunks.set(id, {
|
|
703
753
|
tree: parsePayload(chunkJson).t,
|
|
704
754
|
json: chunkJson,
|
|
705
755
|
values: readTreeValueScripts(chunkScript)
|
|
@@ -715,52 +765,84 @@ async function createServerTreeElement(source, options) {
|
|
|
715
765
|
components.set(key, exportOf(await options.load(src), src, key.slice(separator + 1)));
|
|
716
766
|
}));
|
|
717
767
|
const serverHtml = /* @__PURE__ */ new Map();
|
|
718
|
-
for (const container of root.querySelectorAll(`${SERVER_HTML_TAG}[${SERVER_HTML_ATTRIBUTE}]`))
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
768
|
+
for (const container of root.querySelectorAll(`${SERVER_HTML_TAG}[${SERVER_HTML_ATTRIBUTE}]`)) {
|
|
769
|
+
const id = partId(container.getAttribute(SERVER_HTML_ATTRIBUTE));
|
|
770
|
+
if (id !== null) serverHtml.set(id, container.innerHTML);
|
|
771
|
+
}
|
|
772
|
+
const resolver = (outlets, outletScript) => {
|
|
773
|
+
const treeResolver = {
|
|
774
|
+
component(reference, exportName) {
|
|
775
|
+
const Component = components.get(`${reference}#${exportName}`);
|
|
776
|
+
if (!Component) throw new Error(`client reference ${reference}#${exportName} was not loaded`);
|
|
777
|
+
return Component;
|
|
778
|
+
},
|
|
779
|
+
chunk(id) {
|
|
780
|
+
const chunk = chunks.get(id);
|
|
781
|
+
if (!chunk) throw new Error(`server tree chunk ${id} was not found`);
|
|
782
|
+
const value = React.createElement(React.Fragment, null, createTreeValue(chunk.tree, treeResolver), createTreeScript(CHUNK_SCRIPT_TYPE, scopedName(scope, id), chunk.json, chunk.values));
|
|
783
|
+
return {
|
|
784
|
+
status: "fulfilled",
|
|
785
|
+
value,
|
|
786
|
+
promise: Promise.resolve(value)
|
|
787
|
+
};
|
|
788
|
+
},
|
|
789
|
+
serverHtml(id, key) {
|
|
790
|
+
const html = serverHtml.get(id);
|
|
791
|
+
if (html === void 0) throw new Error(`server HTML ${id} was not found`);
|
|
792
|
+
return React.createElement(options.ServerHtml, {
|
|
793
|
+
...key === null ? {} : { key },
|
|
794
|
+
as: SERVER_HTML_TAG,
|
|
795
|
+
[SERVER_HTML_ATTRIBUTE]: scopedName(scope, id),
|
|
796
|
+
style: { display: "contents" },
|
|
797
|
+
suppressHydrationWarning: true,
|
|
798
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
799
|
+
});
|
|
800
|
+
},
|
|
801
|
+
outlet(name) {
|
|
802
|
+
return outletContent(outlets, name, outletScript);
|
|
803
|
+
},
|
|
804
|
+
action(id, sealed) {
|
|
805
|
+
return createServerReference(id, sealed);
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
return treeResolver;
|
|
750
809
|
};
|
|
751
|
-
|
|
810
|
+
return {
|
|
811
|
+
payload: payload.t,
|
|
812
|
+
json,
|
|
813
|
+
values: scripts,
|
|
814
|
+
components,
|
|
815
|
+
resolver,
|
|
816
|
+
hasOutlet: hasChildrenOutlet(payload.t)
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
/** Points a tree's loaded components at a hot-updated module; true when the tree renders it. */
|
|
820
|
+
function updateTreeComponents(components, src, module) {
|
|
821
|
+
let changed = false;
|
|
822
|
+
for (const key of components.keys()) {
|
|
823
|
+
const separator = key.lastIndexOf("#");
|
|
824
|
+
if (key.slice(0, separator) !== src) continue;
|
|
825
|
+
components.set(key, exportOf(module, src, key.slice(separator + 1)));
|
|
826
|
+
changed = true;
|
|
827
|
+
}
|
|
828
|
+
return changed;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Rebuilds a server-tree page in the browser from its server HTML (an element, or the HTML of a
|
|
832
|
+
* navigation), after loading its client modules. Returns null for a page without a tree.
|
|
833
|
+
*/
|
|
834
|
+
async function createServerTreeElement(source, options) {
|
|
835
|
+
const prepared = await prepareTree(typeof source === "string" ? parseHtml(source) : source, void 0, options);
|
|
836
|
+
if (!prepared) return null;
|
|
837
|
+
const tree = prepared;
|
|
838
|
+
const resolver = tree.resolver();
|
|
839
|
+
const build = () => createTreePage(tree.payload, tree.json, resolver, void 0, tree.values);
|
|
752
840
|
build();
|
|
753
841
|
function ServerTreePage() {
|
|
754
842
|
const [, setVersion] = React.useState(0);
|
|
755
843
|
React.useEffect(() => {
|
|
756
844
|
const listener = (src, module) => {
|
|
757
|
-
|
|
758
|
-
for (const key of components.keys()) {
|
|
759
|
-
const separator = key.lastIndexOf("#");
|
|
760
|
-
if (key.slice(0, separator) !== src) continue;
|
|
761
|
-
components.set(key, exportOf(module, src, key.slice(separator + 1)));
|
|
762
|
-
changed = true;
|
|
763
|
-
}
|
|
845
|
+
const changed = updateTreeComponents(tree.components, src, module);
|
|
764
846
|
if (changed) setVersion((version) => version + 1);
|
|
765
847
|
return changed;
|
|
766
848
|
};
|
|
@@ -774,4 +856,35 @@ async function createServerTreeElement(source, options) {
|
|
|
774
856
|
}
|
|
775
857
|
return React.createElement(ServerTreePage, options.key === void 0 ? null : { key: options.key });
|
|
776
858
|
}
|
|
777
|
-
|
|
859
|
+
/**
|
|
860
|
+
* Rebuilds a layout the server rendered with `renderServerTree(..., { outlets, scope })`, from
|
|
861
|
+
* its server HTML or a navigation's HTML. The layout's own module never reaches the browser: its
|
|
862
|
+
* client components load from the tree, and `render(outlets)` places the page at its outlet.
|
|
863
|
+
* Rendering it with the same component types at the same place keeps its client state across
|
|
864
|
+
* navigations. Returns null when `source` has no tree for `scope`.
|
|
865
|
+
*/
|
|
866
|
+
async function createServerTreeLayout(source, scope, options) {
|
|
867
|
+
const tree = await prepareTree(typeof source === "string" ? parseHtml(source) : source, scope, options);
|
|
868
|
+
if (!tree) return null;
|
|
869
|
+
const script = createTreeScript(TREE_SCRIPT_TYPE, scope, tree.json, tree.values);
|
|
870
|
+
const render = (outlets) => {
|
|
871
|
+
const resolver = tree.resolver(outlets, tree.hasOutlet ? script : void 0);
|
|
872
|
+
const layout = createTreeValue(tree.payload, resolver);
|
|
873
|
+
return tree.hasOutlet ? layout : React.createElement(React.Fragment, null, layout, script);
|
|
874
|
+
};
|
|
875
|
+
render({});
|
|
876
|
+
const listener = (src, module) => {
|
|
877
|
+
const changed = updateTreeComponents(tree.components, src, module);
|
|
878
|
+
if (changed) options.onUpdate?.();
|
|
879
|
+
return changed;
|
|
880
|
+
};
|
|
881
|
+
const listeners = treeListeners();
|
|
882
|
+
listeners.add(listener);
|
|
883
|
+
return {
|
|
884
|
+
render,
|
|
885
|
+
dispose: () => {
|
|
886
|
+
listeners.delete(listener);
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
export { renderServerTree as a, createIsland as c, isolateIslands as d, runtimeTagged as f, withinClientRoot as h, createServerTreeLayout as i, getClientReference as l, setRenderStateResolver as m, PAYLOAD_ONLY_ATTRIBUTE as n, updateServerTrees as o, setIslandServerHook as p, createServerTreeElement as r, IslandsProvider as s, Outlet as t, getContexts as u };
|
package/dist/client.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as emit, c as createValueStore, i as defineClientHooks, l as getValueStore, n as decodeActionReference, o as hookEventName, r as setActionNavigator, s as setClientHooks, t as createServerReference, u as valueHeaders } from "./_chunks/actions-
|
|
1
|
+
import { a as emit, c as createValueStore, i as defineClientHooks, l as getValueStore, n as decodeActionReference, o as hookEventName, r as setActionNavigator, s as setClientHooks, t as createServerReference, u as valueHeaders } from "./_chunks/actions-2sTj0A2T.mjs";
|
|
2
2
|
import { F as stripRuntimeFields, P as readPostedAction, x as RENDER_HEADER } from "./_chunks/wire-CXOodE06.mjs";
|
|
3
3
|
import { i as REPLAYABLE_SELECTOR, n as PENDING_INTERACTIONS, r as PREHYDRATION_SCRIPT, t as INTERACTION_EVENT } from "./_chunks/prehydration-B1GLTLdk.mjs";
|
|
4
4
|
// src/client/forms.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { A as VALUE_SCRIPT_TYPE, C as SLOT_TAG, D as TREE_SCRIPT_TYPE, E as TAG_VALUE, F as stripRuntimeFields, M as formFieldName, N as getTag, O as VALUES_HEADER, P as readPostedAction, S as SLOT_ELEMENT, T as TAG_BOUND, _ as MAX_HELD_VALUES, a as BLOB_TAG, b as REFERENCE_TAG, c as CHUNK_SCRIPT_TYPE, d as FORM_REF_PREFIX, f as FORM_STATE_SCRIPT_TYPE, g as LAYOUTS_HEADER, h as ISLAND_ELEMENT, i as ACTION_TAG, j as attachmentField, k as VALUE_KEY_PATTERN, l as FORM_DATA_TAG, m as HYDRATED_ATTRIBUTE, n as ACTION_KEY_FIELD, o as BOUND_FIELD, p as HTML_ELEMENT, r as ACTION_STALE_HEADER, s as CALL_FIELD, t as ACTION_HEADER, u as FORM_FIELD_PREFIX, v as OBJECT_TAG, w as TAG, x as RENDER_HEADER, y as PROPS_SCRIPT_TYPE } from "./_chunks/wire-CXOodE06.mjs";
|
|
2
|
-
import { a as encode, c as needsObjectTag, d as toBase64, i as decodeTagged, l as plainObject, n as NOT_A_VALUE, o as encodeLeaf, r as decode, s as fromBase64, t as EncodeError
|
|
2
|
+
import { a as encode, c as needsObjectTag, d as tagged, f as toBase64, i as decodeTagged, l as plainObject, n as NOT_A_VALUE, o as encodeLeaf, r as decode, s as fromBase64, t as EncodeError } from "./_chunks/codec-DqfVYKsx.mjs";
|
|
3
3
|
export { ACTION_HEADER, ACTION_KEY_FIELD, ACTION_STALE_HEADER, ACTION_TAG, BLOB_TAG, BOUND_FIELD, CALL_FIELD, CHUNK_SCRIPT_TYPE, EncodeError, FORM_DATA_TAG, FORM_FIELD_PREFIX, FORM_REF_PREFIX, FORM_STATE_SCRIPT_TYPE, HTML_ELEMENT, HYDRATED_ATTRIBUTE, ISLAND_ELEMENT, LAYOUTS_HEADER, MAX_HELD_VALUES, NOT_A_VALUE, OBJECT_TAG, PROPS_SCRIPT_TYPE, REFERENCE_TAG, RENDER_HEADER, SLOT_ELEMENT, SLOT_TAG, TAG, TAG_BOUND, TAG_VALUE, TREE_SCRIPT_TYPE, VALUES_HEADER, VALUE_KEY_PATTERN, VALUE_SCRIPT_TYPE, attachmentField, decode, decodeTagged, encode, encodeLeaf, formFieldName, fromBase64, getTag, needsObjectTag, plainObject, readPostedAction, stripRuntimeFields, tagged, toBase64 };
|
package/dist/react/client.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as scheduleHydration, i as isIslandStrategy, n as IslandStrategy, r as ScheduleOptions, t as ISLAND_STRATEGIES } from "../_chunks/schedule-Dw14dW0H.mjs";
|
|
2
2
|
import { m as HYDRATED_ATTRIBUTE } from "../_chunks/wire-C3Rpbwl6.mjs";
|
|
3
3
|
import { t as ValueStore } from "../_chunks/value-store-Dm-7xJ6W.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { a as ServerTreeLayoutOptions, c as createServerTreeLayout, g as isolateIslands, i as ServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, r as ServerTreeElementOptions, s as createServerTreeElement, u as updateServerTrees, v as withinClientRoot } from "../_chunks/tree-B4RMU2UX.mjs";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
7
|
// src/react/hydrate.d.ts
|
|
@@ -46,4 +46,4 @@ declare function createServerHtml(runtime: Pick<HydrationRuntime, "hydrate" | "d
|
|
|
46
46
|
declare function refreshDocument(runtime: HydrationRuntime): Promise<void>;
|
|
47
47
|
/** Puts another page's body in place of this one's, and hydrates its islands. */
|
|
48
48
|
declare function swapDocument(runtime: HydrationRuntime, next: Document): Promise<void>;
|
|
49
|
-
export { HYDRATED_ATTRIBUTE, type HydrationOptions, type HydrationRuntime, ISLAND_STRATEGIES, type IslandStrategy, PAYLOAD_ONLY_ATTRIBUTE, type ScheduleOptions, type ServerTreeElementOptions, createHydrationRuntime, createServerHtml, createServerTreeElement, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
|
|
49
|
+
export { HYDRATED_ATTRIBUTE, type HydrationOptions, type HydrationRuntime, ISLAND_STRATEGIES, type IslandStrategy, PAYLOAD_ONLY_ATTRIBUTE, type ScheduleOptions, type ServerTreeElementOptions, type ServerTreeLayout, type ServerTreeLayoutOptions, createHydrationRuntime, createServerHtml, createServerTreeElement, createServerTreeLayout, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
|
package/dist/react/client.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as emit, l as getValueStore, t as createServerReference, u as valueHeaders } from "../_chunks/actions-
|
|
2
|
-
import { i as decodeTagged, n as NOT_A_VALUE, r as decode } from "../_chunks/codec-
|
|
1
|
+
import { a as emit, l as getValueStore, t as createServerReference, u as valueHeaders } from "../_chunks/actions-2sTj0A2T.mjs";
|
|
2
|
+
import { i as decodeTagged, n as NOT_A_VALUE, r as decode, u as setOwn } from "../_chunks/codec-DqfVYKsx.mjs";
|
|
3
3
|
import { N as getTag, S as SLOT_ELEMENT, f as FORM_STATE_SCRIPT_TYPE, h as ISLAND_ELEMENT, m as HYDRATED_ATTRIBUTE, y as PROPS_SCRIPT_TYPE } from "../_chunks/wire-CXOodE06.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { d as isolateIslands, h as withinClientRoot, i as createServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, o as updateServerTrees, r as createServerTreeElement, u as getContexts } from "../_chunks/tree-BvYR6-PF.mjs";
|
|
5
5
|
import { n as isIslandStrategy, r as scheduleHydration, t as ISLAND_STRATEGIES } from "../_chunks/schedule-BFoGKRrU.mjs";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { hydrateRoot } from "react-dom/client";
|
|
@@ -31,8 +31,26 @@ function readFormState(document) {
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Revived values, by the object the value store resolved. Islands that share a value (one sent
|
|
36
|
+
* once for several islands) then share the revived object instead of walking it again. A value
|
|
37
|
+
* with a slot is not kept: a slot's HTML belongs to one island.
|
|
38
|
+
*/
|
|
39
|
+
const revivedValues = /* @__PURE__ */ new WeakMap();
|
|
40
|
+
let slotsRevived = 0;
|
|
34
41
|
/** Turns travelled props back into values: slots keep their server HTML, actions become callable. */
|
|
35
42
|
function revive(value, container) {
|
|
43
|
+
if (value && typeof value === "object") {
|
|
44
|
+
const cached = revivedValues.get(value);
|
|
45
|
+
if (cached !== void 0) return cached;
|
|
46
|
+
const slotsBefore = slotsRevived;
|
|
47
|
+
const revived = reviveUncached(value, container);
|
|
48
|
+
if (slotsRevived === slotsBefore) revivedValues.set(value, revived);
|
|
49
|
+
return revived;
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function reviveUncached(value, container) {
|
|
36
54
|
if (Array.isArray(value)) {
|
|
37
55
|
let changed = false;
|
|
38
56
|
const revived = value.map((entry) => {
|
|
@@ -42,7 +60,6 @@ function revive(value, container) {
|
|
|
42
60
|
});
|
|
43
61
|
return changed ? revived : value;
|
|
44
62
|
}
|
|
45
|
-
if (!value || typeof value !== "object") return value;
|
|
46
63
|
const object = value;
|
|
47
64
|
const tag = getTag(object);
|
|
48
65
|
if (tag === "a" && typeof object["v"] === "string") {
|
|
@@ -50,6 +67,7 @@ function revive(value, container) {
|
|
|
50
67
|
return createServerReference(object["v"], typeof token === "string" ? token : void 0);
|
|
51
68
|
}
|
|
52
69
|
if (tag === "s" && typeof object["v"] === "string") {
|
|
70
|
+
slotsRevived++;
|
|
53
71
|
const name = object["v"];
|
|
54
72
|
let html = "";
|
|
55
73
|
for (const slot of container.querySelectorAll(SLOT_ELEMENT)) if (slot.getAttribute("name") === name) {
|
|
@@ -73,16 +91,16 @@ function revive(value, container) {
|
|
|
73
91
|
const revived = {};
|
|
74
92
|
for (const [key, entry] of Object.entries(object)) {
|
|
75
93
|
const next = revive(entry, container);
|
|
76
|
-
|
|
77
|
-
value: next,
|
|
78
|
-
enumerable: true,
|
|
79
|
-
writable: true,
|
|
80
|
-
configurable: true
|
|
81
|
-
});
|
|
94
|
+
setOwn(revived, key, next);
|
|
82
95
|
if (next !== entry) changed = true;
|
|
83
96
|
}
|
|
84
97
|
return changed ? revived : value;
|
|
85
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* React streams Suspense content that resolves late into a hidden `<div id="S:n">`, then moves it
|
|
101
|
+
* into place. Its islands hydrate once moved, not while staged.
|
|
102
|
+
*/
|
|
103
|
+
const STREAM_STAGING = "div[hidden][id^=\"S:\"]";
|
|
86
104
|
/** The hydration runtime for one page. */
|
|
87
105
|
function createHydrationRuntime(options) {
|
|
88
106
|
const roots = /* @__PURE__ */ new Map();
|
|
@@ -146,6 +164,7 @@ function createHydrationRuntime(options) {
|
|
|
146
164
|
const text = script.textContent || "{}";
|
|
147
165
|
let parsed = JSON.parse(text);
|
|
148
166
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("props must be an object");
|
|
167
|
+
if (!text.includes(`"\$"`)) return parsed;
|
|
149
168
|
if (text.includes(`"\$":"r"`)) {
|
|
150
169
|
values.ingest(container.ownerDocument);
|
|
151
170
|
parsed = values.resolve(parsed);
|
|
@@ -160,9 +179,10 @@ function createHydrationRuntime(options) {
|
|
|
160
179
|
formStateRead = true;
|
|
161
180
|
formState = readFormState(scope instanceof Document ? scope : scope.ownerDocument ?? document);
|
|
162
181
|
}
|
|
182
|
+
if (scope instanceof Document) watchStream(scope);
|
|
163
183
|
const candidates = Array.from(scope.querySelectorAll(ISLAND_SELECTOR));
|
|
164
184
|
if (scope instanceof Element && scope.matches(ISLAND_SELECTOR)) candidates.unshift(scope);
|
|
165
|
-
const islands = candidates.filter((container) => !roots.has(container) && !pending.has(container) && !container.parentElement?.closest(ISLAND_SELECTOR));
|
|
185
|
+
const islands = candidates.filter((container) => !roots.has(container) && !pending.has(container) && !container.parentElement?.closest(ISLAND_SELECTOR) && !container.closest(STREAM_STAGING));
|
|
166
186
|
await Promise.all(islands.map(async (container) => {
|
|
167
187
|
const src = container.getAttribute("src");
|
|
168
188
|
const id = container.getAttribute("props");
|
|
@@ -312,6 +332,39 @@ function createHydrationRuntime(options) {
|
|
|
312
332
|
return updated;
|
|
313
333
|
}
|
|
314
334
|
/** Unmounts the islands inside `scope`, and stops scheduled ones. */
|
|
335
|
+
/**
|
|
336
|
+
* A page still streaming: islands in Suspense content that arrives later hydrate as React puts it
|
|
337
|
+
* in place, and islands React removes (a replaced fallback) are released. Ends once the document
|
|
338
|
+
* is parsed and React has revealed every staged boundary (it may batch reveals past parsing).
|
|
339
|
+
*/
|
|
340
|
+
let watching = false;
|
|
341
|
+
function watchStream(document) {
|
|
342
|
+
if (watching || document.readyState !== "loading" || typeof MutationObserver !== "function") return;
|
|
343
|
+
watching = true;
|
|
344
|
+
const removed = /* @__PURE__ */ new Set();
|
|
345
|
+
let queued = false;
|
|
346
|
+
const run = () => {
|
|
347
|
+
queued = false;
|
|
348
|
+
for (const node of removed) if (!node.isConnected) dispose(node);
|
|
349
|
+
removed.clear();
|
|
350
|
+
if (document.readyState !== "loading" && !document.querySelector(STREAM_STAGING)) {
|
|
351
|
+
observer.disconnect();
|
|
352
|
+
watching = false;
|
|
353
|
+
}
|
|
354
|
+
hydrate(document).catch((error) => report("Hydrating streamed islands failed.", error));
|
|
355
|
+
};
|
|
356
|
+
const observer = new MutationObserver((records) => {
|
|
357
|
+
for (const record of records) for (const node of record.removedNodes) removed.add(node);
|
|
358
|
+
if (queued) return;
|
|
359
|
+
queued = true;
|
|
360
|
+
queueMicrotask(run);
|
|
361
|
+
});
|
|
362
|
+
observer.observe(document, {
|
|
363
|
+
childList: true,
|
|
364
|
+
subtree: true
|
|
365
|
+
});
|
|
366
|
+
document.addEventListener("DOMContentLoaded", run, { once: true });
|
|
367
|
+
}
|
|
315
368
|
function dispose(scope) {
|
|
316
369
|
for (const [container, controller] of pending) if (container === scope || scope.contains(container)) {
|
|
317
370
|
controller.abort();
|
|
@@ -400,4 +453,4 @@ async function swapDocument(runtime, next) {
|
|
|
400
453
|
document.body.replaceChildren(...Array.from(body.childNodes));
|
|
401
454
|
await runtime.hydrate(document);
|
|
402
455
|
}
|
|
403
|
-
export { HYDRATED_ATTRIBUTE, ISLAND_STRATEGIES, PAYLOAD_ONLY_ATTRIBUTE, createHydrationRuntime, createServerHtml, createServerTreeElement, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
|
|
456
|
+
export { HYDRATED_ATTRIBUTE, ISLAND_STRATEGIES, PAYLOAD_ONLY_ATTRIBUTE, createHydrationRuntime, createServerHtml, createServerTreeElement, createServerTreeLayout, isIslandStrategy, isolateIslands, refreshDocument, scheduleHydration, swapDocument, updateServerTrees, withinClientRoot };
|
package/dist/react/server.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as ReactFormState } from "../_chunks/handle-C0N9ZyNV.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { _ as setRenderStateResolver, d as ClientReference, f as IslandRenderState, g as isolateIslands, h as getClientReference, l as renderServerTree, m as createIsland, n as PAYLOAD_ONLY_ATTRIBUTE, o as ServerTreeRenderOptions, p as IslandsProvider, t as Outlet, v as withinClientRoot } from "../_chunks/tree-B4RMU2UX.mjs";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { RenderToReadableStreamOptions } from "react-dom/server";
|
|
5
5
|
|
|
@@ -48,4 +48,4 @@ declare function renderToStream(element: React.ReactNode, {
|
|
|
48
48
|
}>;
|
|
49
49
|
/** Whether a response rendered for `request` leaves out values its browser holds. */
|
|
50
50
|
declare function isPersonalized(request: Request): boolean;
|
|
51
|
-
export { type ClientReference, type IslandRenderState, IslandsProvider, PAYLOAD_ONLY_ATTRIBUTE, RenderToStreamOptions, type ServerTreeRenderOptions, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
|
|
51
|
+
export { type ClientReference, type IslandRenderState, IslandsProvider, Outlet, PAYLOAD_ONLY_ATTRIBUTE, RenderToStreamOptions, type ServerTreeRenderOptions, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
|
package/dist/react/server.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as encode } from "../_chunks/codec-
|
|
1
|
+
import { a as encode } from "../_chunks/codec-DqfVYKsx.mjs";
|
|
2
2
|
import { f as FORM_STATE_SCRIPT_TYPE } from "../_chunks/wire-CXOodE06.mjs";
|
|
3
3
|
import { r as PREHYDRATION_SCRIPT } from "../_chunks/prehydration-B1GLTLdk.mjs";
|
|
4
|
-
import { g as escapeScriptJson, h as addressValues, i as encodeActionReference, v as parseHeldValues, y as selectValuesToSend } from "../_chunks/actions-
|
|
4
|
+
import { g as escapeScriptJson, h as addressValues, i as encodeActionReference, v as parseHeldValues, y as selectValuesToSend } from "../_chunks/actions-B8dJ8f7L.mjs";
|
|
5
5
|
import { t as getBuildDefaults } from "../_chunks/build-COIcs7wm.mjs";
|
|
6
|
-
import { a as
|
|
6
|
+
import { a as renderServerTree, c as createIsland, d as isolateIslands, f as runtimeTagged, h as withinClientRoot, l as getClientReference, m as setRenderStateResolver, n as PAYLOAD_ONLY_ATTRIBUTE, p as setIslandServerHook, s as IslandsProvider, t as Outlet, u as getContexts } from "../_chunks/tree-BvYR6-PF.mjs";
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
// src/react/server.ts
|
|
9
9
|
/**
|
|
@@ -81,4 +81,4 @@ async function renderToStream(element, { request, formState, bootstrap = true, p
|
|
|
81
81
|
function isPersonalized(request) {
|
|
82
82
|
return parseHeldValues(request).size > 0;
|
|
83
83
|
}
|
|
84
|
-
export { IslandsProvider, PAYLOAD_ONLY_ATTRIBUTE, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
|
|
84
|
+
export { IslandsProvider, Outlet, PAYLOAD_ONLY_ATTRIBUTE, createIsland, getClientReference, isPersonalized, isolateIslands, renderServerTree, renderToStream, setRenderStateResolver, withinClientRoot };
|
package/dist/react.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as IslandRenderState, g as isolateIslands, l as renderServerTree, m as createIsland, o as ServerTreeRenderOptions, p as IslandsProvider, t as Outlet, v as withinClientRoot } from "./_chunks/tree-B4RMU2UX.mjs";
|
|
2
2
|
import { n as IslandStrategy } from "./_chunks/schedule-Dw14dW0H.mjs";
|
|
3
|
-
export { type IslandRenderState, type IslandStrategy, IslandsProvider, type ServerTreeRenderOptions, createIsland, isolateIslands, renderServerTree, withinClientRoot };
|
|
3
|
+
export { type IslandRenderState, type IslandStrategy, IslandsProvider, Outlet, type ServerTreeRenderOptions, createIsland, isolateIslands, renderServerTree, withinClientRoot };
|
package/dist/react.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { s as IslandsProvider, t as Outlet, c as createIsland, d as isolateIslands, a as renderServerTree, h as withinClientRoot } from "./_chunks/tree-BvYR6-PF.mjs";
|
package/dist/server.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as hashValue, a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, g as escapeScriptJson, h as addressValues, i as encodeActionReference, l as SealError, m as VALUE_MIN_LENGTH, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys, v as parseHeldValues, y as selectValuesToSend } from "./_chunks/actions-
|
|
2
|
-
import { a as encode, r as decode } from "./_chunks/codec-
|
|
1
|
+
import { _ as hashValue, a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, g as escapeScriptJson, h as addressValues, i as encodeActionReference, l as SealError, m as VALUE_MIN_LENGTH, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys, v as parseHeldValues, y as selectValuesToSend } from "./_chunks/actions-B8dJ8f7L.mjs";
|
|
2
|
+
import { a as encode, r as decode } from "./_chunks/codec-DqfVYKsx.mjs";
|
|
3
3
|
import { F as stripRuntimeFields, P as readPostedAction, j as attachmentField, n as ACTION_KEY_FIELD, o as BOUND_FIELD, r as ACTION_STALE_HEADER, s as CALL_FIELD, t as ACTION_HEADER } from "./_chunks/wire-CXOodE06.mjs";
|
|
4
4
|
import { n as setBuildDefaults, t as getBuildDefaults } from "./_chunks/build-COIcs7wm.mjs";
|
|
5
5
|
// src/server/hooks.ts
|
package/package.json
CHANGED