@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +56 -26
  3. package/SPEC.md +5 -4
  4. package/dist/_chunks/{actions-C-IwUCKU.mjs → actions-DAzd4PpV.mjs} +2 -2
  5. package/dist/_chunks/{actions-CvQ0-zjg.mjs → actions-DzIPDecd.mjs} +2 -2
  6. package/dist/_chunks/{codec-C8to-RQW.mjs → codec-DDpUvgRo.mjs} +1 -1
  7. package/dist/_chunks/{actions-Eutk6UzT.mjs → form-status-CRfnB7WT.mjs} +314 -1
  8. package/dist/_chunks/forms-CCDY9QHC.mjs +63 -0
  9. package/dist/_chunks/{tree-C47p6khN.mjs → island-viWIOM1k.mjs} +501 -8
  10. package/dist/_chunks/{prehydration-B1GLTLdk.mjs → prehydration-zEYsfFxW.mjs} +3 -2
  11. package/dist/_chunks/{schedule-CjoybS3b.mjs → schedule-IK9_RwZC.mjs} +13 -4
  12. package/dist/_chunks/{tree-B4RMU2UX.d.mts → tree-CV13RsJv.d.mts} +8 -1
  13. package/dist/_chunks/{values-37UIzxM0.mjs → values-gxhIei_m.mjs} +2 -2
  14. package/dist/_chunks/{vite-BTzYlEiY.mjs → vite-CHB6B8tP.mjs} +36 -12
  15. package/dist/_chunks/{wire-Dipqh-EB.d.mts → wire-1d6wqOia.d.mts} +16 -1
  16. package/dist/_chunks/{wire-QEQWBr5s.mjs → wire-UUwZlUJZ.mjs} +17 -1
  17. package/dist/boot.d.mts +31 -0
  18. package/dist/boot.mjs +177 -0
  19. package/dist/client.d.mts +2 -1
  20. package/dist/client.mjs +4 -28
  21. package/dist/compiler.d.mts +35 -1
  22. package/dist/compiler.mjs +1 -1
  23. package/dist/index.d.mts +2 -2
  24. package/dist/index.mjs +3 -3
  25. package/dist/react/client.d.mts +2 -2
  26. package/dist/react/client.mjs +88 -19
  27. package/dist/react/server.d.mts +1 -1
  28. package/dist/react/server.mjs +13 -10
  29. package/dist/react.d.mts +6 -2
  30. package/dist/react.mjs +22 -3
  31. package/dist/server.d.mts +5 -0
  32. package/dist/server.mjs +4 -4
  33. package/dist/tanstack-start/server.mjs +3 -3
  34. package/dist/tanstack-start.mjs +1 -1
  35. package/dist/vite.d.mts +16 -1
  36. package/dist/vite.mjs +1 -1
  37. package/package.json +5 -1
  38. package/dist/_chunks/island-DVOr-OUw.mjs +0 -262
@@ -1,9 +1,8 @@
1
- import { d as valueHeaders, o as emit, t as createServerReference, u as getValueStore } from "../_chunks/actions-C-IwUCKU.mjs";
2
- import { i as decodeTagged, n as NOT_A_VALUE, r as decode, u as setOwn } from "../_chunks/codec-C8to-RQW.mjs";
3
- import { F as getTag, f as FORM_STATE_SCRIPT_TYPE, h as ISLAND_ELEMENT, m as HYDRATED_ATTRIBUTE, w as SLOT_ELEMENT, x as PROPS_SCRIPT_TYPE, y as PROMISE_SCRIPT_TYPE } from "../_chunks/wire-QEQWBr5s.mjs";
4
- import { i as getContexts, o as isolateIslands, u as withinClientRoot } from "../_chunks/island-DVOr-OUw.mjs";
5
- import { i as createServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, o as updateServerTrees, r as createServerTreeElement } from "../_chunks/tree-C47p6khN.mjs";
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
- let html = "";
74
- for (const slot of container.querySelectorAll(SLOT_ELEMENT)) if (slot.getAttribute("name") === name) {
75
- html = slot.innerHTML;
76
- break;
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 inner = React.createElement(Component, props);
283
- return React.createElement(graph.Provider, { value: true }, React.createElement(Boundary, { onError: restore }, React.createElement(Committed, {
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.parentElement?.closest(ISLAND_SELECTOR) && !container.closest(STREAM_STAGING) && !awaitsStreamedContent(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;
@@ -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-B4RMU2UX.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-CV13RsJv.mjs";
3
3
  import * as React from "react";
4
4
  import { RenderToReadableStreamOptions } from "react-dom/server";
5
5
 
@@ -1,11 +1,11 @@
1
- import { a as encode } from "../_chunks/codec-C8to-RQW.mjs";
2
- import { f as FORM_STATE_SCRIPT_TYPE } from "../_chunks/wire-QEQWBr5s.mjs";
3
- import { r as PREHYDRATION_SCRIPT } from "../_chunks/prehydration-B1GLTLdk.mjs";
4
- import { i as encodeActionReference } from "../_chunks/actions-CvQ0-zjg.mjs";
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-37UIzxM0.mjs";
7
- import { c as setIslandServerHook, i as getContexts, l as setRenderStateResolver, n as createIsland, o as isolateIslands, r as getClientReference, s as runtimeTagged, t as IslandsProvider, u as withinClientRoot } from "../_chunks/island-DVOr-OUw.mjs";
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 entry = bootstrap ? getBuildDefaults().entry : void 0;
92
- const bootstrapModules = entry ? [...options.bootstrapModules ?? [], entry] : options.bootstrapModules;
93
- const bootstrapScriptContent = prehydration ?? entry !== void 0 ? `${PREHYDRATION_SCRIPT}${options.bootstrapScriptContent ?? ""}` : options.bootstrapScriptContent;
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-B4RMU2UX.mjs";
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
- export { type IslandRenderState, type IslandStrategy, IslandsProvider, Outlet, type ServerTreeRenderOptions, createIsland, isolateIslands, renderServerTree, withinClientRoot };
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 { n as createIsland, o as isolateIslands, t as IslandsProvider, u as withinClientRoot } from "./_chunks/island-DVOr-OUw.mjs";
2
- import { a as renderServerTree, t as Outlet } from "./_chunks/tree-C47p6khN.mjs";
3
- export { IslandsProvider, Outlet, createIsland, isolateIslands, renderServerTree, withinClientRoot };
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-37UIzxM0.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-CvQ0-zjg.mjs";
3
- import { a as encode, r as decode } from "./_chunks/codec-C8to-RQW.mjs";
4
- import { I as readPostedAction, L as stripRuntimeFields, N 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-QEQWBr5s.mjs";
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-DVOr-OUw.mjs";
2
- import { A as VALUES_HEADER, C as RENDER_HEADER } from "../_chunks/wire-QEQWBr5s.mjs";
3
- import { a as parseHeldValues } from "../_chunks/values-37UIzxM0.mjs";
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";
@@ -1,4 +1,4 @@
1
- import { n as islx, t as ENTRY_ID } from "./_chunks/vite-BTzYlEiY.mjs";
1
+ import { n as ENTRY_ID, r as islx } from "./_chunks/vite-CHB6B8tP.mjs";
2
2
  import { normalizePath } from "vite";
3
3
  // src/tanstack-start/index.ts
4
4
  /**
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 ENTRY_ID, n as default, n as islx, r as resolveDirectives, i as serializeHooks } from "./_chunks/vite-BTzYlEiY.mjs";
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.7",
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 };