@farm.js/islx 0.0.5 → 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 +15 -0
- package/README.md +4 -4
- package/dist/_chunks/{actions-8RQfrM-p.mjs → actions-C-IwUCKU.mjs} +27 -2
- package/dist/_chunks/{tree-BUkGP4AR.mjs → tree-C47p6khN.mjs} +1 -1
- package/dist/_chunks/{vite-DG7hBn8A.mjs → vite-BTzYlEiY.mjs} +4 -1
- package/dist/client.d.mts +17 -1
- package/dist/client.mjs +2 -2
- package/dist/react/client.mjs +2 -2
- package/dist/react/server.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/tanstack-start.mjs +13 -1
- package/dist/vite.mjs +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
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
|
+
|
|
3
18
|
## 0.0.5
|
|
4
19
|
|
|
5
20
|
TanStack Start.
|
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.
|
|
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
|
|
|
@@ -186,7 +186,9 @@ export default defineConfig({ plugins: [tanstackStart(), react(), islxTanstackSt
|
|
|
186
186
|
Routes, loaders and layouts render on the server. The browser loads islx and the `"use client"`
|
|
187
187
|
components a page uses (the modules under `src/`), not the router, the route tree or loader
|
|
188
188
|
data: Start's client entry becomes islx's, `<Scripts />` still loads it, and the router is not
|
|
189
|
-
dehydrated.
|
|
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
|
|
190
192
|
unawaited to a client component streams in. Islands call Start server functions
|
|
191
193
|
(`createServerFn`) and islx server actions (`"use server"`).
|
|
192
194
|
|
|
@@ -197,8 +199,6 @@ unawaited to a client component streams in. Islands call Start server functions
|
|
|
197
199
|
- Client components run without a router in the browser: they cannot use `useRouter`,
|
|
198
200
|
`useNavigate` or `<Link>` (render plain `<a>` links; the server renders `<Link>` in routes).
|
|
199
201
|
- A library's `"use client"` components render as server HTML.
|
|
200
|
-
- A click before the islands hydrate is not replayed yet (the `interaction` strategy hydrates on
|
|
201
|
-
it, but the click itself is dropped).
|
|
202
202
|
- Start sends a bot the whole page before any of it; people get it streamed.
|
|
203
203
|
- Start needs Node 22.12 or later.
|
|
204
204
|
|
|
@@ -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
|
|
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 {
|
|
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 };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as decodeTagged, n as NOT_A_VALUE, o as encodeLeaf } from "./codec-C8to-RQW.mjs";
|
|
2
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 {
|
|
3
|
+
import { t as createServerReference, u as getValueStore } from "./actions-C-IwUCKU.mjs";
|
|
4
4
|
import { a as getIslandServerHook, o as isolateIslands, r as getClientReference } from "./island-DVOr-OUw.mjs";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
// src/react/tree.ts
|
|
@@ -177,6 +177,7 @@ function transformClientModule(options) {
|
|
|
177
177
|
const RUNTIME_ID = "virtual:islx/runtime";
|
|
178
178
|
/** @internal The browser entry, for a framework whose own entry imports it. */
|
|
179
179
|
const ENTRY_ID = "virtual:islx/client-entry";
|
|
180
|
+
const LIBRARY_RUNTIME_ID = "virtual:islx/react-for-libraries";
|
|
180
181
|
const CLIENT_HOOKS_ID = "virtual:islx/hooks-client";
|
|
181
182
|
const SERVER_HOOKS_ID = "virtual:islx/hooks-server";
|
|
182
183
|
const BROWSER_GLOBALS = /* @__PURE__ */ new Set([
|
|
@@ -366,6 +367,7 @@ function islx(options = {}, host = {}) {
|
|
|
366
367
|
preamble = resolved.plugins.some((plugin) => plugin.name === "vite:react-virtual-preamble");
|
|
367
368
|
},
|
|
368
369
|
resolveId(id) {
|
|
370
|
+
if (id === LIBRARY_RUNTIME_ID) return this.resolve("@farm.js/islx/react", path.join(config.root, "package.json"), { skipSelf: true });
|
|
369
371
|
if (id === RUNTIME_ID || id === "virtual:islx/client-entry" || id === CLIENT_HOOKS_ID || id === SERVER_HOOKS_ID) return `\0${id}`;
|
|
370
372
|
},
|
|
371
373
|
async load(id) {
|
|
@@ -425,7 +427,8 @@ function islx(options = {}, host = {}) {
|
|
|
425
427
|
return JSON.stringify(relative);
|
|
426
428
|
},
|
|
427
429
|
sideEffects: [RUNTIME_ID],
|
|
428
|
-
directive: clientDirective
|
|
430
|
+
directive: clientDirective,
|
|
431
|
+
...library ? { runtime: LIBRARY_RUNTIME_ID } : {}
|
|
429
432
|
});
|
|
430
433
|
if (island) {
|
|
431
434
|
if (!library) transformActions({
|
package/dist/client.d.mts
CHANGED
|
@@ -10,8 +10,24 @@ interface ActionNavigator {
|
|
|
10
10
|
/** A client navigation to `href`, as the action's redirect. */
|
|
11
11
|
navigate?(href: string): Promise<void> | void;
|
|
12
12
|
}
|
|
13
|
+
/** A call of a server action, as an interceptor sees it. */
|
|
14
|
+
interface ActionCall {
|
|
15
|
+
id: string;
|
|
16
|
+
/** The export (or inline function) name, when known. */
|
|
17
|
+
name?: string;
|
|
18
|
+
/** The arguments, after any bound with `.bind`. */
|
|
19
|
+
args: readonly unknown[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wraps every server action call: call `next` to make the request, and return what the caller
|
|
23
|
+
* gets. A framework uses it to unwrap what its own server side adds to a result (cache
|
|
24
|
+
* invalidations, a query's cache entry) or to turn a returned error back into one it throws.
|
|
25
|
+
*/
|
|
26
|
+
type ActionInterceptor = (call: ActionCall, next: () => Promise<unknown>) => Promise<unknown>;
|
|
13
27
|
/** Connects actions to the framework's router. */
|
|
14
28
|
declare function setActionNavigator(navigator: ActionNavigator): void;
|
|
29
|
+
/** Installs the framework's interceptor for server action calls (one; a later call replaces it). */
|
|
30
|
+
declare function setActionInterceptor(interceptor: ActionInterceptor | undefined): void;
|
|
15
31
|
/** Decodes an action in a result or props: `{"$":"a","v":id,"b"?:sealed}`. */
|
|
16
32
|
declare function decodeActionReference(object: Record<string, unknown>): AnyFunction | undefined;
|
|
17
33
|
/** The function that calls a server action; one per id and sealed values, so props keep identity. */
|
|
@@ -55,4 +71,4 @@ declare const INTERACTION_EVENT = "f:island-interaction";
|
|
|
55
71
|
declare const REPLAYABLE_SELECTOR = "button,[role=\"button\"],input[type=\"button\"],input[type=\"submit\"],input[type=\"reset\"]";
|
|
56
72
|
/** The inline script's source (no `<script>` tag): pass it to the page's head or bootstrap. */
|
|
57
73
|
declare const PREHYDRATION_SCRIPT: string;
|
|
58
|
-
export { type ActionNavigator, type ClientHookEvent, type ClientHookEvents, type ClientHooks, INTERACTION_EVENT, NAVIGATION_RENDER, type NavigateOptions, type NavigationOptions, PENDING_INTERACTIONS, PREHYDRATION_SCRIPT, REPLAYABLE_SELECTOR, type ValueStore, createServerReference, createValueStore, decodeActionReference, defineClientHooks, enhanceForms, getValueStore, hookEventName, navigate, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
|
|
74
|
+
export { type ActionCall, type ActionInterceptor, type ActionNavigator, type ClientHookEvent, type ClientHookEvents, type ClientHooks, INTERACTION_EVENT, NAVIGATION_RENDER, type NavigateOptions, type NavigationOptions, PENDING_INTERACTIONS, PREHYDRATION_SCRIPT, REPLAYABLE_SELECTOR, type ValueStore, createServerReference, createValueStore, decodeActionReference, defineClientHooks, enhanceForms, getValueStore, hookEventName, navigate, setActionInterceptor, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
|
package/dist/client.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as defineClientHooks, c as setClientHooks, d as valueHeaders, i as setActionNavigator, l as createValueStore, n as decodeActionReference, o as emit, r as setActionInterceptor, s as hookEventName, t as createServerReference, u as getValueStore } from "./_chunks/actions-C-IwUCKU.mjs";
|
|
2
2
|
import { C as RENDER_HEADER, I as readPostedAction, L as stripRuntimeFields } from "./_chunks/wire-QEQWBr5s.mjs";
|
|
3
3
|
import { i as REPLAYABLE_SELECTOR, n as PENDING_INTERACTIONS, r as PREHYDRATION_SCRIPT, t as INTERACTION_EVENT } from "./_chunks/prehydration-B1GLTLdk.mjs";
|
|
4
4
|
// src/client/forms.ts
|
|
@@ -194,4 +194,4 @@ function navigate(href, options = {}) {
|
|
|
194
194
|
else location.assign(href);
|
|
195
195
|
return Promise.resolve();
|
|
196
196
|
}
|
|
197
|
-
export { INTERACTION_EVENT, NAVIGATION_RENDER, PENDING_INTERACTIONS, PREHYDRATION_SCRIPT, REPLAYABLE_SELECTOR, createServerReference, createValueStore, decodeActionReference, defineClientHooks, enhanceForms, getValueStore, hookEventName, navigate, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
|
|
197
|
+
export { INTERACTION_EVENT, NAVIGATION_RENDER, PENDING_INTERACTIONS, PREHYDRATION_SCRIPT, REPLAYABLE_SELECTOR, createServerReference, createValueStore, decodeActionReference, defineClientHooks, enhanceForms, getValueStore, hookEventName, navigate, setActionInterceptor, setActionNavigator, setClientHooks, startNavigation, valueHeaders };
|
package/dist/react/client.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as valueHeaders, o as emit, t as createServerReference, u as getValueStore } from "../_chunks/actions-C-IwUCKU.mjs";
|
|
2
2
|
import { i as decodeTagged, n as NOT_A_VALUE, r as decode, u as setOwn } from "../_chunks/codec-C8to-RQW.mjs";
|
|
3
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
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-
|
|
5
|
+
import { i as createServerTreeLayout, n as PAYLOAD_ONLY_ATTRIBUTE, o as updateServerTrees, r as createServerTreeElement } from "../_chunks/tree-C47p6khN.mjs";
|
|
6
6
|
import { n as isIslandStrategy, r as scheduleHydration, t as ISLAND_STRATEGIES } from "../_chunks/schedule-CjoybS3b.mjs";
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
import { hydrateRoot } from "react-dom/client";
|
package/dist/react/server.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { i as encodeActionReference } from "../_chunks/actions-BuS5jDi0.mjs";
|
|
|
5
5
|
import { t as getBuildDefaults } from "../_chunks/build-COIcs7wm.mjs";
|
|
6
6
|
import { a as parseHeldValues, n as addressValues, o as selectValuesToSend, r as escapeScriptJson } from "../_chunks/values-37UIzxM0.mjs";
|
|
7
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-
|
|
8
|
+
import { a as renderServerTree, n as PAYLOAD_ONLY_ATTRIBUTE, t as Outlet } from "../_chunks/tree-C47p6khN.mjs";
|
|
9
9
|
import * as React from "react";
|
|
10
10
|
// src/react/server.ts
|
|
11
11
|
/**
|
package/dist/react.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
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-
|
|
2
|
+
import { a as renderServerTree, t as Outlet } from "./_chunks/tree-C47p6khN.mjs";
|
|
3
3
|
export { IslandsProvider, Outlet, createIsland, isolateIslands, renderServerTree, withinClientRoot };
|
package/dist/tanstack-start.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as islx, t as ENTRY_ID } from "./_chunks/vite-
|
|
1
|
+
import { n as islx, t as ENTRY_ID } from "./_chunks/vite-BTzYlEiY.mjs";
|
|
2
2
|
import { normalizePath } from "vite";
|
|
3
3
|
// src/tanstack-start/index.ts
|
|
4
4
|
/**
|
|
@@ -30,7 +30,9 @@ export default createServerEntry({
|
|
|
30
30
|
});
|
|
31
31
|
`;
|
|
32
32
|
const routerWrapper = (routerPath) => `import * as app from ${JSON.stringify(routerPath)};
|
|
33
|
+
import { PREHYDRATION_SCRIPT } from "@farm.js/islx/client";
|
|
33
34
|
export * from ${JSON.stringify(routerPath)};
|
|
35
|
+
const islxHead = new WeakSet();
|
|
34
36
|
export async function getRouter(...args) {
|
|
35
37
|
const router = await app.getRouter(...args);
|
|
36
38
|
const lifecycle = (router.serverSsrLifecycle ??= {});
|
|
@@ -38,6 +40,16 @@ export async function getRouter(...args) {
|
|
|
38
40
|
(lifecycle.onServerSsrAttach ??= []).push((ssr) => {
|
|
39
41
|
ssr.dehydrate = async () => ssr.disableHydration();
|
|
40
42
|
});
|
|
43
|
+
const root = router.routeTree;
|
|
44
|
+
if (root && !islxHead.has(root)) {
|
|
45
|
+
islxHead.add(root);
|
|
46
|
+
const head = root.options.head;
|
|
47
|
+
const add = (own) => ({ ...own, scripts: [{ children: PREHYDRATION_SCRIPT }, ...(own?.scripts ?? [])] });
|
|
48
|
+
root.options.head = (context) => {
|
|
49
|
+
const own = head?.(context);
|
|
50
|
+
return own && typeof own.then === "function" ? own.then(add) : add(own);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
41
53
|
return router;
|
|
42
54
|
}
|
|
43
55
|
`;
|
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-
|
|
1
|
+
export { t as ENTRY_ID, n as default, n as islx, r as resolveDirectives, i as serializeHooks } from "./_chunks/vite-BTzYlEiY.mjs";
|
package/package.json
CHANGED