@farm.js/islx 0.0.8 → 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.
@@ -1,8 +1,75 @@
1
- import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-C8to-RQW.mjs";
2
- import { M as VALUE_SCRIPT_TYPE, c as CHUNK_SCRIPT_TYPE, k as TREE_SCRIPT_TYPE, p as HTML_ELEMENT } from "./wire-QEQWBr5s.mjs";
3
- import { t as createServerReference, u as getValueStore } from "./actions-C-IwUCKU.mjs";
4
- import { a as getIslandServerHook, o as isolateIslands, r as getClientReference } from "./island-DXAGP4FN.mjs";
1
+ import { t as getBuildDefaults } from "./build-COIcs7wm.mjs";
2
+ import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-DDpUvgRo.mjs";
3
+ import { D as SLOT_TEMPLATE_ELEMENT, F as VALUE_SCRIPT_TYPE, M as TREE_SCRIPT_TYPE, S as PROPS_SCRIPT_TYPE, T as SLOT_ELEMENT, b as PROMISE_SCRIPT_TYPE, c as CHUNK_SCRIPT_TYPE, g as ISLAND_ELEMENT, p as HTML_ELEMENT } from "./wire-UUwZlUJZ.mjs";
4
+ import { t as createServerReference, u as getValueStore } from "./actions-DAzd4PpV.mjs";
5
5
  import * as React from "react";
6
+ import * as ReactDOM from "react-dom";
7
+ // src/react/ids.ts
8
+ /**
9
+ * `useId` in islands. React derives an id from the component's position in its root: every array
10
+ * of children on the way pushes a fork, `(slot << length) | bits`. The server renders a page as one
11
+ * root and the browser hydrates each island as a root of its own, so without help an island's ids
12
+ * start again in the browser, differ from its HTML, and repeat across islands.
13
+ *
14
+ * The server renders `[<IdProbe/>, <Component/>]` inside the island. The probe's id is the island's
15
+ * position with one more fork, slot 1 of 2: `(1 << length) | bits`, so its highest bit gives the
16
+ * position's length and the rest its bits. The browser rebuilds the same position with nested
17
+ * arrays (`null` in the slots before) and renders `[null, <Component/>]` there: React then computes
18
+ * the ids the server did, with the server's identifier prefix.
19
+ */
20
+ /** Renders nothing; records the id React gives its position. */
21
+ function IdProbe({ record }) {
22
+ record.id = React.useId();
23
+ return null;
24
+ }
25
+ const MAX_BITS = 30;
26
+ const MAX_FORK_WIDTH = 11;
27
+ /** The island's position, from the probe's id (`_<prefix>R_<tree id>_`), or null to skip. */
28
+ function parseIdPosition(id) {
29
+ const match = id ? /^_(.*)R_([0-9a-v]+)_$/.exec(id) : null;
30
+ if (!match) return null;
31
+ const probe = Number.parseInt(match[2], 32);
32
+ if (!Number.isSafeInteger(probe) || probe < 1) return null;
33
+ let length = 0;
34
+ while (2 ** (length + 1) <= probe) length++;
35
+ if (length + 2 > MAX_BITS) return null;
36
+ return {
37
+ prefix: match[1],
38
+ bits: probe - 2 ** length,
39
+ length
40
+ };
41
+ }
42
+ /**
43
+ * `content` at `position`, as the second of two children, where the server rendered the island's
44
+ * component. Null when the position cannot be rebuilt.
45
+ */
46
+ function atIdPosition(position, content) {
47
+ const { bits, length } = position;
48
+ const forks = [];
49
+ let start = 0;
50
+ for (let bit = 0; bit < length; bit++) if (Math.floor(bits / 2 ** bit) % 2 === 1) {
51
+ forks.push({
52
+ start,
53
+ width: bit - start + 1
54
+ });
55
+ start = bit + 1;
56
+ }
57
+ if (start < length) {
58
+ const last = forks.at(-1);
59
+ if (!last) return null;
60
+ last.width += length - start;
61
+ }
62
+ let node = React.createElement(React.Fragment, { key: "island" }, [null, React.createElement(React.Fragment, { key: "component" }, content)]);
63
+ for (let index = forks.length - 1; index >= 0; index--) {
64
+ const { start: from, width } = forks[index];
65
+ if (width > MAX_FORK_WIDTH) return null;
66
+ const slot = Math.floor(bits / 2 ** from) % 2 ** width;
67
+ const children = Array.from({ length: 2 ** (width - 1) }, () => null);
68
+ children[slot - 1] = node;
69
+ node = React.createElement(React.Fragment, { key: `fork${index}` }, children);
70
+ }
71
+ return node;
72
+ }
6
73
  // src/react/tree.ts
7
74
  /**
8
75
  * Server trees: a page as a serialized React tree, the role React Server Components' Flight
@@ -100,7 +167,7 @@ function callServerComponent(render, props, forwardRef) {
100
167
  slot?.set(previous);
101
168
  }
102
169
  }
103
- function isThenable(value) {
170
+ function isThenable$1(value) {
104
171
  return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
105
172
  }
106
173
  function serverHtmlNode(state, node, key, context) {
@@ -146,7 +213,7 @@ async function resolveValue(state, value, context, seen) {
146
213
  }
147
214
  if (typeof value !== "object") throw new UnsupportedNode(`passes a ${typeof value} value`);
148
215
  if (React.isValidElement(value)) return resolveElement(state, value, context);
149
- if (isThenable(value)) try {
216
+ if (isThenable$1(value)) try {
150
217
  return {
151
218
  $: "P",
152
219
  v: await resolveValue(state, await value, context, seen)
@@ -261,7 +328,7 @@ async function resolveElement(state, element, context) {
261
328
  if (typeof type !== "function") throw new UnsupportedNode("is not a function component");
262
329
  if (type.prototype?.isReactComponent) throw new UnsupportedNode("is a class component");
263
330
  let rendered = callServerComponent(type, props, forwardRef);
264
- if (isThenable(rendered)) rendered = await rendered;
331
+ if (isThenable$1(rendered)) rendered = await rendered;
265
332
  const resolved = await resolveValue(state, rendered, context, /* @__PURE__ */ new Set());
266
333
  return key === null ? resolved : {
267
334
  $: "f",
@@ -687,4 +754,430 @@ async function createServerTreeLayout(source, scope, options) {
687
754
  }
688
755
  };
689
756
  }
690
- export { renderServerTree as a, createServerTreeLayout as i, PAYLOAD_ONLY_ATTRIBUTE as n, updateServerTrees as o, createServerTreeElement as r, Outlet as t };
757
+ /** Whether a resolved tree needs the browser to rebuild it: it holds anything but plain HTML. */
758
+ function needsRebuild(value) {
759
+ if (value === null || typeof value !== "object") return false;
760
+ if (Array.isArray(value)) return value.some(needsRebuild);
761
+ if (value.$ === "c" || value.$ === "p" || value.$ === "x" || value.$ === "a") return true;
762
+ return Object.values(value).some(needsRebuild);
763
+ }
764
+ /**
765
+ * @internal Resolves server output passed to an island. Plain HTML renders as it is, and the
766
+ * browser keeps it. Output holding client components (or Suspense, actions, server HTML parts)
767
+ * renders with its payload, scoped to the slot's name, so the browser rebuilds it inside the
768
+ * island's root: its client components are then the island's descendants and read its context,
769
+ * as in React Server Components.
770
+ */
771
+ async function renderSlotTree(element, scope, held) {
772
+ const hook = getIslandServerHook();
773
+ if (!hook) throw new Error("islx: an island rendered without @farm.js/islx/react/server");
774
+ const state = {
775
+ nextId: 0,
776
+ components: /* @__PURE__ */ new Map(),
777
+ serverHtml: /* @__PURE__ */ new Map(),
778
+ chunks: /* @__PURE__ */ new Map(),
779
+ actions: /* @__PURE__ */ new Map()
780
+ };
781
+ const tree = await resolveValue(state, element, { parserSensitive: false }, /* @__PURE__ */ new Set());
782
+ const encode = (payload) => {
783
+ const addressed = hook.address(payload, held);
784
+ return {
785
+ json: hook.escape(addressed.json),
786
+ values: addressed.values.map((value) => [value.key, hook.escape(value.json)])
787
+ };
788
+ };
789
+ const resolver = {
790
+ component(reference, exportName) {
791
+ const Component = state.components.get(`${reference}#${exportName}`);
792
+ if (!Component) throw new Error(`client reference ${reference}#${exportName} was not resolved`);
793
+ return Component;
794
+ },
795
+ chunk(id) {
796
+ const entry = state.chunks.get(id);
797
+ if (entry.chunk) return entry.chunk;
798
+ const chunk = {
799
+ status: "pending",
800
+ promise: entry.encoded.then((encoded) => {
801
+ const payload = encode({ t: encoded });
802
+ chunk.value = React.createElement(React.Fragment, null, createTreeValue(encoded, resolver), createTreeScript(CHUNK_SCRIPT_TYPE, scopedName(scope, id), payload.json, payload.values));
803
+ chunk.status = "fulfilled";
804
+ return chunk.value;
805
+ }, (error) => {
806
+ chunk.status = "rejected";
807
+ chunk.reason = error;
808
+ throw error;
809
+ })
810
+ };
811
+ chunk.promise.catch(() => {});
812
+ entry.chunk = chunk;
813
+ return chunk;
814
+ },
815
+ serverHtml(id, key) {
816
+ return React.createElement(SERVER_HTML_TAG, {
817
+ ...key === null ? {} : { key },
818
+ [SERVER_HTML_ATTRIBUTE]: scopedName(scope, id),
819
+ style: { display: "contents" }
820
+ }, isolateIslands(state.serverHtml.get(id)));
821
+ },
822
+ outlet() {
823
+ return null;
824
+ },
825
+ action(id) {
826
+ return state.actions.get(id);
827
+ }
828
+ };
829
+ const rendered = createTreeValue(tree, resolver);
830
+ if (!needsRebuild(tree)) return {
831
+ element: React.createElement(React.Fragment, null, rendered),
832
+ rebuild: false
833
+ };
834
+ const payload = encode({
835
+ v: 1,
836
+ t: tree
837
+ });
838
+ return {
839
+ element: React.createElement(React.Fragment, null, rendered, createTreeScript(TREE_SCRIPT_TYPE, scope, payload.json, payload.values)),
840
+ rebuild: true
841
+ };
842
+ }
843
+ /**
844
+ * @internal Rebuilds a slot the server rendered with `renderSlotTree`, from `root` (the slot's
845
+ * element, or the template holding it) after loading its client modules. Null when the slot has no
846
+ * payload there.
847
+ */
848
+ async function createSlotTree(root, scope, options) {
849
+ const tree = await prepareTree(root, scope, options);
850
+ if (!tree) return null;
851
+ const resolver = tree.resolver();
852
+ const script = createTreeScript(TREE_SCRIPT_TYPE, scope, tree.json, tree.values);
853
+ const build = () => React.createElement(React.Fragment, null, createTreeValue(tree.payload, resolver), script);
854
+ build();
855
+ return build();
856
+ }
857
+ // src/react/island.ts
858
+ /**
859
+ * A client component, as the server renders it: its HTML inside `<f-island>`, and its props in
860
+ * a script after it. Shared by the server and the browser: inside a hydrating island, a nested
861
+ * client component is part of that island's React tree, not an island of its own.
862
+ *
863
+ * The compiler wraps every export of a `"use client"` module with `createIsland` in the server
864
+ * build. The browser build keeps the module as it is.
865
+ */
866
+ const CLIENT_REFERENCE = Symbol.for("islx.react.client-reference");
867
+ /** The module export an island component renders, or undefined for any other type. */
868
+ function getClientReference(type) {
869
+ if (!type || typeof type !== "function" && typeof type !== "object") return void 0;
870
+ return type[CLIENT_REFERENCE];
871
+ }
872
+ /**
873
+ * Makes `element`'s client components part of the React root around it (a layout that hydrates
874
+ * as one root), instead of islands of their own.
875
+ */
876
+ function withinClientRoot(element) {
877
+ return React.createElement(getContexts().graph.Provider, { value: true }, element);
878
+ }
879
+ /**
880
+ * Starts islands again below a `withinClientRoot`: the page a hydrating layout keeps as server
881
+ * HTML renders its client components as islands of their own.
882
+ */
883
+ function isolateIslands(element) {
884
+ return React.createElement(getContexts().graph.Provider, { value: false }, element);
885
+ }
886
+ const RENDER_STATE_RESOLVER = Symbol.for("islx.react.render-state");
887
+ /**
888
+ * For a framework that knows the request being rendered without React context (a request store):
889
+ * returns the render's settings when no `IslandsProvider` or `renderToStream` gave them. Keep the
890
+ * same `sent` set for the whole response.
891
+ */
892
+ function setRenderStateResolver(resolve) {
893
+ globalThis[RENDER_STATE_RESOLVER] = resolve;
894
+ }
895
+ function resolveRenderState() {
896
+ const resolve = globalThis[RENDER_STATE_RESOLVER];
897
+ return resolve ? resolve() : null;
898
+ }
899
+ /**
900
+ * Gives islands a render's settings when the framework renders the page itself (not through
901
+ * `renderToStream`): which values the browser holds, and which this render already wrote.
902
+ */
903
+ function IslandsProvider({ held = null, children }) {
904
+ const state = React.useMemo(() => ({
905
+ held: held && held.size > 0 ? held : null,
906
+ sent: /* @__PURE__ */ new Set()
907
+ }), [held]);
908
+ return React.createElement(getContexts().render.Provider, { value: state }, children);
909
+ }
910
+ const SERVER_HOOK = Symbol.for("islx.react.server");
911
+ /** @internal */
912
+ function getIslandServerHook() {
913
+ return globalThis[SERVER_HOOK];
914
+ }
915
+ /** @internal */
916
+ function setIslandServerHook(hook) {
917
+ globalThis[SERVER_HOOK] = hook;
918
+ }
919
+ /** Tagged objects the runtime made (slots), as opposed to app data with a "$" key. */
920
+ const runtimeTagged = /* @__PURE__ */ new WeakSet();
921
+ const CONTEXTS = Symbol.for("islx.react.contexts");
922
+ /** @internal */
923
+ function getContexts() {
924
+ const scope = globalThis;
925
+ const all = scope[CONTEXTS] ??= /* @__PURE__ */ new WeakMap();
926
+ let contexts = all.get(React.createElement);
927
+ if (!contexts) {
928
+ contexts = {
929
+ graph: React.createContext(false),
930
+ render: React.createContext(null),
931
+ container: React.createContext(null)
932
+ };
933
+ all.set(React.createElement, contexts);
934
+ }
935
+ return contexts;
936
+ }
937
+ function isElement(value) {
938
+ const type = value?.$$typeof;
939
+ return type === Symbol.for("react.transitional.element") || type === Symbol.for("react.element");
940
+ }
941
+ const slotStyle = { display: "contents" };
942
+ const resolvedSlots = /* @__PURE__ */ new WeakMap();
943
+ function resolveSlots(props, elements, held) {
944
+ let resolved = resolvedSlots.get(props);
945
+ if (!resolved) {
946
+ resolved = Promise.all(Array.from(elements, async ([name, element]) => [name, await renderSlotTree(element, name, held)])).then((entries) => new Map(entries));
947
+ resolvedSlots.set(props, resolved);
948
+ }
949
+ return resolved;
950
+ }
951
+ /** Server output in an island, where the island placed it. */
952
+ function Slot({ name, slots }) {
953
+ slots.placed.add(name);
954
+ return React.createElement(SLOT_ELEMENT, {
955
+ name,
956
+ style: slotStyle
957
+ }, slots.trees.get(name)?.element);
958
+ }
959
+ /**
960
+ * Splits props into what renders on the server and what travels: server output passed to an
961
+ * island (`children`, or any element prop) renders once inside `<f-slot>`, and travels as a
962
+ * reference to it, so the browser reads its HTML from the page instead of receiving it twice.
963
+ */
964
+ function splitSlots(value, prefix, slots) {
965
+ if (isElement(value)) {
966
+ const name = `${prefix}${slots.next++}`;
967
+ const reference = {
968
+ ["$"]: "s",
969
+ ["v"]: name
970
+ };
971
+ runtimeTagged.add(reference);
972
+ slots.elements.set(name, value);
973
+ slots.references.set(name, reference);
974
+ return {
975
+ render: React.createElement(Slot, {
976
+ key: value.key ?? name,
977
+ name,
978
+ slots
979
+ }),
980
+ wire: reference
981
+ };
982
+ }
983
+ if (Array.isArray(value)) {
984
+ const parts = value.map((entry) => splitSlots(entry, prefix, slots));
985
+ return {
986
+ render: parts.map((part) => part.render),
987
+ wire: parts.map((part) => part.wire)
988
+ };
989
+ }
990
+ if (value && typeof value === "object") {
991
+ const prototype = Object.getPrototypeOf(value);
992
+ if (prototype !== Object.prototype && prototype !== null) return {
993
+ render: value,
994
+ wire: value
995
+ };
996
+ const render = {};
997
+ const wire = {};
998
+ for (const [key, entry] of Object.entries(value)) {
999
+ const part = splitSlots(entry, prefix, slots);
1000
+ render[key] = part.render;
1001
+ wire[key] = part.wire;
1002
+ }
1003
+ return {
1004
+ render,
1005
+ wire
1006
+ };
1007
+ }
1008
+ return {
1009
+ render: value,
1010
+ wire: value
1011
+ };
1012
+ }
1013
+ const warned = /* @__PURE__ */ new Set();
1014
+ const outcomes = /* @__PURE__ */ new WeakMap();
1015
+ function settle(promise) {
1016
+ let outcome = outcomes.get(promise);
1017
+ if (!outcome) {
1018
+ outcome = Promise.resolve(promise).then((value) => ({
1019
+ ok: true,
1020
+ value
1021
+ }), (reason) => ({
1022
+ ok: false,
1023
+ reason
1024
+ }));
1025
+ outcomes.set(promise, outcome);
1026
+ }
1027
+ return outcome;
1028
+ }
1029
+ const REJECTED = "{\"e\":1}";
1030
+ /** Each promise an island received, in a boundary of its own: its outcome once it settles. */
1031
+ function promiseBoundaries(promises, label) {
1032
+ return promises.map(({ name, promise }) => React.createElement(React.Suspense, {
1033
+ key: name,
1034
+ fallback: null
1035
+ }, React.createElement(IslandPromise, {
1036
+ name,
1037
+ promise,
1038
+ label
1039
+ })));
1040
+ }
1041
+ /**
1042
+ * A promise's outcome, written when it settles. Its values are sent whether or not the page wrote
1043
+ * them already: this script may land before an earlier island's, wherever React puts it.
1044
+ */
1045
+ function IslandPromise({ name, promise, label }) {
1046
+ const outcome = React.use(settle(promise));
1047
+ const state = React.useContext(getContexts().render) ?? resolveRenderState();
1048
+ const hook = getIslandServerHook();
1049
+ let encoded = {
1050
+ json: REJECTED,
1051
+ values: [],
1052
+ promises: []
1053
+ };
1054
+ if (outcome.ok) try {
1055
+ encoded = hook.encode({ v: outcome.value }, state?.held ?? null);
1056
+ } catch (error) {
1057
+ console.warn(`[islx] A promise passed to ${label} resolved to a value that cannot be sent to the browser: it rejects there.`, error);
1058
+ }
1059
+ else console.error(`[islx] A promise passed to ${label} rejected.`, outcome.reason);
1060
+ return React.createElement(React.Fragment, null, ...encoded.values.map((value) => React.createElement("script", {
1061
+ key: value.key,
1062
+ type: VALUE_SCRIPT_TYPE,
1063
+ name: value.key,
1064
+ dangerouslySetInnerHTML: { __html: hook.escape(value.json) }
1065
+ })), React.createElement("script", {
1066
+ key: "outcome",
1067
+ type: PROMISE_SCRIPT_TYPE,
1068
+ name,
1069
+ dangerouslySetInnerHTML: { __html: hook.escape(encoded.json) }
1070
+ }), ...promiseBoundaries(encoded.promises, label));
1071
+ }
1072
+ /**
1073
+ * The island's props. It renders after the island, so by then the island's id position (`ids`) is
1074
+ * known, and which of its element props it placed. The others (a closed panel, a tab not shown
1075
+ * yet) render once in a `<template>` after the props, which the browser reads them from when the
1076
+ * island shows them; a slot placed later, by a part of the island that suspended first, renders in
1077
+ * both places.
1078
+ */
1079
+ function PropsScript({ id, ids, slots, json }) {
1080
+ const script = React.createElement("script", {
1081
+ key: "props",
1082
+ type: PROPS_SCRIPT_TYPE,
1083
+ htmlFor: id,
1084
+ ["ids"]: ids.id,
1085
+ dangerouslySetInnerHTML: { __html: json }
1086
+ });
1087
+ const unplaced = [...slots.elements].filter(([name]) => !slots.placed.has(name));
1088
+ if (unplaced.length === 0) return script;
1089
+ return React.createElement(React.Fragment, null, script, React.createElement(SLOT_TEMPLATE_ELEMENT, {
1090
+ key: "slots",
1091
+ htmlFor: id
1092
+ }, unplaced.map(([name]) => React.createElement(SLOT_ELEMENT, {
1093
+ key: name,
1094
+ name,
1095
+ style: slotStyle
1096
+ }, slots.trees.get(name)?.element))));
1097
+ }
1098
+ function isThenable(value) {
1099
+ return !!value && typeof value.then === "function";
1100
+ }
1101
+ /**
1102
+ * Wraps a client component so the server renders it as an island. `src` is the module the browser
1103
+ * imports, `exportName` the component's export there.
1104
+ */
1105
+ function createIsland(Component, src, exportName, declared = "load", options = {}) {
1106
+ function Island(props) {
1107
+ const { graph, render } = getContexts();
1108
+ const insideIsland = React.useContext(graph);
1109
+ const state = React.useContext(render) ?? resolveRenderState();
1110
+ if (insideIsland) return React.createElement(Component, props);
1111
+ const id = React.useId();
1112
+ const hook = getIslandServerHook();
1113
+ if (!hook) throw new Error("islx: an island rendered on a server that did not import @farm.js/islx/react/server");
1114
+ let encoded;
1115
+ let strategy = declared;
1116
+ const slots = {
1117
+ next: 0,
1118
+ elements: /* @__PURE__ */ new Map(),
1119
+ references: /* @__PURE__ */ new Map(),
1120
+ placed: /* @__PURE__ */ new Set(),
1121
+ trees: /* @__PURE__ */ new Map()
1122
+ };
1123
+ const { render: renderProps, wire } = splitSlots(props, `${id}s`, slots);
1124
+ if (slots.elements.size > 0) {
1125
+ slots.trees = React.use(resolveSlots(props, slots.elements, state?.held ?? null));
1126
+ for (const [name, reference] of slots.references) if (slots.trees.get(name)?.rebuild) reference["t"] = 1;
1127
+ }
1128
+ try {
1129
+ if (options.inferred && slots.next > 0) strategy = "load";
1130
+ const skip = state ? /* @__PURE__ */ new Set([...state.held ?? [], ...state.sent]) : null;
1131
+ encoded = hook.encode(wire, skip);
1132
+ for (const value of encoded.values) state?.sent.add(value.key);
1133
+ } catch (error) {
1134
+ if (isThenable(error)) throw error;
1135
+ const message = error instanceof Error ? error.message : String(error);
1136
+ const key = `${src}#${exportName}:${message}`;
1137
+ if (!warned.has(key)) {
1138
+ warned.add(key);
1139
+ console.warn(`[islx] ${exportName} (${src}) was rendered without hydration: its props cannot be sent to the browser. ${message}`);
1140
+ }
1141
+ return React.createElement(Component, props);
1142
+ }
1143
+ const ids = {};
1144
+ if (strategy === "load") {
1145
+ const defaults = getBuildDefaults();
1146
+ if (defaults.boot && defaults.entry) ReactDOM.preloadModule?.(defaults.entry, { as: "script" });
1147
+ for (const href of defaults.modules?.[src] ?? []) ReactDOM.preloadModule?.(href, { as: "script" });
1148
+ }
1149
+ return React.createElement(React.Fragment, null, ...encoded.values.map((value) => React.createElement("script", {
1150
+ key: value.key,
1151
+ type: VALUE_SCRIPT_TYPE,
1152
+ name: value.key,
1153
+ dangerouslySetInnerHTML: { __html: hook.escape(value.json) }
1154
+ })), React.createElement(ISLAND_ELEMENT, {
1155
+ key: "island",
1156
+ src,
1157
+ export: exportName,
1158
+ props: id,
1159
+ strategy,
1160
+ style: slotStyle
1161
+ }, React.createElement(graph.Provider, { value: true }, [React.createElement(IdProbe, {
1162
+ key: "ids",
1163
+ record: ids
1164
+ }), React.createElement(Component, {
1165
+ ...renderProps,
1166
+ key: "component"
1167
+ })])), React.createElement(PropsScript, {
1168
+ key: "props",
1169
+ id,
1170
+ ids,
1171
+ slots,
1172
+ json: hook.escape(encoded.json)
1173
+ }), ...promiseBoundaries(encoded.promises, `${exportName} (${src})`));
1174
+ }
1175
+ Island.displayName = `Island(${Component.displayName || Component.name || exportName})`;
1176
+ Object.defineProperty(Island, CLIENT_REFERENCE, { value: {
1177
+ src,
1178
+ exportName,
1179
+ Component
1180
+ } });
1181
+ return Island;
1182
+ }
1183
+ export { atIdPosition as _, isolateIslands as a, setRenderStateResolver as c, PAYLOAD_ONLY_ATTRIBUTE as d, createServerTreeElement as f, updateServerTrees as g, renderServerTree as h, getContexts as i, withinClientRoot as l, createSlotTree as m, createIsland as n, runtimeTagged as o, createServerTreeLayout as p, getClientReference as r, setIslandServerHook as s, IslandsProvider as t, Outlet as u, parseIdPosition as v };
@@ -1,5 +1,5 @@
1
1
  import { a as hasPendingInteraction, o as takePendingInteractions, t as INTERACTION_EVENT } from "./prehydration-zEYsfFxW.mjs";
2
- import { m as HYDRATED_ATTRIBUTE } from "./wire-QEQWBr5s.mjs";
2
+ import { m as HYDRATED_ATTRIBUTE } from "./wire-UUwZlUJZ.mjs";
3
3
  // src/client/schedule.ts
4
4
  /**
5
5
  * When an island hydrates. Renderer-neutral: every adapter schedules through this.
@@ -1,5 +1,5 @@
1
- import { c as needsObjectTag, o as encodeLeaf } from "./codec-C8to-RQW.mjs";
2
- import { A as VALUES_HEADER, j as VALUE_KEY_PATTERN } from "./wire-QEQWBr5s.mjs";
1
+ import { c as needsObjectTag, o as encodeLeaf } from "./codec-DDpUvgRo.mjs";
2
+ import { N as VALUES_HEADER, P as VALUE_KEY_PATTERN } from "./wire-UUwZlUJZ.mjs";
3
3
  // src/server/values.ts
4
4
  /**
5
5
  * Values sent once: which parts of an island's props travel as values of their own, keyed by a
@@ -1,5 +1,5 @@
1
- import { a as transformActions, i as hasModuleDirective, n as ActionCompileError, t as inferStrategy } from "./infer-BIVT5aDz.mjs";
2
- import { t as ISLAND_STRATEGIES } from "./schedule-Cwn9vQL0.mjs";
1
+ import { i as ActionCompileError, n as rewriteFormStatusImports, o as hasModuleDirective, r as inferStrategy, s as transformActions, t as rewriteFormStatusImport } from "./form-status-CRfnB7WT.mjs";
2
+ import { t as ISLAND_STRATEGIES } from "./schedule-IK9_RwZC.mjs";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import path from "node:path";
5
5
  import { walk } from "estree-walker";
@@ -86,6 +86,7 @@ function transformClientModule(options) {
86
86
  return configured ?? strategies.byName.get(name) ?? strategies.all ?? (inference?.strategy === "interaction" ? "interaction" : "load");
87
87
  };
88
88
  const output = new MagicString(code);
89
+ rewriteFormStatusImport(program, code, output, options.runtime ?? "@farm.js/islx/react");
89
90
  const wrapped = [];
90
91
  for (const node of program.body ?? []) if (node.type === "ExportNamedDeclaration" && !node.source && node.exportKind !== "type") {
91
92
  const declaration = node.declaration;
@@ -460,6 +461,14 @@ function islx(options = {}, host = {}) {
460
461
  }
461
462
  }
462
463
  if (library) return null;
464
+ if (!server && !code.includes(directives.server) && hasModuleDirective(code, clientDirective) && !excluded(relative)) {
465
+ const rewritten = rewriteFormStatusImports({
466
+ code,
467
+ id: file,
468
+ parse: parseFile
469
+ });
470
+ if (rewritten) return rewritten;
471
+ }
463
472
  const actions = transformActions({
464
473
  code,
465
474
  id: file,
@@ -12,12 +12,22 @@
12
12
  declare const ISLAND_ELEMENT = "f-island";
13
13
  /** The script type of an island's props; its `for` attribute names the island. */
14
14
  declare const PROPS_SCRIPT_TYPE = "f/props";
15
+ /**
16
+ * On a props script: the id React gave the island's position on the server, so the browser
17
+ * hydrates the island where the server rendered it and `useId` matches.
18
+ */
19
+ declare const ID_POSITION_ATTRIBUTE = "ids";
15
20
  /** The script type of a value sent once; its `name` attribute is the value's key. */
16
21
  declare const VALUE_SCRIPT_TYPE = "f/value";
17
22
  /** Set on an island once it hydrated (and its held interaction was replayed). */
18
23
  declare const HYDRATED_ATTRIBUTE = "hydrated";
19
24
  /** The element that keeps server output passed to an island as children. */
20
25
  declare const SLOT_ELEMENT = "f-slot";
26
+ /**
27
+ * After an island's props: `<template for>` holding the `<f-slot>`s the island did not place when
28
+ * the server rendered it, which the browser shows when the island places them.
29
+ */
30
+ declare const SLOT_TEMPLATE_ELEMENT = "template";
21
31
  /** The script type of a page's server tree, rebuilt in the browser without the page's HTML. */
22
32
  declare const TREE_SCRIPT_TYPE = "f/tree";
23
33
  /** The script type of a server tree chunk that streamed in after the tree. */
@@ -41,6 +51,11 @@ declare const TAG_BOUND = "b";
41
51
  declare const REFERENCE_TAG = "r";
42
52
  /** `{"$":"s","v":name}`: a slot. */
43
53
  declare const SLOT_TAG = "s";
54
+ /**
55
+ * On a slot reference: `1` when the slot carries a server tree (`<script type="f/tree">` named
56
+ * after the slot) that the browser rebuilds inside the island, instead of keeping its HTML.
57
+ */
58
+ declare const SLOT_TREE_FIELD = "t";
44
59
  /** `{"$":"a","v":id}` or `{"$":"a","v":id,"b":sealed}`: a server action. */
45
60
  declare const ACTION_TAG = "a";
46
61
  /**
@@ -105,4 +120,4 @@ declare const MAX_HELD_VALUES = 128;
105
120
  * `{"$":"a","v":id,"b":sealed}`), or undefined for anything else.
106
121
  */
107
122
  declare function getTag(value: unknown): string | undefined;
108
- export { VALUES_HEADER as A, RENDER_HEADER as C, TAG_BOUND as D, TAG as E, getTag as F, readPostedAction as I, stripRuntimeFields as L, VALUE_SCRIPT_TYPE as M, attachmentField as N, TAG_VALUE as O, formFieldName as P, REFERENCE_TAG as S, SLOT_TAG as T, MAX_HELD_VALUES as _, BLOB_TAG as a, PROMISE_TAG as b, CHUNK_SCRIPT_TYPE as c, FORM_REF_PREFIX as d, FORM_STATE_SCRIPT_TYPE as f, LAYOUTS_HEADER as g, ISLAND_ELEMENT as h, ACTION_TAG as i, VALUE_KEY_PATTERN as j, TREE_SCRIPT_TYPE as k, FORM_DATA_TAG as l, HYDRATED_ATTRIBUTE as m, ACTION_KEY_FIELD as n, BOUND_FIELD as o, HTML_ELEMENT as p, ACTION_STALE_HEADER as r, CALL_FIELD as s, ACTION_HEADER as t, FORM_FIELD_PREFIX as u, OBJECT_TAG as v, SLOT_ELEMENT as w, PROPS_SCRIPT_TYPE as x, PROMISE_SCRIPT_TYPE as y };
123
+ export { TAG_BOUND as A, stripRuntimeFields as B, REFERENCE_TAG as C, SLOT_TEMPLATE_ELEMENT as D, SLOT_TAG as E, VALUE_SCRIPT_TYPE as F, attachmentField as I, formFieldName as L, TREE_SCRIPT_TYPE as M, VALUES_HEADER as N, SLOT_TREE_FIELD as O, VALUE_KEY_PATTERN as P, getTag as R, PROPS_SCRIPT_TYPE as S, SLOT_ELEMENT as T, LAYOUTS_HEADER as _, BLOB_TAG as a, PROMISE_SCRIPT_TYPE as b, CHUNK_SCRIPT_TYPE as c, FORM_REF_PREFIX as d, FORM_STATE_SCRIPT_TYPE as f, ISLAND_ELEMENT as g, ID_POSITION_ATTRIBUTE as h, ACTION_TAG as i, TAG_VALUE as j, TAG as k, FORM_DATA_TAG as l, HYDRATED_ATTRIBUTE as m, ACTION_KEY_FIELD as n, BOUND_FIELD as o, HTML_ELEMENT as p, ACTION_STALE_HEADER as r, CALL_FIELD as s, ACTION_HEADER as t, FORM_FIELD_PREFIX as u, MAX_HELD_VALUES as v, RENDER_HEADER as w, PROMISE_TAG as x, OBJECT_TAG as y, readPostedAction as z };
@@ -13,12 +13,22 @@
13
13
  const ISLAND_ELEMENT = "f-island";
14
14
  /** The script type of an island's props; its `for` attribute names the island. */
15
15
  const PROPS_SCRIPT_TYPE = "f/props";
16
+ /**
17
+ * On a props script: the id React gave the island's position on the server, so the browser
18
+ * hydrates the island where the server rendered it and `useId` matches.
19
+ */
20
+ const ID_POSITION_ATTRIBUTE = "ids";
16
21
  /** The script type of a value sent once; its `name` attribute is the value's key. */
17
22
  const VALUE_SCRIPT_TYPE = "f/value";
18
23
  /** Set on an island once it hydrated (and its held interaction was replayed). */
19
24
  const HYDRATED_ATTRIBUTE = "hydrated";
20
25
  /** The element that keeps server output passed to an island as children. */
21
26
  const SLOT_ELEMENT = "f-slot";
27
+ /**
28
+ * After an island's props: `<template for>` holding the `<f-slot>`s the island did not place when
29
+ * the server rendered it, which the browser shows when the island places them.
30
+ */
31
+ const SLOT_TEMPLATE_ELEMENT = "template";
22
32
  /** The script type of a page's server tree, rebuilt in the browser without the page's HTML. */
23
33
  const TREE_SCRIPT_TYPE = "f/tree";
24
34
  /** The script type of a server tree chunk that streamed in after the tree. */
@@ -42,6 +52,11 @@ const TAG_BOUND = "b";
42
52
  const REFERENCE_TAG = "r";
43
53
  /** `{"$":"s","v":name}`: a slot. */
44
54
  const SLOT_TAG = "s";
55
+ /**
56
+ * On a slot reference: `1` when the slot carries a server tree (`<script type="f/tree">` named
57
+ * after the slot) that the browser rebuilds inside the island, instead of keeping its HTML.
58
+ */
59
+ const SLOT_TREE_FIELD = "t";
45
60
  /** `{"$":"a","v":id}` or `{"$":"a","v":id,"b":sealed}`: a server action. */
46
61
  const ACTION_TAG = "a";
47
62
  /**
@@ -127,5 +142,6 @@ function getTag(value) {
127
142
  if (keys.length === 1) return tag;
128
143
  if (keys.length === 2 && "v" in value) return tag;
129
144
  if (keys.length === 3 && tag === "a" && "v" in value && "b" in value) return tag;
145
+ if (keys.length === 3 && tag === "s" && "v" in value && "t" in value) return tag;
130
146
  }
131
- export { VALUES_HEADER as A, RENDER_HEADER as C, TAG_BOUND as D, TAG as E, getTag as F, readPostedAction as I, stripRuntimeFields as L, VALUE_SCRIPT_TYPE as M, attachmentField as N, TAG_VALUE as O, formFieldName as P, REFERENCE_TAG as S, SLOT_TAG as T, MAX_HELD_VALUES as _, BLOB_TAG as a, PROMISE_TAG as b, CHUNK_SCRIPT_TYPE as c, FORM_REF_PREFIX as d, FORM_STATE_SCRIPT_TYPE as f, LAYOUTS_HEADER as g, ISLAND_ELEMENT as h, ACTION_TAG as i, VALUE_KEY_PATTERN as j, TREE_SCRIPT_TYPE as k, FORM_DATA_TAG as l, HYDRATED_ATTRIBUTE as m, ACTION_KEY_FIELD as n, BOUND_FIELD as o, HTML_ELEMENT as p, ACTION_STALE_HEADER as r, CALL_FIELD as s, ACTION_HEADER as t, FORM_FIELD_PREFIX as u, OBJECT_TAG as v, SLOT_ELEMENT as w, PROPS_SCRIPT_TYPE as x, PROMISE_SCRIPT_TYPE as y };
147
+ export { TAG_BOUND as A, stripRuntimeFields as B, REFERENCE_TAG as C, SLOT_TEMPLATE_ELEMENT as D, SLOT_TAG as E, VALUE_SCRIPT_TYPE as F, attachmentField as I, formFieldName as L, TREE_SCRIPT_TYPE as M, VALUES_HEADER as N, SLOT_TREE_FIELD as O, VALUE_KEY_PATTERN as P, getTag as R, PROPS_SCRIPT_TYPE as S, SLOT_ELEMENT as T, LAYOUTS_HEADER as _, BLOB_TAG as a, PROMISE_SCRIPT_TYPE as b, CHUNK_SCRIPT_TYPE as c, FORM_REF_PREFIX as d, FORM_STATE_SCRIPT_TYPE as f, ISLAND_ELEMENT as g, ID_POSITION_ATTRIBUTE as h, ACTION_TAG as i, TAG_VALUE as j, TAG as k, FORM_DATA_TAG as l, HYDRATED_ATTRIBUTE as m, ACTION_KEY_FIELD as n, BOUND_FIELD as o, HTML_ELEMENT as p, ACTION_STALE_HEADER as r, CALL_FIELD as s, ACTION_HEADER as t, FORM_FIELD_PREFIX as u, MAX_HELD_VALUES as v, RENDER_HEADER as w, PROMISE_TAG as x, OBJECT_TAG as y, readPostedAction as z };