@solidjs/prerender 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -45,6 +45,32 @@ export const getPost = query(
45
45
 
46
46
  Design pages so the crawl exercises the calls the site needs — which happens naturally when pages link to what they use.
47
47
 
48
+ ## `announceRoutes(router)`
49
+
50
+ The crawl follows links; a page nothing links to needs announcing. Call this in the app root during render with the `createRouter` instance (or a route-definition tree, with `{ base }`):
51
+
52
+ ```tsx
53
+ import { announceRoutes } from "@solidjs/prerender";
54
+ import { Router } from "./router";
55
+
56
+ export default function App() {
57
+ announceRoutes(Router);
58
+ return <Router>{props => props.children}</Router>;
59
+ }
60
+ ```
61
+
62
+ On the server, 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. A visitor's response is untouched; in the browser it is a no-op. Dynamic routes (`/posts/:id`) are not announced — only a render knows their values; the crawl finds them by their links. Options: `header` (a custom crawl `hintHeader`), `base`.
63
+
64
+ It takes either router Solid apps use. With TanStack Router the instance is built per request, so the call goes where the router is — the `start.setup` hook:
65
+
66
+ ```ts
67
+ // src/setup.tsx
68
+ const router = createAppRouter(queryClient, history);
69
+ announceRoutes(router);
70
+ ```
71
+
72
+ The enumerators behind it are exported too — `solidRouterPages(router | routes, { base? })` and `tanstackRouterPages(router)` — for code that wants the path list itself (a sitemap of its own, a hand-rolled `announcePages` call from `prerender-crawler/announce`). Both are pure: they read the router instance's public shape and import nothing from either router package.
73
+
48
74
  ## `serverFunctions(options?)`
49
75
 
50
76
  The integration has two jobs.
@@ -75,8 +101,9 @@ The client learns the posture from `import.meta.env.PRERENDER_MODE`, which the c
75
101
 
76
102
  ## Other exports
77
103
 
104
+ - `solidRouterPages(router | routes, { base? })`, `tanstackRouterPages(router)` — the static-page enumerators behind `announceRoutes`, pure and isomorphic.
78
105
  - `staticCallKey(id, args)` / `staticArtifactPath(id, args)` — the artifact key derivation, for tooling that needs to locate an artifact.
79
- - Types: `PrerenderedFunction`, `ServerFunctionsIntegration`, `ServerFunctionsIntegrationOptions`, `CaptureSink` (server).
106
+ - Types: `PrerenderedFunction`, `AnnounceableRouter`, `AnnounceRoutesOptions`, `SolidRouterLike`, `SolidRouteLike`, `TanStackRouterLike`, `ServerFunctionsIntegration`, `ServerFunctionsIntegrationOptions`, `CaptureSink` (server).
80
107
 
81
108
  ## License
82
109
 
@@ -0,0 +1,57 @@
1
+ // src/routers.ts
2
+ function tanstackRouterPages(router) {
3
+ const paths = /* @__PURE__ */ new Set();
4
+ for (const [path, route] of Object.entries(router.routesByPath)) {
5
+ if (path.split("/").some((segment) => segment.includes("$"))) continue;
6
+ const isIndex = route.fullPath.endsWith("/");
7
+ const isLeaf = !hasChildren(route.children);
8
+ if (!isIndex && !isLeaf) continue;
9
+ paths.add(normalize(path));
10
+ }
11
+ return [...paths];
12
+ }
13
+ function solidRouterPages(router, options = {}) {
14
+ const instance = isSolidRouter(router) ? router : void 0;
15
+ const routes = instance ? instance.routes : router;
16
+ const base = options.base ?? instance?.config?.base ?? "";
17
+ const paths = /* @__PURE__ */ new Set();
18
+ const walk = (route, prefix) => {
19
+ const own = route.path === void 0 ? [""] : [].concat(route.path);
20
+ for (const pattern of own) {
21
+ const full = join(prefix, pattern);
22
+ const children = route.children;
23
+ if (children === void 0) {
24
+ if (!isDynamic(full)) paths.add(normalize(full));
25
+ continue;
26
+ }
27
+ if (typeof children === "function") continue;
28
+ for (const child of [].concat(children)) walk(child, full);
29
+ }
30
+ };
31
+ for (const route of [].concat(routes)) walk(route, base);
32
+ return [...paths];
33
+ }
34
+ function isSolidRouter(value) {
35
+ if (value === null || Array.isArray(value)) return false;
36
+ return (typeof value === "function" || typeof value === "object") && "routes" in value;
37
+ }
38
+ var isDynamic = (path) => path.split("/").some((segment) => segment.startsWith(":") || segment.startsWith("*"));
39
+ function hasChildren(children) {
40
+ if (children === void 0 || children === null) return false;
41
+ if (Array.isArray(children)) return children.length > 0;
42
+ return typeof children === "object" ? Object.keys(children).length > 0 : true;
43
+ }
44
+ function join(prefix, path) {
45
+ const left = prefix.replace(/\/+$/, "");
46
+ const right = path.replace(/^\/+/, "");
47
+ return right ? `${left}/${right}` : left || "/";
48
+ }
49
+ function normalize(path) {
50
+ const trimmed = path.replace(/\/+$/, "");
51
+ return trimmed === "" ? "/" : trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
52
+ }
53
+
54
+ export {
55
+ tanstackRouterPages,
56
+ solidRouterPages
57
+ };
package/dist/client.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- import { P as PrerenderedFunction } from './shared-BcBpMOMU.js';
2
- export { s as staticArtifactPath, a as staticCallKey } from './shared-BcBpMOMU.js';
1
+ import { A as AnnounceRoutesOptions, P as PrerenderedFunction } from './routers-hMGLsbjF.js';
2
+ export { S as SolidRouteLike, a as SolidRouterLike, b as SolidRouterPagesOptions, T as TanStackRouteLike, c as TanStackRouterLike, s as solidRouterPages, d as staticArtifactPath, e as staticCallKey, t as tanstackRouterPages } from './routers-hMGLsbjF.js';
3
3
 
4
+ /** A router `announceRoutes` can read — see the server half. */
5
+ type AnnounceableRouter = unknown;
6
+ /** The client half of `announceRoutes`: there is no request here. Always false. */
7
+ declare function announceRoutes(_router: AnnounceableRouter, _options?: AnnounceRoutesOptions): boolean;
4
8
  /**
5
9
  * Declares a server function PRERENDERED: it runs at build time, during
6
10
  * prerendering, and each call's result is captured as a static JSON
@@ -34,4 +38,4 @@ export { s as staticArtifactPath, a as staticCallKey } from './shared-BcBpMOMU.j
34
38
  */
35
39
  declare function prerendered<A extends readonly unknown[], R>(fn: (...args: A) => R): PrerenderedFunction<A, Awaited<R>>;
36
40
 
37
- export { PrerenderedFunction, prerendered };
41
+ export { AnnounceRoutesOptions, type AnnounceableRouter, PrerenderedFunction, announceRoutes, prerendered };
package/dist/client.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ solidRouterPages,
3
+ tanstackRouterPages
4
+ } from "./chunk-E6I43WY5.js";
1
5
  import {
2
6
  PRERENDERED_META_KEY,
3
7
  staticArtifactPath,
@@ -14,6 +18,9 @@ import {
14
18
  isServerFunction,
15
19
  withMeta
16
20
  } from "@solidjs/web/server-functions/client";
21
+ function announceRoutes(_router, _options) {
22
+ return false;
23
+ }
17
24
  var SERVER_FUNCTION_METADATA = /* @__PURE__ */ Symbol.for("solid.ServerFunctionMetadata");
18
25
  function posture() {
19
26
  const env = import.meta.env;
@@ -60,7 +67,10 @@ function prerendered(fn) {
60
67
  return wrapped;
61
68
  }
62
69
  export {
70
+ announceRoutes,
63
71
  prerendered,
72
+ solidRouterPages,
64
73
  staticArtifactPath,
65
- staticCallKey
74
+ staticCallKey,
75
+ tanstackRouterPages
66
76
  };
@@ -0,0 +1,94 @@
1
+ /** What the server wrapper hands the sink per executed static call. */
2
+ interface CaptureSink {
3
+ capture(id: string, args: readonly unknown[], value: unknown): void | Promise<void>;
4
+ }
5
+ /**
6
+ * The artifact key of a static call: 128 bits of SHA-256 over the call's
7
+ * canonical spelling, hex-encoded. Async because hashing is
8
+ * (`crypto.subtle` is the one SHA-256 both realms share).
9
+ */
10
+ declare function staticCallKey(id: string, args: readonly unknown[]): Promise<string>;
11
+ /**
12
+ * The artifact's path relative to the static output root:
13
+ * `_static/<label>.<key>.json`. The label is a sanitized slice of the
14
+ * function id — for humans reading a build output or a network tab; the
15
+ * key alone carries the identity.
16
+ */
17
+ declare function staticArtifactPath(id: string, args: readonly unknown[]): Promise<string>;
18
+ /**
19
+ * The public shape of a prerendered reference — mirrors the runtime's
20
+ * `ServerFunction`: an async callable plus its build-stable identity.
21
+ */
22
+ interface PrerenderedFunction<A extends readonly unknown[] = unknown[], T = unknown> {
23
+ (...args: A): Promise<T>;
24
+ /** The build-stable function id. */
25
+ readonly id: string;
26
+ /** The live HTTP address the artifact stands in for (dev fallback, form actions). */
27
+ readonly url: string;
28
+ }
29
+ /** Options for `announceRoutes`. */
30
+ interface AnnounceRoutesOptions {
31
+ /** The crawl's `hintHeader`, if configured away from the default. @default "x-prerender" */
32
+ header?: string;
33
+ /** The app's base path, when passing a route tree rather than the router instance. */
34
+ base?: string;
35
+ }
36
+
37
+ /** The subset of a TanStack `Router` instance this reads. */
38
+ interface TanStackRouterLike {
39
+ /**
40
+ * Every route with a path, keyed by full path with the trailing slash
41
+ * trimmed; where a layout and its index share a path the index wins.
42
+ * Public on the router instance, built by `@tanstack/router-core`.
43
+ */
44
+ routesByPath: Record<string, TanStackRouteLike>;
45
+ }
46
+ interface TanStackRouteLike {
47
+ /** The route's full path — an index route's ends with `/`. */
48
+ fullPath: string;
49
+ children?: unknown;
50
+ }
51
+ /**
52
+ * The static pages of a TanStack Router instance. A route is a page when
53
+ * its path has no `$` segment (params `$id`, splats `$`, optional params
54
+ * `{-$id}`) and it is a leaf or an index — a layout route with children
55
+ * but no index has no page of its own (its URL renders not-found).
56
+ *
57
+ * The router must be a built instance (`createRouter({ routeTree })`): the
58
+ * generated `routeTree` alone has no full paths until the router
59
+ * initializes it.
60
+ */
61
+ declare function tanstackRouterPages(router: TanStackRouterLike): string[];
62
+ /** The subset of a Solid Router `RouteDefinition` this reads. */
63
+ interface SolidRouteLike {
64
+ /** A pattern, or several — aliases for one route. Absent on a pathless layout. */
65
+ path?: string | readonly string[];
66
+ /**
67
+ * Nested routes, or a thunk producing them lazily. Lazy children are not
68
+ * enumerated — that would load modules during a render; the crawl finds
69
+ * those pages by their links.
70
+ */
71
+ children?: SolidRouteLike | readonly SolidRouteLike[] | ((...args: never[]) => unknown);
72
+ }
73
+ /** The subset of a Solid Router `createRouter` instance this reads. */
74
+ interface SolidRouterLike {
75
+ readonly routes: SolidRouteLike | readonly SolidRouteLike[];
76
+ readonly config?: {
77
+ base?: string;
78
+ };
79
+ }
80
+ interface SolidRouterPagesOptions {
81
+ /** The app's base path, when the tree is passed without its router. */
82
+ base?: string;
83
+ }
84
+ /**
85
+ * The static pages of a Solid Router instance (or a route-definition tree).
86
+ * Paths join root to leaf; a route is a page when it is a leaf — a parent
87
+ * with children has a page only through an index child (`""` or `"/"`) —
88
+ * and its joined path has no `:param`, optional `:param?`, or `*splat`
89
+ * segment. Pathless layouts join through; each alias in a `path` array is
90
+ * its own page.
91
+ */
92
+ declare function solidRouterPages(router: SolidRouterLike | SolidRouteLike | readonly SolidRouteLike[], options?: SolidRouterPagesOptions): string[];
93
+
94
+ export { type AnnounceRoutesOptions as A, type CaptureSink as C, type PrerenderedFunction as P, type SolidRouteLike as S, type TanStackRouteLike as T, type SolidRouterLike as a, type SolidRouterPagesOptions as b, type TanStackRouterLike as c, staticArtifactPath as d, staticCallKey as e, solidRouterPages as s, tanstackRouterPages as t };
package/dist/server.d.ts CHANGED
@@ -1,6 +1,34 @@
1
- import { P as PrerenderedFunction } from './shared-BcBpMOMU.js';
2
- export { C as CaptureSink, s as staticArtifactPath, a as staticCallKey } from './shared-BcBpMOMU.js';
1
+ import { a as SolidRouterLike, S as SolidRouteLike, c as TanStackRouterLike, A as AnnounceRoutesOptions, P as PrerenderedFunction } from './routers-hMGLsbjF.js';
2
+ export { C as CaptureSink, b as SolidRouterPagesOptions, T as TanStackRouteLike, s as solidRouterPages, d as staticArtifactPath, e as staticCallKey, t as tanstackRouterPages } from './routers-hMGLsbjF.js';
3
3
 
4
+ /** A router `announceRoutes` can read: Solid Router (instance or tree) or a TanStack Router instance. */
5
+ type AnnounceableRouter = SolidRouterLike | SolidRouteLike | readonly SolidRouteLike[] | TanStackRouterLike;
6
+ /**
7
+ * Tells a prerender crawl which pages this app's router has — the server
8
+ * half. Called during a server render or request setup, it reads the
9
+ * ambient request: when the request is the crawler's, the router's static
10
+ * pages go on the response's hint header and the crawl seeds every one of
11
+ * them, linked or not. A visitor's request is untouched; on the client
12
+ * this is a no-op. Returns whether it announced.
13
+ *
14
+ * Takes either router Solid apps use — a Solid Router `createRouter`
15
+ * instance (or its route-definition tree, with `base`) or a TanStack
16
+ * Router instance — and tells them apart by shape.
17
+ *
18
+ * ```tsx
19
+ * import { announceRoutes } from "@solidjs/prerender";
20
+ * import { Router } from "./router";
21
+ *
22
+ * export default function App() {
23
+ * announceRoutes(Router);
24
+ * return <Router>{props => props.children}</Router>;
25
+ * }
26
+ * ```
27
+ *
28
+ * Dynamic routes (`/posts/:id`, `/posts/$id`) are not announced — only a
29
+ * render knows their values; the crawl finds them by their links.
30
+ */
31
+ declare function announceRoutes(router: AnnounceableRouter, options?: AnnounceRoutesOptions): boolean;
4
32
  /**
5
33
  * Declares a server function PRERENDERED — the server half. Calling the
6
34
  * reference during SSR runs the function in-process exactly like a direct
@@ -16,4 +44,4 @@ export { C as CaptureSink, s as staticArtifactPath, a as staticCallKey } from '.
16
44
  */
17
45
  declare function prerendered<A extends readonly unknown[], R>(fn: (...args: A) => R): PrerenderedFunction<A, Awaited<R>>;
18
46
 
19
- export { PrerenderedFunction, prerendered };
47
+ export { AnnounceRoutesOptions, type AnnounceableRouter, PrerenderedFunction, SolidRouteLike, SolidRouterLike, TanStackRouterLike, announceRoutes, prerendered };
package/dist/server.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ solidRouterPages,
3
+ tanstackRouterPages
4
+ } from "./chunk-E6I43WY5.js";
1
5
  import {
2
6
  CAPTURE_SINK,
3
7
  PRERENDERED_META_KEY,
@@ -12,6 +16,16 @@ import {
12
16
  getServerFunctionMetadata,
13
17
  isServerFunction
14
18
  } from "@solidjs/web/server-functions/server";
19
+ import { getRequestEvent } from "@solidjs/web";
20
+ import { announcePages } from "prerender-crawler/announce";
21
+ function announceRoutes(router, options = {}) {
22
+ const event = getRequestEvent();
23
+ if (!event?.response || event.response.committed) return false;
24
+ if (!event.request.headers.has(options.header ?? "x-prerender")) return false;
25
+ const pages = isTanStackRouter(router) ? tanstackRouterPages(router) : solidRouterPages(router, { base: options.base });
26
+ return announcePages(event.request, event.response.headers, pages, { header: options.header });
27
+ }
28
+ var isTanStackRouter = (router) => typeof router === "object" && router !== null && "routesByPath" in router;
15
29
  var SERVER_FUNCTION_METADATA = /* @__PURE__ */ Symbol.for("solid.ServerFunctionMetadata");
16
30
  function prerendered(fn) {
17
31
  if (!isServerFunction(fn)) {
@@ -36,7 +50,10 @@ function prerendered(fn) {
36
50
  return wrapped;
37
51
  }
38
52
  export {
53
+ announceRoutes,
39
54
  prerendered,
55
+ solidRouterPages,
40
56
  staticArtifactPath,
41
- staticCallKey
57
+ staticCallKey,
58
+ tanstackRouterPages
42
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/prerender",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Solid's integration for prerender-crawler: prerendered() turns server functions into build-time data captured as static artifacts, and the serverFunctions() integration captures them during the crawl and guards static builds against calls nothing prerendered.",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Carniato",
@@ -49,13 +49,15 @@
49
49
  "prerender-crawler": ">=0.1.0"
50
50
  },
51
51
  "devDependencies": {
52
+ "@solidjs/router": "^2.0.0-next.21",
52
53
  "@solidjs/web": "^2.0.0-rc.6",
54
+ "@tanstack/router-core": "^1.168.0",
53
55
  "@types/node": "^22.0.0",
54
56
  "solid-js": "^2.0.0-rc.6",
55
57
  "tsup": "^8.5.0",
56
58
  "typescript": "^5.8.0",
57
59
  "vitest": "^4.0.0",
58
- "prerender-crawler": "^0.1.0"
60
+ "prerender-crawler": "^0.2.0"
59
61
  },
60
62
  "scripts": {
61
63
  "build": "rm -rf dist && tsup",
@@ -1,30 +0,0 @@
1
- /** What the server wrapper hands the sink per executed static call. */
2
- interface CaptureSink {
3
- capture(id: string, args: readonly unknown[], value: unknown): void | Promise<void>;
4
- }
5
- /**
6
- * The artifact key of a static call: 128 bits of SHA-256 over the call's
7
- * canonical spelling, hex-encoded. Async because hashing is
8
- * (`crypto.subtle` is the one SHA-256 both realms share).
9
- */
10
- declare function staticCallKey(id: string, args: readonly unknown[]): Promise<string>;
11
- /**
12
- * The artifact's path relative to the static output root:
13
- * `_static/<label>.<key>.json`. The label is a sanitized slice of the
14
- * function id — for humans reading a build output or a network tab; the
15
- * key alone carries the identity.
16
- */
17
- declare function staticArtifactPath(id: string, args: readonly unknown[]): Promise<string>;
18
- /**
19
- * The public shape of a prerendered reference — mirrors the runtime's
20
- * `ServerFunction`: an async callable plus its build-stable identity.
21
- */
22
- interface PrerenderedFunction<A extends readonly unknown[] = unknown[], T = unknown> {
23
- (...args: A): Promise<T>;
24
- /** The build-stable function id. */
25
- readonly id: string;
26
- /** The live HTTP address the artifact stands in for (dev fallback, form actions). */
27
- readonly url: string;
28
- }
29
-
30
- export { type CaptureSink as C, type PrerenderedFunction as P, staticCallKey as a, staticArtifactPath as s };