@farm.js/islx 0.0.7 → 0.0.9
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 +41 -0
- package/README.md +56 -26
- package/SPEC.md +5 -4
- package/dist/_chunks/{actions-C-IwUCKU.mjs → actions-DAzd4PpV.mjs} +2 -2
- package/dist/_chunks/{actions-CvQ0-zjg.mjs → actions-DzIPDecd.mjs} +2 -2
- package/dist/_chunks/{codec-C8to-RQW.mjs → codec-DDpUvgRo.mjs} +1 -1
- package/dist/_chunks/{actions-Eutk6UzT.mjs → form-status-CRfnB7WT.mjs} +314 -1
- package/dist/_chunks/forms-CCDY9QHC.mjs +63 -0
- package/dist/_chunks/{tree-C47p6khN.mjs → island-viWIOM1k.mjs} +501 -8
- package/dist/_chunks/{prehydration-B1GLTLdk.mjs → prehydration-zEYsfFxW.mjs} +3 -2
- package/dist/_chunks/{schedule-CjoybS3b.mjs → schedule-IK9_RwZC.mjs} +13 -4
- package/dist/_chunks/{tree-B4RMU2UX.d.mts → tree-CV13RsJv.d.mts} +8 -1
- package/dist/_chunks/{values-37UIzxM0.mjs → values-gxhIei_m.mjs} +2 -2
- package/dist/_chunks/{vite-BTzYlEiY.mjs → vite-CHB6B8tP.mjs} +36 -12
- package/dist/_chunks/{wire-Dipqh-EB.d.mts → wire-1d6wqOia.d.mts} +16 -1
- package/dist/_chunks/{wire-QEQWBr5s.mjs → wire-UUwZlUJZ.mjs} +17 -1
- package/dist/boot.d.mts +31 -0
- package/dist/boot.mjs +177 -0
- package/dist/client.d.mts +2 -1
- package/dist/client.mjs +4 -28
- package/dist/compiler.d.mts +35 -1
- package/dist/compiler.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/react/client.d.mts +2 -2
- package/dist/react/client.mjs +88 -19
- package/dist/react/server.d.mts +1 -1
- package/dist/react/server.mjs +13 -10
- package/dist/react.d.mts +6 -2
- package/dist/react.mjs +22 -3
- package/dist/server.d.mts +5 -0
- package/dist/server.mjs +4 -4
- package/dist/tanstack-start/server.mjs +3 -3
- package/dist/tanstack-start.mjs +1 -1
- package/dist/vite.d.mts +16 -1
- package/dist/vite.mjs +1 -1
- package/package.json +5 -1
- package/dist/_chunks/island-DVOr-OUw.mjs +0 -262
package/dist/react/client.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { d as valueHeaders, o as emit, t as createServerReference, u as getValueStore } from "../_chunks/actions-
|
|
2
|
-
import { i as decodeTagged, n as NOT_A_VALUE, r as decode, u as setOwn } from "../_chunks/codec-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { n as isIslandStrategy, r as scheduleHydration, t as ISLAND_STRATEGIES } from "../_chunks/schedule-CjoybS3b.mjs";
|
|
1
|
+
import { d as valueHeaders, o as emit, t as createServerReference, u as getValueStore } from "../_chunks/actions-DAzd4PpV.mjs";
|
|
2
|
+
import { i as decodeTagged, n as NOT_A_VALUE, r as decode, u as setOwn } from "../_chunks/codec-DDpUvgRo.mjs";
|
|
3
|
+
import { D as SLOT_TEMPLATE_ELEMENT, R as getTag, S as PROPS_SCRIPT_TYPE, T as SLOT_ELEMENT, b as PROMISE_SCRIPT_TYPE, f as FORM_STATE_SCRIPT_TYPE, g as ISLAND_ELEMENT, m as HYDRATED_ATTRIBUTE } from "../_chunks/wire-UUwZlUJZ.mjs";
|
|
4
|
+
import { _ as atIdPosition, a as isolateIslands, d as PAYLOAD_ONLY_ATTRIBUTE, f as createServerTreeElement, g as updateServerTrees, i as getContexts, l as withinClientRoot, m as createSlotTree, p as createServerTreeLayout, v as parseIdPosition } from "../_chunks/island-viWIOM1k.mjs";
|
|
5
|
+
import { n as isIslandStrategy, r as scheduleHydration, t as ISLAND_STRATEGIES } from "../_chunks/schedule-IK9_RwZC.mjs";
|
|
7
6
|
import * as React from "react";
|
|
8
7
|
import { hydrateRoot } from "react-dom/client";
|
|
9
8
|
// src/react/hydrate.ts
|
|
@@ -39,6 +38,37 @@ function readFormState(document) {
|
|
|
39
38
|
*/
|
|
40
39
|
const revivedValues = /* @__PURE__ */ new WeakMap();
|
|
41
40
|
let slotsRevived = 0;
|
|
41
|
+
/**
|
|
42
|
+
* A slot's server HTML: inside the island where it placed the slot, or in the template after its
|
|
43
|
+
* props for a slot it had not placed.
|
|
44
|
+
*/
|
|
45
|
+
function findSlot(container, name) {
|
|
46
|
+
for (const slot of container.querySelectorAll(SLOT_ELEMENT)) if (slot.getAttribute("name") === name) return slot;
|
|
47
|
+
const id = container.getAttribute("props");
|
|
48
|
+
for (const template of container.ownerDocument.querySelectorAll(`${SLOT_TEMPLATE_ELEMENT}[for]`)) {
|
|
49
|
+
if (template.getAttribute("for") !== id) continue;
|
|
50
|
+
for (const slot of template.content.querySelectorAll(SLOT_ELEMENT)) if (slot.getAttribute("name") === name) return slot;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/** Each island's slots that carry server trees, rebuilt before the island hydrates. */
|
|
55
|
+
const preparedSlots = /* @__PURE__ */ new WeakMap();
|
|
56
|
+
/** The names of the slots in `value` that carry server trees. */
|
|
57
|
+
function collectTreeSlots(value, names, seen = /* @__PURE__ */ new Set()) {
|
|
58
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
59
|
+
seen.add(value);
|
|
60
|
+
const object = value;
|
|
61
|
+
if (object["$"] === "s" && object["t"] === 1 && typeof object["v"] === "string") {
|
|
62
|
+
names.add(object["v"]);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const entry of Array.isArray(value) ? value : Object.values(object)) collectTreeSlots(entry, names, seen);
|
|
66
|
+
}
|
|
67
|
+
/** Whether `container` sits inside another island below `scope`. */
|
|
68
|
+
function insideAnotherIsland(container, scope) {
|
|
69
|
+
for (let node = container.parentElement; node && node !== scope; node = node.parentElement) if (node.matches(ISLAND_SELECTOR)) return true;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
42
72
|
/** Turns travelled props back into values: slots keep their server HTML, actions become callable. */
|
|
43
73
|
function revive(value, container, values) {
|
|
44
74
|
if (value && typeof value === "object") {
|
|
@@ -70,11 +100,16 @@ function reviveUncached(value, container, values) {
|
|
|
70
100
|
if (tag === "s" && typeof object["v"] === "string") {
|
|
71
101
|
slotsRevived++;
|
|
72
102
|
const name = object["v"];
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
103
|
+
if (object["t"] === 1) {
|
|
104
|
+
const tree = preparedSlots.get(container)?.get(name);
|
|
105
|
+
if (tree === void 0) throw new Error(`the server tree of slot ${name} was not prepared`);
|
|
106
|
+
return React.createElement(SLOT_ELEMENT, {
|
|
107
|
+
key: name,
|
|
108
|
+
name,
|
|
109
|
+
style: { display: "contents" }
|
|
110
|
+
}, tree);
|
|
77
111
|
}
|
|
112
|
+
const html = findSlot(container, name)?.innerHTML ?? "";
|
|
78
113
|
return React.createElement(SLOT_ELEMENT, {
|
|
79
114
|
key: name,
|
|
80
115
|
name,
|
|
@@ -277,14 +312,43 @@ function createHydrationRuntime(options) {
|
|
|
277
312
|
}, [onCommit]);
|
|
278
313
|
return children;
|
|
279
314
|
}
|
|
280
|
-
const element = (Component, props, restore, onCommit) => {
|
|
281
|
-
const { graph } = getContexts();
|
|
282
|
-
const
|
|
283
|
-
|
|
315
|
+
const element = (container, Component, props, restore, position, onCommit) => {
|
|
316
|
+
const { graph, container: containerContext } = getContexts();
|
|
317
|
+
const component = React.createElement(Component, props);
|
|
318
|
+
const inner = position && atIdPosition(position, component) || component;
|
|
319
|
+
return React.createElement(graph.Provider, { value: true }, React.createElement(containerContext.Provider, { value: container }, React.createElement(Boundary, { onError: restore }, React.createElement(Committed, {
|
|
284
320
|
onCommit,
|
|
285
321
|
children: options.wrap ? options.wrap(inner) : inner
|
|
286
|
-
})));
|
|
322
|
+
}))));
|
|
287
323
|
};
|
|
324
|
+
const ServerHtml = createServerHtml({
|
|
325
|
+
hydrate,
|
|
326
|
+
dispose
|
|
327
|
+
});
|
|
328
|
+
/** Rebuilds the server trees of an island's slots, loading their client modules. */
|
|
329
|
+
async function prepareSlots(container, id) {
|
|
330
|
+
const text = findPropsScript(container, id)?.textContent ?? "";
|
|
331
|
+
if (!text.includes(`"t":1`)) return;
|
|
332
|
+
let parsed = JSON.parse(text);
|
|
333
|
+
if (text.includes(`"\$":"r"`)) {
|
|
334
|
+
values.ingest(container.ownerDocument);
|
|
335
|
+
parsed = values.resolve(parsed);
|
|
336
|
+
}
|
|
337
|
+
const names = /* @__PURE__ */ new Set();
|
|
338
|
+
collectTreeSlots(parsed, names);
|
|
339
|
+
const trees = /* @__PURE__ */ new Map();
|
|
340
|
+
await Promise.all(Array.from(names, async (name) => {
|
|
341
|
+
const slot = findSlot(container, name);
|
|
342
|
+
const tree = slot && await createSlotTree(slot, name, {
|
|
343
|
+
load: options.load,
|
|
344
|
+
ServerHtml,
|
|
345
|
+
values
|
|
346
|
+
});
|
|
347
|
+
if (!tree) throw new Error(`slot ${name} has no server tree`);
|
|
348
|
+
trees.set(name, tree);
|
|
349
|
+
}));
|
|
350
|
+
preparedSlots.set(container, trees);
|
|
351
|
+
}
|
|
288
352
|
/** An island's props, with values, slots and actions revived. */
|
|
289
353
|
function readProps(container, id) {
|
|
290
354
|
const script = findPropsScript(container, id);
|
|
@@ -310,7 +374,7 @@ function createHydrationRuntime(options) {
|
|
|
310
374
|
if (scope instanceof Document) watchStream(scope);
|
|
311
375
|
const candidates = Array.from(scope.querySelectorAll(ISLAND_SELECTOR));
|
|
312
376
|
if (scope instanceof Element && scope.matches(ISLAND_SELECTOR)) candidates.unshift(scope);
|
|
313
|
-
const islands = candidates.filter((container) => !roots.has(container) && !pending.has(container) && !container
|
|
377
|
+
const islands = candidates.filter((container) => !roots.has(container) && !pending.has(container) && !insideAnotherIsland(container, scope) && !container.closest(STREAM_STAGING) && !awaitsStreamedContent(container));
|
|
314
378
|
await Promise.all(islands.map(async (container) => {
|
|
315
379
|
const src = container.getAttribute("src");
|
|
316
380
|
const id = container.getAttribute("props");
|
|
@@ -348,7 +412,10 @@ function createHydrationRuntime(options) {
|
|
|
348
412
|
if (controller.signal.aborted || !container.isConnected) return;
|
|
349
413
|
const Component = module[exportName];
|
|
350
414
|
if (typeof Component !== "function" && (typeof Component !== "object" || Component === null)) throw new Error(`${src} has no export ${exportName}`);
|
|
415
|
+
await prepareSlots(container, id);
|
|
416
|
+
if (controller.signal.aborted || !container.isConnected) return;
|
|
351
417
|
const props = readProps(container, id);
|
|
418
|
+
const position = parseIdPosition(findPropsScript(container, id)?.getAttribute("ids"));
|
|
352
419
|
let failure;
|
|
353
420
|
const restore = (error) => {
|
|
354
421
|
if (failure !== void 0) return;
|
|
@@ -364,8 +431,9 @@ function createHydrationRuntime(options) {
|
|
|
364
431
|
strategy,
|
|
365
432
|
duration: performance.now() - scheduledAt
|
|
366
433
|
});
|
|
367
|
-
const root = hydrateRoot(container, element(Component, props, restore, onCommit), {
|
|
434
|
+
const root = hydrateRoot(container, element(container, Component, props, restore, position, onCommit), {
|
|
368
435
|
formState,
|
|
436
|
+
...position ? { identifierPrefix: position.prefix } : {},
|
|
369
437
|
onUncaughtError: restore,
|
|
370
438
|
onRecoverableError(error) {
|
|
371
439
|
if (disposing && isEarlyUpdate(error)) return;
|
|
@@ -374,6 +442,7 @@ function createHydrationRuntime(options) {
|
|
|
374
442
|
}
|
|
375
443
|
});
|
|
376
444
|
roots.set(container, {
|
|
445
|
+
position,
|
|
377
446
|
root,
|
|
378
447
|
Component,
|
|
379
448
|
src,
|
|
@@ -433,7 +502,7 @@ function createHydrationRuntime(options) {
|
|
|
433
502
|
if (!previous || !record || !id) continue;
|
|
434
503
|
try {
|
|
435
504
|
const props = readProps(container, id);
|
|
436
|
-
record.root.render(element(record.Component, props, record.restore));
|
|
505
|
+
record.root.render(element(previous, record.Component, props, record.restore, record.position));
|
|
437
506
|
record.props = props;
|
|
438
507
|
container.replaceWith(previous);
|
|
439
508
|
carried++;
|
|
@@ -454,7 +523,7 @@ function createHydrationRuntime(options) {
|
|
|
454
523
|
continue;
|
|
455
524
|
}
|
|
456
525
|
record.Component = Component;
|
|
457
|
-
record.root.render(element(record.Component, record.props, record.restore));
|
|
526
|
+
record.root.render(element(container, record.Component, record.props, record.restore, record.position));
|
|
458
527
|
updated++;
|
|
459
528
|
}
|
|
460
529
|
return updated;
|
package/dist/react/server.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as ReactFormState } from "../_chunks/handle-C0N9ZyNV.mjs";
|
|
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-
|
|
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-CV13RsJv.mjs";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { RenderToReadableStreamOptions } from "react-dom/server";
|
|
5
5
|
|
package/dist/react/server.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { a as encode } from "../_chunks/codec-
|
|
2
|
-
import { f as FORM_STATE_SCRIPT_TYPE } from "../_chunks/wire-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { a as encode } from "../_chunks/codec-DDpUvgRo.mjs";
|
|
2
|
+
import { f as FORM_STATE_SCRIPT_TYPE } from "../_chunks/wire-UUwZlUJZ.mjs";
|
|
3
|
+
import { RUNTIME_GLOBAL } from "../boot.mjs";
|
|
4
|
+
import { r as PREHYDRATION_SCRIPT } from "../_chunks/prehydration-zEYsfFxW.mjs";
|
|
5
|
+
import { i as encodeActionReference } from "../_chunks/actions-DzIPDecd.mjs";
|
|
5
6
|
import { t as getBuildDefaults } from "../_chunks/build-COIcs7wm.mjs";
|
|
6
|
-
import { a as parseHeldValues, n as addressValues, o as selectValuesToSend, r as escapeScriptJson } from "../_chunks/values-
|
|
7
|
-
import { c as
|
|
8
|
-
import { a as renderServerTree, n as PAYLOAD_ONLY_ATTRIBUTE, t as Outlet } from "../_chunks/tree-C47p6khN.mjs";
|
|
7
|
+
import { a as parseHeldValues, n as addressValues, o as selectValuesToSend, r as escapeScriptJson } from "../_chunks/values-gxhIei_m.mjs";
|
|
8
|
+
import { a as isolateIslands, c as setRenderStateResolver, d as PAYLOAD_ONLY_ATTRIBUTE, h as renderServerTree, i as getContexts, l as withinClientRoot, n as createIsland, o as runtimeTagged, r as getClientReference, s as setIslandServerHook, t as IslandsProvider, u as Outlet } from "../_chunks/island-viWIOM1k.mjs";
|
|
9
9
|
import * as React from "react";
|
|
10
10
|
// src/react/server.ts
|
|
11
11
|
/**
|
|
@@ -88,9 +88,12 @@ async function renderToStream(element, { request, formState, bootstrap = true, p
|
|
|
88
88
|
held: held && held.size > 0 ? held : null,
|
|
89
89
|
sent: /* @__PURE__ */ new Set()
|
|
90
90
|
} }, element, formState ? React.createElement(FormStateScript, { formState }) : null);
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
91
|
+
const defaults = getBuildDefaults();
|
|
92
|
+
const entry = bootstrap ? defaults.entry : void 0;
|
|
93
|
+
const boot = entry !== void 0 ? defaults.boot : void 0;
|
|
94
|
+
const bootstrapModules = entry ? [...options.bootstrapModules ?? [], boot ?? entry] : options.bootstrapModules;
|
|
95
|
+
const runtime = boot ? `window.${RUNTIME_GLOBAL}=${escapeScriptJson(JSON.stringify(entry))};` : "";
|
|
96
|
+
const bootstrapScriptContent = prehydration ?? entry !== void 0 ? `${PREHYDRATION_SCRIPT}${runtime}${options.bootstrapScriptContent ?? ""}` : runtime || options.bootstrapScriptContent ? `${runtime}${options.bootstrapScriptContent ?? ""}` : void 0;
|
|
94
97
|
const { renderToReadableStream } = await import("react-dom/server");
|
|
95
98
|
const stream = await renderToReadableStream(tree, {
|
|
96
99
|
...options,
|
package/dist/react.d.mts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
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-
|
|
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-CV13RsJv.mjs";
|
|
2
2
|
import { n as IslandStrategy } from "./_chunks/schedule-Dw14dW0H.mjs";
|
|
3
|
-
|
|
3
|
+
import * as ReactDOM from "react-dom";
|
|
4
|
+
|
|
5
|
+
// src/react/form-status.d.ts
|
|
6
|
+
declare function useFormStatus(): ReactDOM.FormStatus;
|
|
7
|
+
export { type IslandRenderState, type IslandStrategy, IslandsProvider, Outlet, type ServerTreeRenderOptions, createIsland, isolateIslands, renderServerTree, useFormStatus, withinClientRoot };
|
package/dist/react.mjs
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { a as isolateIslands, h as renderServerTree, i as getContexts, l as withinClientRoot, n as createIsland, t as IslandsProvider, u as Outlet } from "./_chunks/island-viWIOM1k.mjs";
|
|
2
|
+
import { i as subscribeServerForms, r as getServerFormStatus, t as IDLE_FORM_STATUS } from "./_chunks/forms-CCDY9QHC.mjs";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as ReactDOM from "react-dom";
|
|
5
|
+
// src/react/form-status.ts
|
|
6
|
+
/**
|
|
7
|
+
* `useFormStatus` for islands. React reports the status of the React `<form>` around a component,
|
|
8
|
+
* but a form written in a server component is server HTML: an island inside it is a React root of
|
|
9
|
+
* its own, with no form above it, so React's hook never sees the form submit. The compiler points
|
|
10
|
+
* client modules' `useFormStatus` from `react-dom` here.
|
|
11
|
+
*
|
|
12
|
+
* Forms do not nest, so an island inside a `<form>` element has no React form of its own around
|
|
13
|
+
* its components: that server form is the one, and `enhanceForms` reports its submissions.
|
|
14
|
+
* Anywhere else this is React's hook.
|
|
15
|
+
*/
|
|
16
|
+
function useFormStatus() {
|
|
17
|
+
const status = ReactDOM.useFormStatus();
|
|
18
|
+
const form = React.useContext(getContexts().container)?.parentElement?.closest("form") ?? null;
|
|
19
|
+
const server = React.useSyncExternalStore(subscribeServerForms, () => form ? getServerFormStatus(form) : IDLE_FORM_STATUS, () => IDLE_FORM_STATUS);
|
|
20
|
+
return form ? server : status;
|
|
21
|
+
}
|
|
22
|
+
export { IslandsProvider, Outlet, createIsland, isolateIslands, renderServerTree, useFormStatus, withinClientRoot };
|
package/dist/server.d.mts
CHANGED
|
@@ -187,6 +187,11 @@ declare function isSameOriginRequest(request: Request, allowedOrigins: readonly
|
|
|
187
187
|
interface BuildDefaults {
|
|
188
188
|
/** The URL of the browser entry `renderToStream` adds to pages. */
|
|
189
189
|
entry?: string;
|
|
190
|
+
/**
|
|
191
|
+
* The URL of the boot entry (`@farm.js/islx/boot`): pages start with it instead, and it loads
|
|
192
|
+
* `entry` when the page first needs it. An island that hydrates at load preloads `entry`.
|
|
193
|
+
*/
|
|
194
|
+
boot?: string;
|
|
190
195
|
/** A key made for this build: every instance of one build shares it. */
|
|
191
196
|
key?: string;
|
|
192
197
|
/**
|
package/dist/server.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as parseHeldValues, i as hashValue, n as addressValues, o as selectValuesToSend, r as escapeScriptJson, t as VALUE_MIN_LENGTH } from "./_chunks/values-
|
|
2
|
-
import { a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, i as encodeActionReference, l as SealError, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys } from "./_chunks/actions-
|
|
3
|
-
import { a as encode, r as decode } from "./_chunks/codec-
|
|
4
|
-
import {
|
|
1
|
+
import { a as parseHeldValues, i as hashValue, n as addressValues, o as selectValuesToSend, r as escapeScriptJson, t as VALUE_MIN_LENGTH } from "./_chunks/values-gxhIei_m.mjs";
|
|
2
|
+
import { a as getActionReference, c as resolveAction, d as generateActionKey, f as sealBound, i as encodeActionReference, l as SealError, n as configureActionKeys, o as registerInlineAction, p as unsealBound, r as decodeActionReference, s as registerServerReference, t as bindInlineAction, u as createActionKeys } from "./_chunks/actions-DzIPDecd.mjs";
|
|
3
|
+
import { a as encode, r as decode } from "./_chunks/codec-DDpUvgRo.mjs";
|
|
4
|
+
import { B as stripRuntimeFields, I as attachmentField, n as ACTION_KEY_FIELD, o as BOUND_FIELD, r as ACTION_STALE_HEADER, s as CALL_FIELD, t as ACTION_HEADER, z as readPostedAction } from "./_chunks/wire-UUwZlUJZ.mjs";
|
|
5
5
|
import { n as setBuildDefaults, t as getBuildDefaults } from "./_chunks/build-COIcs7wm.mjs";
|
|
6
6
|
// src/server/hooks.ts
|
|
7
7
|
const HOOKS_KEY = Symbol.for("islx.server.hooks");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as IslandsProvider } from "../_chunks/island-
|
|
2
|
-
import {
|
|
3
|
-
import { a as parseHeldValues } from "../_chunks/values-
|
|
1
|
+
import { t as IslandsProvider } from "../_chunks/island-viWIOM1k.mjs";
|
|
2
|
+
import { N as VALUES_HEADER, w as RENDER_HEADER } from "../_chunks/wire-UUwZlUJZ.mjs";
|
|
3
|
+
import { a as parseHeldValues } from "../_chunks/values-gxhIei_m.mjs";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { StartServer } from "@tanstack/react-start/server";
|
|
6
6
|
import { createSsrStreamResponse, defineHandlerCallback, getSsrStatus, renderRouterToStream, transformReadableStreamWithRouter } from "@tanstack/react-router/ssr/server";
|
package/dist/tanstack-start.mjs
CHANGED
package/dist/vite.d.mts
CHANGED
|
@@ -42,9 +42,24 @@ interface IslxOptions {
|
|
|
42
42
|
client?: string | ClientHooks;
|
|
43
43
|
server?: string | ServerHooks;
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* A client module without a `hydrate` export waits for a click when its code provably does
|
|
47
|
+
* nothing before one (`inferStrategy` in `@farm.js/islx/compiler`): a page of such islands loads
|
|
48
|
+
* the runtime only when one is used. Default true; false hydrates them at load.
|
|
49
|
+
*/
|
|
50
|
+
infer?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Pages start with a small boot script (`@farm.js/islx/boot`) instead of the browser entry,
|
|
53
|
+
* and load the entry when the page first needs it: at once when an island hydrates at load,
|
|
54
|
+
* otherwise on an island's trigger or a link. Default true; false starts every page with the
|
|
55
|
+
* entry.
|
|
56
|
+
*/
|
|
57
|
+
boot?: boolean;
|
|
45
58
|
}
|
|
46
59
|
/** @internal The browser entry, for a framework whose own entry imports it. */
|
|
47
60
|
declare const ENTRY_ID = "virtual:islx/client-entry";
|
|
61
|
+
/** The boot entry: pages start with it, and it loads ENTRY_ID when the page first needs it. */
|
|
62
|
+
declare const BOOT_ID = "virtual:islx/boot";
|
|
48
63
|
/**
|
|
49
64
|
* The source of a hooks module made from callbacks. Each callback is copied as written, so it
|
|
50
65
|
* must not use anything from where it was written: an outside variable, an import.
|
|
@@ -73,4 +88,4 @@ interface IslxHost {
|
|
|
73
88
|
frameworkBuild?: boolean;
|
|
74
89
|
}
|
|
75
90
|
declare function islx(options?: IslxOptions, host?: IslxHost): Plugin[];
|
|
76
|
-
export { ENTRY_ID, IslxHost, IslxOptions, islx as default, islx, resolveDirectives, serializeHooks };
|
|
91
|
+
export { BOOT_ID, ENTRY_ID, IslxHost, IslxOptions, islx as default, islx, resolveDirectives, serializeHooks };
|
package/dist/vite.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as
|
|
1
|
+
export { t as BOOT_ID, n as ENTRY_ID, r as default, r as islx, i as resolveDirectives, a as serializeHooks } from "./_chunks/vite-CHB6B8tP.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/islx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "A renderer-agnostic wire format for server-rendered islands: typed props, values sent once, server actions, and partial navigations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"islands",
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"types": "./dist/client.d.mts",
|
|
40
40
|
"default": "./dist/client.mjs"
|
|
41
41
|
},
|
|
42
|
+
"./boot": {
|
|
43
|
+
"types": "./dist/boot.d.mts",
|
|
44
|
+
"default": "./dist/boot.mjs"
|
|
45
|
+
},
|
|
42
46
|
"./server": {
|
|
43
47
|
"types": "./dist/server.d.mts",
|
|
44
48
|
"default": "./dist/server.mjs"
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { t as getBuildDefaults } from "./build-COIcs7wm.mjs";
|
|
2
|
-
import { M as VALUE_SCRIPT_TYPE, h as ISLAND_ELEMENT, w as SLOT_ELEMENT, x as PROPS_SCRIPT_TYPE, y as PROMISE_SCRIPT_TYPE } from "./wire-QEQWBr5s.mjs";
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import * as ReactDOM from "react-dom";
|
|
5
|
-
// src/react/island.ts
|
|
6
|
-
/**
|
|
7
|
-
* A client component, as the server renders it: its HTML inside `<f-island>`, and its props in
|
|
8
|
-
* a script after it. Shared by the server and the browser: inside a hydrating island, a nested
|
|
9
|
-
* client component is part of that island's React tree, not an island of its own.
|
|
10
|
-
*
|
|
11
|
-
* The compiler wraps every export of a `"use client"` module with `createIsland` in the server
|
|
12
|
-
* build. The browser build keeps the module as it is.
|
|
13
|
-
*/
|
|
14
|
-
const CLIENT_REFERENCE = Symbol.for("islx.react.client-reference");
|
|
15
|
-
/** The module export an island component renders, or undefined for any other type. */
|
|
16
|
-
function getClientReference(type) {
|
|
17
|
-
if (!type || typeof type !== "function" && typeof type !== "object") return void 0;
|
|
18
|
-
return type[CLIENT_REFERENCE];
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Makes `element`'s client components part of the React root around it (a layout that hydrates
|
|
22
|
-
* as one root), instead of islands of their own.
|
|
23
|
-
*/
|
|
24
|
-
function withinClientRoot(element) {
|
|
25
|
-
return React.createElement(getContexts().graph.Provider, { value: true }, element);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Starts islands again below a `withinClientRoot`: the page a hydrating layout keeps as server
|
|
29
|
-
* HTML renders its client components as islands of their own.
|
|
30
|
-
*/
|
|
31
|
-
function isolateIslands(element) {
|
|
32
|
-
return React.createElement(getContexts().graph.Provider, { value: false }, element);
|
|
33
|
-
}
|
|
34
|
-
const RENDER_STATE_RESOLVER = Symbol.for("islx.react.render-state");
|
|
35
|
-
/**
|
|
36
|
-
* For a framework that knows the request being rendered without React context (a request store):
|
|
37
|
-
* returns the render's settings when no `IslandsProvider` or `renderToStream` gave them. Keep the
|
|
38
|
-
* same `sent` set for the whole response.
|
|
39
|
-
*/
|
|
40
|
-
function setRenderStateResolver(resolve) {
|
|
41
|
-
globalThis[RENDER_STATE_RESOLVER] = resolve;
|
|
42
|
-
}
|
|
43
|
-
function resolveRenderState() {
|
|
44
|
-
const resolve = globalThis[RENDER_STATE_RESOLVER];
|
|
45
|
-
return resolve ? resolve() : null;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Gives islands a render's settings when the framework renders the page itself (not through
|
|
49
|
-
* `renderToStream`): which values the browser holds, and which this render already wrote.
|
|
50
|
-
*/
|
|
51
|
-
function IslandsProvider({ held = null, children }) {
|
|
52
|
-
const state = React.useMemo(() => ({
|
|
53
|
-
held: held && held.size > 0 ? held : null,
|
|
54
|
-
sent: /* @__PURE__ */ new Set()
|
|
55
|
-
}), [held]);
|
|
56
|
-
return React.createElement(getContexts().render.Provider, { value: state }, children);
|
|
57
|
-
}
|
|
58
|
-
const SERVER_HOOK = Symbol.for("islx.react.server");
|
|
59
|
-
/** @internal */
|
|
60
|
-
function getIslandServerHook() {
|
|
61
|
-
return globalThis[SERVER_HOOK];
|
|
62
|
-
}
|
|
63
|
-
/** @internal */
|
|
64
|
-
function setIslandServerHook(hook) {
|
|
65
|
-
globalThis[SERVER_HOOK] = hook;
|
|
66
|
-
}
|
|
67
|
-
/** Tagged objects the runtime made (slots), as opposed to app data with a "$" key. */
|
|
68
|
-
const runtimeTagged = /* @__PURE__ */ new WeakSet();
|
|
69
|
-
const CONTEXTS = Symbol.for("islx.react.contexts");
|
|
70
|
-
/** @internal */
|
|
71
|
-
function getContexts() {
|
|
72
|
-
const scope = globalThis;
|
|
73
|
-
const all = scope[CONTEXTS] ??= /* @__PURE__ */ new WeakMap();
|
|
74
|
-
let contexts = all.get(React.createElement);
|
|
75
|
-
if (!contexts) {
|
|
76
|
-
contexts = {
|
|
77
|
-
graph: React.createContext(false),
|
|
78
|
-
render: React.createContext(null)
|
|
79
|
-
};
|
|
80
|
-
all.set(React.createElement, contexts);
|
|
81
|
-
}
|
|
82
|
-
return contexts;
|
|
83
|
-
}
|
|
84
|
-
function isElement(value) {
|
|
85
|
-
const type = value?.$$typeof;
|
|
86
|
-
return type === Symbol.for("react.transitional.element") || type === Symbol.for("react.element");
|
|
87
|
-
}
|
|
88
|
-
const slotStyle = { display: "contents" };
|
|
89
|
-
/**
|
|
90
|
-
* Splits props into what renders on the server and what travels: server output passed to an
|
|
91
|
-
* island (`children`, or any element prop) renders once inside `<f-slot>`, and travels as a
|
|
92
|
-
* reference to it, so the browser reads its HTML from the page instead of receiving it twice.
|
|
93
|
-
*/
|
|
94
|
-
function splitSlots(value, prefix, counter) {
|
|
95
|
-
if (isElement(value)) {
|
|
96
|
-
const name = `${prefix}${counter.next++}`;
|
|
97
|
-
const reference = {
|
|
98
|
-
["$"]: "s",
|
|
99
|
-
["v"]: name
|
|
100
|
-
};
|
|
101
|
-
runtimeTagged.add(reference);
|
|
102
|
-
return {
|
|
103
|
-
render: React.createElement(SLOT_ELEMENT, {
|
|
104
|
-
key: value.key ?? name,
|
|
105
|
-
name,
|
|
106
|
-
style: slotStyle
|
|
107
|
-
}, value),
|
|
108
|
-
wire: reference
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
if (Array.isArray(value)) {
|
|
112
|
-
const parts = value.map((entry) => splitSlots(entry, prefix, counter));
|
|
113
|
-
return {
|
|
114
|
-
render: parts.map((part) => part.render),
|
|
115
|
-
wire: parts.map((part) => part.wire)
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
if (value && typeof value === "object") {
|
|
119
|
-
const prototype = Object.getPrototypeOf(value);
|
|
120
|
-
if (prototype !== Object.prototype && prototype !== null) return {
|
|
121
|
-
render: value,
|
|
122
|
-
wire: value
|
|
123
|
-
};
|
|
124
|
-
const render = {};
|
|
125
|
-
const wire = {};
|
|
126
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
127
|
-
const part = splitSlots(entry, prefix, counter);
|
|
128
|
-
render[key] = part.render;
|
|
129
|
-
wire[key] = part.wire;
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
render,
|
|
133
|
-
wire
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
render: value,
|
|
138
|
-
wire: value
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
const warned = /* @__PURE__ */ new Set();
|
|
142
|
-
const outcomes = /* @__PURE__ */ new WeakMap();
|
|
143
|
-
function settle(promise) {
|
|
144
|
-
let outcome = outcomes.get(promise);
|
|
145
|
-
if (!outcome) {
|
|
146
|
-
outcome = Promise.resolve(promise).then((value) => ({
|
|
147
|
-
ok: true,
|
|
148
|
-
value
|
|
149
|
-
}), (reason) => ({
|
|
150
|
-
ok: false,
|
|
151
|
-
reason
|
|
152
|
-
}));
|
|
153
|
-
outcomes.set(promise, outcome);
|
|
154
|
-
}
|
|
155
|
-
return outcome;
|
|
156
|
-
}
|
|
157
|
-
const REJECTED = "{\"e\":1}";
|
|
158
|
-
/** Each promise an island received, in a boundary of its own: its outcome once it settles. */
|
|
159
|
-
function promiseBoundaries(promises, label) {
|
|
160
|
-
return promises.map(({ name, promise }) => React.createElement(React.Suspense, {
|
|
161
|
-
key: name,
|
|
162
|
-
fallback: null
|
|
163
|
-
}, React.createElement(IslandPromise, {
|
|
164
|
-
name,
|
|
165
|
-
promise,
|
|
166
|
-
label
|
|
167
|
-
})));
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* A promise's outcome, written when it settles. Its values are sent whether or not the page wrote
|
|
171
|
-
* them already: this script may land before an earlier island's, wherever React puts it.
|
|
172
|
-
*/
|
|
173
|
-
function IslandPromise({ name, promise, label }) {
|
|
174
|
-
const outcome = React.use(settle(promise));
|
|
175
|
-
const state = React.useContext(getContexts().render) ?? resolveRenderState();
|
|
176
|
-
const hook = getIslandServerHook();
|
|
177
|
-
let encoded = {
|
|
178
|
-
json: REJECTED,
|
|
179
|
-
values: [],
|
|
180
|
-
promises: []
|
|
181
|
-
};
|
|
182
|
-
if (outcome.ok) try {
|
|
183
|
-
encoded = hook.encode({ v: outcome.value }, state?.held ?? null);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.warn(`[islx] A promise passed to ${label} resolved to a value that cannot be sent to the browser: it rejects there.`, error);
|
|
186
|
-
}
|
|
187
|
-
else console.error(`[islx] A promise passed to ${label} rejected.`, outcome.reason);
|
|
188
|
-
return React.createElement(React.Fragment, null, ...encoded.values.map((value) => React.createElement("script", {
|
|
189
|
-
key: value.key,
|
|
190
|
-
type: VALUE_SCRIPT_TYPE,
|
|
191
|
-
name: value.key,
|
|
192
|
-
dangerouslySetInnerHTML: { __html: hook.escape(value.json) }
|
|
193
|
-
})), React.createElement("script", {
|
|
194
|
-
key: "outcome",
|
|
195
|
-
type: PROMISE_SCRIPT_TYPE,
|
|
196
|
-
name,
|
|
197
|
-
dangerouslySetInnerHTML: { __html: hook.escape(encoded.json) }
|
|
198
|
-
}), ...promiseBoundaries(encoded.promises, label));
|
|
199
|
-
}
|
|
200
|
-
function isThenable(value) {
|
|
201
|
-
return !!value && typeof value.then === "function";
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Wraps a client component so the server renders it as an island. `src` is the module the browser
|
|
205
|
-
* imports, `exportName` the component's export there.
|
|
206
|
-
*/
|
|
207
|
-
function createIsland(Component, src, exportName, strategy = "load") {
|
|
208
|
-
function Island(props) {
|
|
209
|
-
const { graph, render } = getContexts();
|
|
210
|
-
const insideIsland = React.useContext(graph);
|
|
211
|
-
const state = React.useContext(render) ?? resolveRenderState();
|
|
212
|
-
const id = React.useId();
|
|
213
|
-
if (insideIsland) return React.createElement(Component, props);
|
|
214
|
-
const hook = getIslandServerHook();
|
|
215
|
-
if (!hook) throw new Error("islx: an island rendered on a server that did not import @farm.js/islx/react/server");
|
|
216
|
-
let encoded;
|
|
217
|
-
let renderProps;
|
|
218
|
-
try {
|
|
219
|
-
const { render: rendered, wire } = splitSlots(props, `${id}s`, { next: 0 });
|
|
220
|
-
renderProps = rendered;
|
|
221
|
-
const skip = state ? /* @__PURE__ */ new Set([...state.held ?? [], ...state.sent]) : null;
|
|
222
|
-
encoded = hook.encode(wire, skip);
|
|
223
|
-
for (const value of encoded.values) state?.sent.add(value.key);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
if (isThenable(error)) throw error;
|
|
226
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
227
|
-
const key = `${src}#${exportName}:${message}`;
|
|
228
|
-
if (!warned.has(key)) {
|
|
229
|
-
warned.add(key);
|
|
230
|
-
console.warn(`[islx] ${exportName} (${src}) was rendered without hydration: its props cannot be sent to the browser. ${message}`);
|
|
231
|
-
}
|
|
232
|
-
return React.createElement(Component, props);
|
|
233
|
-
}
|
|
234
|
-
if (strategy === "load") for (const href of getBuildDefaults().modules?.[src] ?? []) ReactDOM.preloadModule?.(href, { as: "script" });
|
|
235
|
-
return React.createElement(React.Fragment, null, ...encoded.values.map((value) => React.createElement("script", {
|
|
236
|
-
key: value.key,
|
|
237
|
-
type: VALUE_SCRIPT_TYPE,
|
|
238
|
-
name: value.key,
|
|
239
|
-
dangerouslySetInnerHTML: { __html: hook.escape(value.json) }
|
|
240
|
-
})), React.createElement(ISLAND_ELEMENT, {
|
|
241
|
-
key: "island",
|
|
242
|
-
src,
|
|
243
|
-
export: exportName,
|
|
244
|
-
props: id,
|
|
245
|
-
strategy,
|
|
246
|
-
style: slotStyle
|
|
247
|
-
}, React.createElement(graph.Provider, { value: true }, React.createElement(Component, renderProps))), React.createElement("script", {
|
|
248
|
-
key: "props",
|
|
249
|
-
type: PROPS_SCRIPT_TYPE,
|
|
250
|
-
htmlFor: id,
|
|
251
|
-
dangerouslySetInnerHTML: { __html: hook.escape(encoded.json) }
|
|
252
|
-
}), ...promiseBoundaries(encoded.promises, `${exportName} (${src})`));
|
|
253
|
-
}
|
|
254
|
-
Island.displayName = `Island(${Component.displayName || Component.name || exportName})`;
|
|
255
|
-
Object.defineProperty(Island, CLIENT_REFERENCE, { value: {
|
|
256
|
-
src,
|
|
257
|
-
exportName,
|
|
258
|
-
Component
|
|
259
|
-
} });
|
|
260
|
-
return Island;
|
|
261
|
-
}
|
|
262
|
-
export { getIslandServerHook as a, setIslandServerHook as c, getContexts as i, setRenderStateResolver as l, createIsland as n, isolateIslands as o, getClientReference as r, runtimeTagged as s, IslandsProvider as t, withinClientRoot as u };
|