@farm.js/islx 0.0.4 → 0.0.6

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 CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.6
4
+
5
+ Server action interceptor, early clicks on TanStack Start, library islands under pnpm.
6
+
7
+ - `setActionInterceptor((call, next) => …)` from `@farm.js/islx/client` wraps every server
8
+ action call, references decoded from props included. It sees the call when it is made
9
+ (`{ id, name, args }`) and `next` sends it in queue order, so a framework can unwrap what its
10
+ server side adds to a result (cache invalidations, a query's cache entry, a declared failure).
11
+ - TanStack Start: a click on a button, or a form submit, in an island before it hydrates is held
12
+ and replayed, as in a plain islx app (the pre-hydration script goes in the root route's head).
13
+ - A library's `"use client"` components (islands the Vite plugin makes in a bundled server build)
14
+ import islx's island runtime from the app instead of from the library's folder. Under pnpm the
15
+ library's folder in the store cannot see `@farm.js/islx`, so the server build failed to resolve
16
+ `@farm.js/islx/react`; now it gets the same copy the app's pages use.
17
+
18
+ ## 0.0.5
19
+
20
+ TanStack Start.
21
+
22
+ - `@farm.js/islx/tanstack-start`: `islxTanstackStart()` next to `tanstackStart()` runs a Start app
23
+ as islands, with configuration only. Routes, loaders and layouts render on the server; the
24
+ browser loads islx and the `"use client"` components a page uses (the modules under `src/`), not
25
+ the router, the route tree or loader data. Start's client entry becomes islx's and the router is
26
+ not dehydrated. Links move between routes without loading a document; a loader's promise passed
27
+ unawaited to a client component streams in; islands call Start server functions and islx server
28
+ actions. An app with its own `src/server.ts` uses `islandsHandler` from
29
+ `@farm.js/islx/tanstack-start/server`. Needs Node 22.12 or later (Start's own requirement).
30
+
3
31
  ## 0.0.4
4
32
 
5
33
  Promise props, and islands in a page that streams after it loaded.
package/README.md CHANGED
@@ -13,7 +13,7 @@ islx started as [Farm.js](https://github.com/farming-labs/farm.js)'s islands mod
13
13
  replaces React Server Components' Flight payload. On a 300-page docs app it ships 26% less
14
14
  JavaScript, and a client navigation sends 2.5 KB instead of 12 KB.
15
15
 
16
- > Status: 0.0.4. React and Vite are supported; other renderers and framework
16
+ > Status: 0.0.6. React, Vite and TanStack Start are supported; other renderers and framework
17
17
  > integrations follow. The wire format is in [SPEC.md](./SPEC.md), changes in
18
18
  > [CHANGELOG.md](./CHANGELOG.md).
19
19
 
@@ -169,6 +169,41 @@ export function Chart(props: { points: Map<string, number> }) { … }
169
169
  `examples/react` is a runnable app; `pnpm test:e2e` runs it in Chrome, in development and from
170
170
  its production build.
171
171
 
172
+ ## TanStack Start
173
+
174
+ A Start app as islands, with configuration only:
175
+
176
+ ```ts
177
+ // vite.config.ts
178
+ import { defineConfig } from "vite";
179
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
180
+ import react from "@vitejs/plugin-react";
181
+ import { islxTanstackStart } from "@farm.js/islx/tanstack-start";
182
+
183
+ export default defineConfig({ plugins: [tanstackStart(), react(), islxTanstackStart()] });
184
+ ```
185
+
186
+ Routes, loaders and layouts render on the server. The browser loads islx and the `"use client"`
187
+ components a page uses (the modules under `src/`), not the router, the route tree or loader
188
+ data: Start's client entry becomes islx's, `<Scripts />` still loads it, and the router is not
189
+ dehydrated. A click on a button (or a form submit) in an island before it hydrates is held and
190
+ replayed. Links move between
191
+ routes without loading a document. A loader's promise passed
192
+ unawaited to a client component streams in. Islands call Start server functions
193
+ (`createServerFn`) and islx server actions (`"use server"`).
194
+
195
+ - An app with its own `src/server.ts` renders with islx's handler:
196
+ `createServerEntry({ fetch: createStartHandler(islandsHandler) })`, with `islandsHandler` from
197
+ `@farm.js/islx/tanstack-start/server` (and `handleAction` from `@farm.js/islx/server` in front of
198
+ it for islx actions).
199
+ - Client components run without a router in the browser: they cannot use `useRouter`,
200
+ `useNavigate` or `<Link>` (render plain `<a>` links; the server renders `<Link>` in routes).
201
+ - A library's `"use client"` components render as server HTML.
202
+ - Start sends a bot the whole page before any of it; people get it streamed.
203
+ - Start needs Node 22.12 or later.
204
+
205
+ `examples/tanstack-start` runs in the e2e suite, in development and from its production build.
206
+
172
207
  ## In a framework
173
208
 
174
209
  A framework that renders pages itself (Farm does) uses the pieces directly instead of
@@ -1,147 +1,6 @@
1
- import { A as VALUES_HEADER, P as formFieldName, d as FORM_REF_PREFIX, j as VALUE_KEY_PATTERN } from "./wire-QEQWBr5s.mjs";
2
- import { a as encode, c as needsObjectTag, o as encodeLeaf, r as decode, t as EncodeError } from "./codec-C8to-RQW.mjs";
1
+ import { P as formFieldName, d as FORM_REF_PREFIX } from "./wire-QEQWBr5s.mjs";
3
2
  import { t as getBuildDefaults } from "./build-COIcs7wm.mjs";
4
- // src/server/values.ts
5
- /**
6
- * Values sent once: which parts of an island's props travel as values of their own, keyed by a
7
- * hash of their JSON, and which of those a response leaves out because the browser already holds
8
- * them. The browser side is `@farm.js/islx/client` (`getValueStore`).
9
- */
10
- /** Shorter values stay inline: a reference and its script cost about 60 bytes. */
11
- const VALUE_MIN_LENGTH = 512;
12
- /** A 64-bit hash of a value's JSON, as 14 base-36 characters. Not cryptographic. */
13
- function hashValue(json) {
14
- let h1 = 3735928559 ^ json.length;
15
- let h2 = 1103547991 ^ json.length;
16
- for (let index = 0; index < json.length; index++) {
17
- const code = json.charCodeAt(index);
18
- h1 = Math.imul(h1 ^ code, 2654435761);
19
- h2 = Math.imul(h2 ^ code, 1597334677);
20
- }
21
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
22
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
23
- return (h2 >>> 0).toString(36).padStart(7, "0") + (h1 >>> 0).toString(36).padStart(7, "0");
24
- }
25
- /**
26
- * Serializes props with every value whose JSON reaches `minLength` replaced by a reference to it.
27
- * Types JSON cannot hold are tagged first (the codec), so a `Map` or a `Date` is addressed like
28
- * any other value. Inner values are addressed first, so a large value that differs between pages
29
- * in one place still shares its unchanged parts.
30
- */
31
- function addressValues(props, { minLength = 512, encodeSpecial, encoded = false } = {}) {
32
- const values = /* @__PURE__ */ new Map();
33
- const reference = (json, children, parent) => {
34
- const key = hashValue(json);
35
- if (!values.has(key)) values.set(key, {
36
- key,
37
- json,
38
- children
39
- });
40
- parent.push(key);
41
- return `{"$":"r","v":"${key}"}`;
42
- };
43
- const seen = /* @__PURE__ */ new Set();
44
- const encode = (value, parent) => {
45
- const special = encoded ? void 0 : encodeSpecial?.(value);
46
- if (special !== void 0) {
47
- const json = JSON.stringify(special);
48
- return [json, json.length];
49
- }
50
- const leaf = encoded ? void 0 : encodeLeaf(value);
51
- if (leaf) {
52
- const json = JSON.stringify(leaf);
53
- return [json, json.length];
54
- }
55
- if (typeof value === "function") throw new TypeError("functions are not serializable");
56
- if (typeof value === "symbol") throw new TypeError("symbol values are not serializable");
57
- if (value === null || typeof value !== "object") {
58
- const json = JSON.stringify(value);
59
- return [typeof value === "string" && json.length >= minLength ? reference(json, [], parent) : json, json.length];
60
- }
61
- if (seen.has(value)) throw new TypeError("circular values are not serializable");
62
- seen.add(value);
63
- try {
64
- if (!encoded && (value instanceof Map || value instanceof Set)) {
65
- const entries = value instanceof Map ? Array.from(value, ([key, entry]) => [key, entry]) : [...value];
66
- const [json, length] = encode(entries, parent);
67
- const prefix = `{"$":"${value instanceof Map ? "M" : "S"}","v":`;
68
- return [`${prefix}${json}}`, length + prefix.length + 1];
69
- }
70
- const prototype = Object.getPrototypeOf(value);
71
- if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) throw new TypeError("class instances are not serializable");
72
- const own = [];
73
- let length = 2;
74
- const parts = [];
75
- if (Array.isArray(value)) for (const entry of value) {
76
- const [json, entryLength] = encode(entry, own);
77
- parts.push(json);
78
- length += entryLength + 1;
79
- }
80
- else for (const [key, entry] of Object.entries(value)) {
81
- const [json, entryLength] = encode(entry, own);
82
- const name = JSON.stringify(key);
83
- parts.push(`${name}:${json}`);
84
- length += name.length + entryLength + 2;
85
- }
86
- let json = Array.isArray(value) ? `[${parts.join(",")}]` : `{${parts.join(",")}}`;
87
- if (!encoded && !Array.isArray(value) && needsObjectTag(value)) {
88
- json = `{"$":"o","v":${json}}`;
89
- length += 14;
90
- }
91
- const wrapper = own.length === 1 && json.length < minLength / 4;
92
- if (length >= minLength && !wrapper) return [reference(json, own, parent), length];
93
- parent.push(...own);
94
- return [json, length];
95
- } finally {
96
- seen.delete(value);
97
- }
98
- };
99
- const roots = [];
100
- return {
101
- json: `{${Object.entries(props).map(([key, entry]) => `${JSON.stringify(key)}:${encode(entry, roots)[0]}`).join(",")}}`,
102
- values,
103
- roots
104
- };
105
- }
106
- /**
107
- * The values a response sends for `addressed`, parents first. A value the browser holds is left
108
- * out with everything inside it: the browser kept those when it read the value.
109
- */
110
- function selectValuesToSend(addressed, held) {
111
- const send = [];
112
- const seen = /* @__PURE__ */ new Set();
113
- const visit = (key) => {
114
- if (seen.has(key)) return;
115
- seen.add(key);
116
- if (held?.has(key)) return;
117
- const value = addressed.values.get(key);
118
- send.push(value);
119
- value.children.forEach(visit);
120
- };
121
- addressed.roots.forEach(visit);
122
- return send;
123
- }
124
- /**
125
- * The value keys a request names (`F-Values`): well-formed keys only, at most `MAX_HELD_VALUES`.
126
- *
127
- * A response that leaves values out is one browser's. Integrations must keep it out of shared
128
- * caches (CDN, ISR, PPR shells), and add `Vary: F-Values` to cacheable responses.
129
- */
130
- function parseHeldValues(input) {
131
- const header = typeof input === "string" || input == null ? input : (input instanceof Request ? input.headers : input).get(VALUES_HEADER);
132
- const held = /* @__PURE__ */ new Set();
133
- if (!header) return held;
134
- for (const key of header.split(",", 256)) {
135
- const trimmed = key.trim();
136
- if (VALUE_KEY_PATTERN.test(trimmed)) held.add(trimmed);
137
- if (held.size === 128) break;
138
- }
139
- return held;
140
- }
141
- /** A value script's JSON, safe inside a `<script>` element. */
142
- function escapeScriptJson(json) {
143
- return json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
144
- }
3
+ import { a as encode, r as decode, t as EncodeError } from "./codec-C8to-RQW.mjs";
145
4
  // src/server/seal.ts
146
5
  /**
147
6
  * Sealing an action's captured values. An inline `"use server"` function that reads a variable of
@@ -612,4 +471,4 @@ async function resolveAction(id, token) {
612
471
  run: async (args) => runEntry(entry, values, args)
613
472
  };
614
473
  }
615
- export { hashValue as _, getActionReference as a, resolveAction as c, generateActionKey as d, sealBound as f, escapeScriptJson as g, addressValues as h, encodeActionReference as i, SealError as l, VALUE_MIN_LENGTH as m, configureActionKeys as n, registerInlineAction as o, unsealBound as p, decodeActionReference as r, registerServerReference as s, bindInlineAction as t, createActionKeys as u, parseHeldValues as v, selectValuesToSend as y };
474
+ export { getActionReference as a, resolveAction as c, generateActionKey as d, sealBound as f, encodeActionReference as i, SealError as l, configureActionKeys as n, registerInlineAction as o, unsealBound as p, decodeActionReference as r, registerServerReference as s, bindInlineAction as t, createActionKeys as u };
@@ -151,6 +151,10 @@ function state() {
151
151
  function setActionNavigator(navigator) {
152
152
  state().navigator = navigator;
153
153
  }
154
+ /** Installs the framework's interceptor for server action calls (one; a later call replaces it). */
155
+ function setActionInterceptor(interceptor) {
156
+ state().interceptor = interceptor;
157
+ }
154
158
  /** Encodes an action passed back to the server as an argument. */
155
159
  function encodeFunction(value) {
156
160
  const reference = state().info.get(value);
@@ -252,7 +256,28 @@ function createReference(reference) {
252
256
  reference.redirect = void 0;
253
257
  return call(reference, args);
254
258
  };
255
- const result = client.queue.then(run).then((value) => {
259
+ const previous = client.queue;
260
+ const queued = () => previous.then(run);
261
+ const interceptor = client.interceptor;
262
+ let sent;
263
+ if (!interceptor) sent = queued();
264
+ else {
265
+ let stale = false;
266
+ const next = () => queued().then((value) => {
267
+ if (value === STALE) stale = true;
268
+ return value;
269
+ });
270
+ try {
271
+ sent = Promise.resolve(interceptor({
272
+ id,
273
+ name,
274
+ args: [...reference.bound, ...args]
275
+ }, next)).then((value) => stale ? STALE : value);
276
+ } catch (error) {
277
+ sent = Promise.reject(error);
278
+ }
279
+ }
280
+ const result = sent.then((value) => {
256
281
  if (value !== STALE) {
257
282
  const { redirect } = reference;
258
283
  emit("actionEnd", {
@@ -303,4 +328,4 @@ function createServerReference(id, token, name) {
303
328
  }
304
329
  return reference;
305
330
  }
306
- export { emit as a, createValueStore as c, defineClientHooks as i, getValueStore as l, decodeActionReference as n, hookEventName as o, setActionNavigator as r, setClientHooks as s, createServerReference as t, valueHeaders as u };
331
+ export { defineClientHooks as a, setClientHooks as c, valueHeaders as d, setActionNavigator as i, createValueStore as l, decodeActionReference as n, emit as o, setActionInterceptor as r, hookEventName as s, createServerReference as t, getValueStore as u };
@@ -0,0 +1,262 @@
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 };