-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.ts
More file actions
118 lines (110 loc) · 5.39 KB
/
Copy pathserver.ts
File metadata and controls
118 lines (110 loc) · 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Server half of `prerendered`. On the server a prerendered reference is
// the GET-declared reference with one extra behavior: while a capture sink
// is installed (the prerender integration installs one for the duration of
// the crawl — see ./integration.ts), every executed call is delivered to it as
// (id, args, settled value), and the delivery is AWAITED so a page's
// render does not finish before its artifacts are safely captured.
// Without a sink — dev SSR, a live production server — the wrapper is
// call-through: the function runs in-process like any direct SSR call.
import {
GET,
SERVER_FUNCTION_INVOKE,
getServerFunctionMetadata,
isServerFunction
} from "@solidjs/web/server-functions/server";
import { getRequestEvent } from "@solidjs/web";
import type { RequestEvent, ResponseStub } from "@solidjs/web";
import { announcePages, solidRouterPages, tanstackRouterPages } from "prerender-crawler/routers";
import type {
SolidRouteLike,
SolidRouterLike,
TanStackRouterLike
} from "prerender-crawler/routers";
import { CAPTURE_SINK, PRERENDERED_META_KEY } from "./shared.ts";
import type { AnnounceRoutesOptions, CaptureSink, PrerenderedFunction } from "./shared.ts";
export { staticArtifactPath, staticCallKey } from "./shared.ts";
export type { CaptureSink, PrerenderedFunction } from "./shared.ts";
export type { AnnounceRoutesOptions } from "./shared.ts";
/** A router `announceRoutes` can read: Solid Router (instance or tree) or a TanStack Router instance. */
export type AnnounceableRouter =
SolidRouterLike | SolidRouteLike | readonly SolidRouteLike[] | TanStackRouterLike;
/**
* Tells a prerender crawl which pages this app's router has — the server
* half. Called during a server render or request setup, it reads the
* ambient request: when the request is the crawler's, the router's static
* pages go on the response's hint header and the crawl seeds every one of
* them, linked or not. A visitor's request is untouched; on the client
* this is a no-op. Returns whether it announced.
*
* Takes either router Solid apps use — a Solid Router `createRouter`
* instance (or its route-definition tree, with `base`) or a TanStack
* Router instance — and tells them apart by shape.
*
* ```tsx
* import { announceRoutes } from "@solidjs/prerender";
* import { Router } from "./router";
*
* export default function App() {
* announceRoutes(Router);
* return <Router>{props => props.children}</Router>;
* }
* ```
*
* Dynamic routes (`/posts/:id`, `/posts/$id`) are not announced — only a
* render knows their values; the crawl finds them by their links.
*/
export function announceRoutes(router: AnnounceableRouter, options: AnnounceRoutesOptions = {}) {
const event = getRequestEvent() as (RequestEvent & { response?: ResponseStub }) | undefined;
if (!event?.response || event.response.committed) return false;
if (!event.request.headers.has(options.header ?? "x-prerender")) return false;
const pages = isTanStackRouter(router)
? tanstackRouterPages(router)
: solidRouterPages(router, { base: options.base });
return announcePages(event.request, event.response.headers, pages, { header: options.header });
}
// a TanStack instance carries its path index; nothing of Solid Router's does
const isTanStackRouter = (router: AnnounceableRouter): router is TanStackRouterLike =>
typeof router === "object" && router !== null && "routesByPath" in router;
const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
/**
* Declares a server function PRERENDERED — the server half. Calling the
* reference during SSR runs the function in-process exactly like a direct
* server-function call; during prerendering (when the prerender
* integration has installed its capture sink) each call's settled result
* is additionally captured as a static artifact keyed by the call's
* identity. The declaration implies `GET` — it registers the id's GET
* grant so the client's live fallback (dev, plugin-less builds) can
* dispatch over HTTP GET.
*
* See the client half (the browser build of this module) for the full
* declaration contract.
*/
export function prerendered<A extends readonly unknown[], R>(
fn: (...args: A) => R
): PrerenderedFunction<A, Awaited<R>> {
if (!isServerFunction(fn)) {
throw new Error("prerendered expects a server function reference");
}
// the GET declaration registers the id's method grant server-side, so
// the client's live-posture GET calls dispatch instead of answering 405
const source: any =
getServerFunctionMetadata(fn)?.method === "GET" ? fn : GET(fn as (...args: any[]) => any);
const id: string = source.id;
const run = async (args: A, options?: unknown) => {
const value = await source[SERVER_FUNCTION_INVOKE](args, options);
const sink = (globalThis as { [CAPTURE_SINK]?: CaptureSink })[CAPTURE_SINK];
// awaited so the render that triggered the call cannot outrun the
// capture — the prerender run's teardown sees every artifact settled
if (sink) await sink.capture(id, args, value);
return value as Awaited<R>;
};
const wrapped = ((...args: A) => run(args)) as any;
wrapped[SERVER_FUNCTION_METADATA] = {
...getServerFunctionMetadata(source),
[PRERENDERED_META_KEY]: true
};
wrapped[SERVER_FUNCTION_INVOKE] = run;
wrapped.id = id;
Object.defineProperty(wrapped, "url", { get: () => source.url, configurable: true });
return wrapped as PrerenderedFunction<A, Awaited<R>>;
}