@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,269 +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, declared = "load", options = {}) {
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
- let strategy = declared;
219
- try {
220
- const slots = { next: 0 };
221
- const { render: rendered, wire } = splitSlots(props, `${id}s`, slots);
222
- renderProps = rendered;
223
- if (options.inferred && slots.next > 0) strategy = "load";
224
- const skip = state ? /* @__PURE__ */ new Set([...state.held ?? [], ...state.sent]) : null;
225
- encoded = hook.encode(wire, skip);
226
- for (const value of encoded.values) state?.sent.add(value.key);
227
- } catch (error) {
228
- if (isThenable(error)) throw error;
229
- const message = error instanceof Error ? error.message : String(error);
230
- const key = `${src}#${exportName}:${message}`;
231
- if (!warned.has(key)) {
232
- warned.add(key);
233
- console.warn(`[islx] ${exportName} (${src}) was rendered without hydration: its props cannot be sent to the browser. ${message}`);
234
- }
235
- return React.createElement(Component, props);
236
- }
237
- if (strategy === "load") {
238
- const defaults = getBuildDefaults();
239
- if (defaults.boot && defaults.entry) ReactDOM.preloadModule?.(defaults.entry, { as: "script" });
240
- for (const href of defaults.modules?.[src] ?? []) ReactDOM.preloadModule?.(href, { as: "script" });
241
- }
242
- return React.createElement(React.Fragment, null, ...encoded.values.map((value) => React.createElement("script", {
243
- key: value.key,
244
- type: VALUE_SCRIPT_TYPE,
245
- name: value.key,
246
- dangerouslySetInnerHTML: { __html: hook.escape(value.json) }
247
- })), React.createElement(ISLAND_ELEMENT, {
248
- key: "island",
249
- src,
250
- export: exportName,
251
- props: id,
252
- strategy,
253
- style: slotStyle
254
- }, React.createElement(graph.Provider, { value: true }, React.createElement(Component, renderProps))), React.createElement("script", {
255
- key: "props",
256
- type: PROPS_SCRIPT_TYPE,
257
- htmlFor: id,
258
- dangerouslySetInnerHTML: { __html: hook.escape(encoded.json) }
259
- }), ...promiseBoundaries(encoded.promises, `${exportName} (${src})`));
260
- }
261
- Island.displayName = `Island(${Component.displayName || Component.name || exportName})`;
262
- Object.defineProperty(Island, CLIENT_REFERENCE, { value: {
263
- src,
264
- exportName,
265
- Component
266
- } });
267
- return Island;
268
- }
269
- 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 };