@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 +28 -0
- package/README.md +36 -1
- package/dist/_chunks/{actions-CtLpJPEB.mjs → actions-BuS5jDi0.mjs} +3 -144
- package/dist/_chunks/{actions-8RQfrM-p.mjs → actions-C-IwUCKU.mjs} +27 -2
- package/dist/_chunks/island-DVOr-OUw.mjs +262 -0
- package/dist/_chunks/{tree-opIpaCke.mjs → tree-C47p6khN.mjs} +4 -262
- package/dist/_chunks/values-37UIzxM0.mjs +144 -0
- package/dist/_chunks/vite-BTzYlEiY.mjs +506 -0
- package/dist/client.d.mts +17 -1
- package/dist/client.mjs +2 -2
- package/dist/react/client.mjs +3 -2
- package/dist/react/server.mjs +4 -2
- package/dist/react.mjs +3 -1
- package/dist/server.mjs +2 -1
- package/dist/tanstack-start/server.d.mts +11 -0
- package/dist/tanstack-start/server.mjs +54 -0
- package/dist/tanstack-start.d.mts +8 -0
- package/dist/tanstack-start.mjs +100 -0
- package/dist/vite.d.mts +21 -2
- package/dist/vite.mjs +1 -497
- package/package.json +19 -1
|
@@ -0,0 +1,54 @@
|
|
|
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";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { StartServer } from "@tanstack/react-start/server";
|
|
6
|
+
import { createSsrStreamResponse, defineHandlerCallback, getSsrStatus, renderRouterToStream, transformReadableStreamWithRouter } from "@tanstack/react-router/ssr/server";
|
|
7
|
+
// src/tanstack-start/server.ts
|
|
8
|
+
/**
|
|
9
|
+
* @farm.js/islx/tanstack-start/server: the render handler of a TanStack Start app as islands.
|
|
10
|
+
* `islxTanstackStart()` uses it for Start's default server entry; an app with its own
|
|
11
|
+
* `src/server.ts` passes it to `createStartHandler`:
|
|
12
|
+
*
|
|
13
|
+
* import { createServerEntry, createStartHandler } from "@tanstack/react-start/server";
|
|
14
|
+
* import { islandsHandler } from "@farm.js/islx/tanstack-start/server";
|
|
15
|
+
* export default createServerEntry({ fetch: createStartHandler(islandsHandler) });
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Renders the page with its islands: values the browser holds stay out, and a navigation (an
|
|
19
|
+
* `F-Render` request) gets the whole page in one piece, as islx's browser runtime swaps it in.
|
|
20
|
+
*/
|
|
21
|
+
const islandsHandler = defineHandlerCallback(({ request, router, responseHeaders }) => {
|
|
22
|
+
const held = parseHeldValues(request);
|
|
23
|
+
responseHeaders.append("Vary", `${VALUES_HEADER}, ${RENDER_HEADER}`);
|
|
24
|
+
if (held.size > 0) responseHeaders.set("Cache-Control", "private, no-store");
|
|
25
|
+
const children = React.createElement(IslandsProvider, { held }, React.createElement(StartServer, { router }));
|
|
26
|
+
if (!request.headers.has("F-Render")) return renderRouterToStream({
|
|
27
|
+
request,
|
|
28
|
+
router,
|
|
29
|
+
responseHeaders,
|
|
30
|
+
children
|
|
31
|
+
});
|
|
32
|
+
return renderWhole(request, router, responseHeaders, children);
|
|
33
|
+
});
|
|
34
|
+
/** Waits for every Suspense boundary, so React writes the page with no streaming scripts. */
|
|
35
|
+
async function renderWhole(request, router, responseHeaders, children) {
|
|
36
|
+
const { renderToReadableStream } = await import("react-dom/server");
|
|
37
|
+
const stream = await renderToReadableStream(children, {
|
|
38
|
+
signal: request.signal,
|
|
39
|
+
nonce: router.options.ssr?.nonce,
|
|
40
|
+
onError(error) {
|
|
41
|
+
if (!request.signal.aborted) console.error("Error in renderToReadableStream:", error);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
await stream.allReady;
|
|
45
|
+
const body = transformReadableStreamWithRouter(router, stream, {
|
|
46
|
+
rendererSafePoint: "script-close",
|
|
47
|
+
signal: request.signal
|
|
48
|
+
});
|
|
49
|
+
return createSsrStreamResponse(router, new Response(body, {
|
|
50
|
+
status: getSsrStatus(router),
|
|
51
|
+
headers: responseHeaders
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
export { islandsHandler };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IslxOptions } from "./vite.mjs";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
|
|
4
|
+
// src/tanstack-start/index.d.ts
|
|
5
|
+
|
|
6
|
+
type IslxTanstackStartOptions = IslxOptions;
|
|
7
|
+
declare function islxTanstackStart(options?: IslxTanstackStartOptions): Plugin[];
|
|
8
|
+
export { IslxTanstackStartOptions, islxTanstackStart as default, islxTanstackStart };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { n as islx, t as ENTRY_ID } from "./_chunks/vite-BTzYlEiY.mjs";
|
|
2
|
+
import { normalizePath } from "vite";
|
|
3
|
+
// src/tanstack-start/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* @farm.js/islx/tanstack-start: a TanStack Start app as islands.
|
|
6
|
+
*
|
|
7
|
+
* import { tanstackStart } from "@tanstack/react-start/plugin/vite";
|
|
8
|
+
* import { islxTanstackStart } from "@farm.js/islx/tanstack-start";
|
|
9
|
+
* export default defineConfig({ plugins: [tanstackStart(), react(), islxTanstackStart()] });
|
|
10
|
+
*
|
|
11
|
+
* Routes render on the server; the browser loads islx and the `"use client"` components a page
|
|
12
|
+
* uses, not the router or the routes. TanStack Start's client entry becomes islx's browser entry,
|
|
13
|
+
* so `<Scripts />` still loads it. Client components are the `"use client"` modules under `src/`
|
|
14
|
+
* (`clientComponents.include`); a library's stay server HTML.
|
|
15
|
+
*/
|
|
16
|
+
const START_CLIENT_ENTRY = "virtual:tanstack-start-client-entry";
|
|
17
|
+
const START_SERVER_ENTRY = "virtual:tanstack-start-server-entry";
|
|
18
|
+
const START_ROUTER_ENTRY = "#tanstack-router-entry";
|
|
19
|
+
const ROUTER_WRAPPER = "\0islx:tanstack-router";
|
|
20
|
+
const SERVER_WRAPPER = "\0islx:tanstack-server-entry";
|
|
21
|
+
const SERVER_ENTRY = `import { createServerEntry, createStartHandler } from "@tanstack/react-start/server";
|
|
22
|
+
import { handleAction } from "@farm.js/islx/server";
|
|
23
|
+
import { islandsHandler } from "@farm.js/islx/tanstack-start/server";
|
|
24
|
+
const start = createStartHandler(islandsHandler);
|
|
25
|
+
export default createServerEntry({
|
|
26
|
+
// islx's server actions first; any other request (and Start's server functions) goes to Start.
|
|
27
|
+
async fetch(request, ...rest) {
|
|
28
|
+
return (await handleAction(request)) ?? start(request, ...rest);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
`;
|
|
32
|
+
const routerWrapper = (routerPath) => `import * as app from ${JSON.stringify(routerPath)};
|
|
33
|
+
import { PREHYDRATION_SCRIPT } from "@farm.js/islx/client";
|
|
34
|
+
export * from ${JSON.stringify(routerPath)};
|
|
35
|
+
const islxHead = new WeakSet();
|
|
36
|
+
export async function getRouter(...args) {
|
|
37
|
+
const router = await app.getRouter(...args);
|
|
38
|
+
const lifecycle = (router.serverSsrLifecycle ??= {});
|
|
39
|
+
// Start dehydrates every request: here that turns hydration off instead.
|
|
40
|
+
(lifecycle.onServerSsrAttach ??= []).push((ssr) => {
|
|
41
|
+
ssr.dehydrate = async () => ssr.disableHydration();
|
|
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
|
+
}
|
|
53
|
+
return router;
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
function islxTanstackStart(options = {}) {
|
|
57
|
+
let clientEntry;
|
|
58
|
+
let serverEntry;
|
|
59
|
+
let routerEntry;
|
|
60
|
+
const pathOf = (id) => normalizePath(id.split("?", 1)[0]);
|
|
61
|
+
return [{
|
|
62
|
+
name: "islx:tanstack-start",
|
|
63
|
+
enforce: "pre",
|
|
64
|
+
configResolved(config) {
|
|
65
|
+
const aliasOf = (find) => {
|
|
66
|
+
const alias = config.resolve.alias.find((entry) => entry.find === find);
|
|
67
|
+
if (!alias) throw new Error("islx: add tanstackStart() from @tanstack/react-start/plugin/vite before islxTanstackStart()");
|
|
68
|
+
return normalizePath(alias.replacement);
|
|
69
|
+
};
|
|
70
|
+
clientEntry = aliasOf(START_CLIENT_ENTRY);
|
|
71
|
+
routerEntry = aliasOf(START_ROUTER_ENTRY);
|
|
72
|
+
const server = aliasOf(START_SERVER_ENTRY);
|
|
73
|
+
if (server.includes("/node_modules/") || server.includes("/@tanstack/")) serverEntry = server;
|
|
74
|
+
},
|
|
75
|
+
resolveId(id, importer) {
|
|
76
|
+
if (this.environment.name === "client" || !routerEntry) return void 0;
|
|
77
|
+
if (serverEntry && pathOf(id) === serverEntry) return SERVER_WRAPPER;
|
|
78
|
+
if (!importer) return void 0;
|
|
79
|
+
if (pathOf(id) === routerEntry && importer !== ROUTER_WRAPPER && importer.includes("@tanstack")) return ROUTER_WRAPPER;
|
|
80
|
+
},
|
|
81
|
+
load(id) {
|
|
82
|
+
if (id === ROUTER_WRAPPER) return routerWrapper(routerEntry);
|
|
83
|
+
if (id === SERVER_WRAPPER) return SERVER_ENTRY;
|
|
84
|
+
const file = pathOf(id);
|
|
85
|
+
if (this.environment.name === "client") return file === clientEntry ? `import ${JSON.stringify(ENTRY_ID)};` : void 0;
|
|
86
|
+
}
|
|
87
|
+
}, ...islx({
|
|
88
|
+
serverEnvironments: ["ssr"],
|
|
89
|
+
...options,
|
|
90
|
+
clientComponents: {
|
|
91
|
+
include: options.clientComponents?.include ?? ["src/**"],
|
|
92
|
+
exclude: ["**/node_modules/**", ...options.clientComponents?.exclude ?? []]
|
|
93
|
+
}
|
|
94
|
+
}, {
|
|
95
|
+
frameworkEntry: true,
|
|
96
|
+
frameworkBuild: true,
|
|
97
|
+
frameworkRender: true
|
|
98
|
+
})];
|
|
99
|
+
}
|
|
100
|
+
export { islxTanstackStart as default, islxTanstackStart };
|
package/dist/vite.d.mts
CHANGED
|
@@ -43,6 +43,8 @@ interface IslxOptions {
|
|
|
43
43
|
server?: string | ServerHooks;
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
/** @internal The browser entry, for a framework whose own entry imports it. */
|
|
47
|
+
declare const ENTRY_ID = "virtual:islx/client-entry";
|
|
46
48
|
/**
|
|
47
49
|
* The source of a hooks module made from callbacks. Each callback is copied as written, so it
|
|
48
50
|
* must not use anything from where it was written: an outside variable, an import.
|
|
@@ -53,5 +55,22 @@ declare function resolveDirectives(directives?: IslxOptions["directives"]): {
|
|
|
53
55
|
client: string;
|
|
54
56
|
server: string;
|
|
55
57
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
/**
|
|
59
|
+
* @internal How a framework integration hosts islx (`@farm.js/islx/tanstack-start`).
|
|
60
|
+
*/
|
|
61
|
+
interface IslxHost {
|
|
62
|
+
/** The framework's own client entry imports islx's, so islx adds no browser entry of its own. */
|
|
63
|
+
frameworkEntry?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The framework renders pages itself, without `renderToStream`: the server runtime islands need
|
|
66
|
+
* loads with them.
|
|
67
|
+
*/
|
|
68
|
+
frameworkRender?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The framework orders the build (the browser before the server): client modules come from
|
|
71
|
+
* `clientComponents.include`, and the server build gets their files directly.
|
|
72
|
+
*/
|
|
73
|
+
frameworkBuild?: boolean;
|
|
74
|
+
}
|
|
75
|
+
declare function islx(options?: IslxOptions, host?: IslxHost): Plugin[];
|
|
76
|
+
export { ENTRY_ID, IslxHost, IslxOptions, islx as default, islx, resolveDirectives, serializeHooks };
|