@kasperrt/smolter 0.4.3 → 0.4.4
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/.generated/client/hash-history.ts +27 -0
- package/.generated/client/hydration.ts +98 -0
- package/.generated/client/root-data.ts +52 -0
- package/.generated/core/prepare-routes.ts +50 -0
- package/.generated/core/presentation.ts +1 -1
- package/.generated/preact/client.tsx +1 -26
- package/.generated/preact/deferred-hydration.ts +1 -98
- package/.generated/preact/index.ts +6 -1
- package/.generated/preact/navigation.tsx +13 -5
- package/.generated/preact/root-data.ts +1 -45
- package/.generated/preact/router.tsx +2 -48
- package/.generated/react/client.tsx +130 -0
- package/.generated/react/document.tsx +235 -0
- package/.generated/react/fetcher.ts +29 -7
- package/.generated/react/hooks.ts +20 -18
- package/.generated/react/index.ts +17 -1
- package/.generated/react/lazy-route.ts +116 -0
- package/.generated/react/link.ts +1 -38
- package/.generated/react/navigation-state.ts +13 -0
- package/.generated/react/navigation.ts +145 -0
- package/.generated/react/root-context.ts +22 -0
- package/.generated/react/route-hooks.ts +159 -0
- package/.generated/react/route-types.ts +91 -0
- package/.generated/react/router.ts +50 -0
- package/.generated/react/runtime.ts +1 -0
- package/.generated/server/application.tsx +18 -253
- package/.generated/server/dispatch.ts +87 -0
- package/.generated/server/index.ts +2 -0
- package/.generated/server/react.ts +34 -0
- package/.generated/server/render-application.ts +203 -0
- package/.generated/vite/application/lazy-files.ts +2 -1
- package/.generated/vite/application/vite.ts +12 -4
- package/.generated/vite/react.ts +11 -0
- package/CHANGELOG.md +10 -0
- package/README.md +41 -41
- package/dist/client/hash-history.d.ts +2 -0
- package/dist/client/hash-history.js +26 -0
- package/dist/client/hydration.d.ts +10 -0
- package/dist/{preact/deferred-hydration.js → client/hydration.js} +1 -1
- package/dist/client/root-data.d.ts +14 -0
- package/dist/{preact → client}/root-data.js +1 -1
- package/dist/core/prepare-routes.d.ts +7 -0
- package/dist/core/prepare-routes.js +28 -0
- package/dist/core/presentation.js +1 -1
- package/dist/preact/client.js +5 -26
- package/dist/preact/deferred-hydration.d.ts +1 -10
- package/dist/preact/document.js +2 -2
- package/dist/preact/index.d.ts +1 -1
- package/dist/preact/index.js +1 -1
- package/dist/preact/navigation-state.js +1 -1
- package/dist/preact/navigation.js +7 -5
- package/dist/preact/root-data.d.ts +1 -8
- package/dist/preact/route-hooks.js +1 -1
- package/dist/preact/router.d.ts +2 -2
- package/dist/preact/router.js +2 -26
- package/dist/react/client.d.ts +20 -0
- package/dist/react/client.js +86 -0
- package/dist/react/document.d.ts +25 -0
- package/dist/react/document.js +169 -0
- package/dist/react/fetcher.d.ts +8 -4
- package/dist/react/fetcher.js +12 -1
- package/dist/react/form.js +1 -1
- package/dist/react/hooks.d.ts +7 -6
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +6 -2
- package/dist/react/lazy-route.d.ts +22 -0
- package/dist/react/lazy-route.js +57 -0
- package/dist/react/link.d.ts +1 -12
- package/dist/react/navigation-state.d.ts +2 -0
- package/dist/react/navigation-state.js +9 -0
- package/dist/react/navigation.d.ts +33 -0
- package/dist/react/navigation.js +64 -0
- package/dist/react/root-context.d.ts +14 -0
- package/dist/react/root-context.js +10 -0
- package/dist/react/route-hooks.d.ts +34 -0
- package/dist/react/route-hooks.js +89 -0
- package/dist/react/route-types.d.ts +69 -0
- package/dist/react/router.d.ts +10 -0
- package/dist/react/router.js +22 -0
- package/dist/react/runtime.d.ts +1 -1
- package/dist/react/runtime.js +1 -1
- package/dist/server/application.d.ts +4 -8
- package/dist/server/application.js +9 -156
- package/dist/server/dispatch.d.ts +5 -0
- package/dist/server/dispatch.js +50 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2 -1
- package/dist/server/preact.js +2 -1
- package/dist/server/react.d.ts +10 -0
- package/dist/server/react.js +22 -0
- package/dist/server/render-application.d.ts +31 -0
- package/dist/server/render-application.js +118 -0
- package/dist/vite/application/lazy-files.d.ts +1 -1
- package/dist/vite/application/lazy-files.js +2 -2
- package/dist/vite/application/vite.d.ts +2 -1
- package/dist/vite/application/vite.js +12 -5
- package/dist/vite/react.d.ts +4 -0
- package/dist/vite/react.js +9 -1
- package/jsr.json +10 -6
- package/package.json +18 -10
- package/.generated/core/resource-types.ts +0 -48
- package/.generated/core/resources.ts +0 -446
- package/.generated/react/data.ts +0 -29
- package/.generated/react/resources/components.tsx +0 -416
- package/dist/core/resource-types.d.ts +0 -41
- package/dist/core/resources.d.ts +0 -21
- package/dist/core/resources.js +0 -347
- package/dist/react/data.d.ts +0 -9
- package/dist/react/data.js +0 -3
- package/dist/react/link.js +0 -27
- package/dist/react/resources/components.d.ts +0 -76
- package/dist/react/resources/components.js +0 -250
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { DataArgs, RevalidationArgs, Route, Snapshot } from "../core/index.ts";
|
|
2
|
+
import type { DataResponse } from "../core/responses.ts";
|
|
3
|
+
import type { ComponentType, ReactNode } from "./runtime.ts";
|
|
4
|
+
/** Request, route parameters and cancellation signal supplied to a loader. */
|
|
5
|
+
export interface LoaderFunctionArgs extends DataArgs {
|
|
6
|
+
context?: unknown;
|
|
7
|
+
}
|
|
8
|
+
/** Request, route parameters and cancellation signal supplied to an action. */
|
|
9
|
+
export type ActionFunctionArgs = LoaderFunctionArgs;
|
|
10
|
+
/** Loader arguments with a serverLoader callback for the server transport. */
|
|
11
|
+
export interface ClientLoaderFunctionArgs extends LoaderFunctionArgs {
|
|
12
|
+
serverLoader: <T = unknown>() => Promise<T>;
|
|
13
|
+
}
|
|
14
|
+
/** Action arguments with a serverAction callback for the server transport. */
|
|
15
|
+
export interface ClientActionFunctionArgs extends LoaderFunctionArgs {
|
|
16
|
+
serverAction: <T = unknown>() => Promise<T>;
|
|
17
|
+
}
|
|
18
|
+
/** Navigation or mutation context used to decide whether loader data should refresh. */
|
|
19
|
+
export type ShouldRevalidateFunctionArgs = RevalidationArgs;
|
|
20
|
+
type UnwrapData<T> = T extends DataResponse<infer V> ? V : T;
|
|
21
|
+
/** Infer successful loader data, unwrapping promises and data responses. */
|
|
22
|
+
export type RouteData<T> = T extends (...args: never[]) => infer R ? UnwrapData<Exclude<Awaited<R>, Response | Error>> : T;
|
|
23
|
+
/** A document metadata descriptor, such as a title, meta tag or structured-data record. */
|
|
24
|
+
export type MetaDescriptor = Record<string, unknown>;
|
|
25
|
+
/** Loader data, error, parameters and location supplied to a route metadata function. */
|
|
26
|
+
export interface MetaArgs<T> {
|
|
27
|
+
data: RouteData<T> | undefined;
|
|
28
|
+
loaderData: RouteData<T> | undefined;
|
|
29
|
+
error?: unknown;
|
|
30
|
+
params: Record<string, string>;
|
|
31
|
+
location: URL;
|
|
32
|
+
}
|
|
33
|
+
/** Derive document metadata descriptors from the current route data and location. */
|
|
34
|
+
export type MetaFunction<T = unknown> = (args: MetaArgs<T>) => MetaDescriptor[];
|
|
35
|
+
/** Return link descriptors rendered in the document head. */
|
|
36
|
+
export type LinksFunction = () => Record<string, string>[];
|
|
37
|
+
/** Data route with optional lazy component, preload and document metadata integration. */
|
|
38
|
+
export interface AppRoute extends Route {
|
|
39
|
+
resolveComponent?: () => ComponentType | undefined;
|
|
40
|
+
preload?: () => Promise<void>;
|
|
41
|
+
id?: string;
|
|
42
|
+
routePath?: string;
|
|
43
|
+
meta?: MetaFunction;
|
|
44
|
+
}
|
|
45
|
+
/** Root document components, loader, metadata and revalidation policy for a React application. */
|
|
46
|
+
export interface RootModule {
|
|
47
|
+
/** Route identity used by targeted revalidation. Defaults to root. */
|
|
48
|
+
id?: string;
|
|
49
|
+
/** Optional registered route URL used to revalidate root data after mutations. */
|
|
50
|
+
loaderPath?: string;
|
|
51
|
+
ErrorBoundary?: ComponentType;
|
|
52
|
+
default: ComponentType;
|
|
53
|
+
Layout: ComponentType<{
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
}>;
|
|
56
|
+
loader?: (args: LoaderFunctionArgs) => unknown;
|
|
57
|
+
links?: LinksFunction;
|
|
58
|
+
meta?: MetaFunction;
|
|
59
|
+
shouldRevalidate?: (args: RevalidationArgs) => boolean;
|
|
60
|
+
}
|
|
61
|
+
/** Serialized root data and route snapshot used to hydrate without repeating initial loaders. */
|
|
62
|
+
export interface Hydration {
|
|
63
|
+
rootError?: boolean;
|
|
64
|
+
root: unknown;
|
|
65
|
+
snapshot: Snapshot;
|
|
66
|
+
}
|
|
67
|
+
/** Transform a data transport response using the current request context. */
|
|
68
|
+
export type HandleDataRequestFunction = (response: Response, args: LoaderFunctionArgs) => Response | Promise<Response>;
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createStore } from "../core/store.ts";
|
|
2
|
+
import { type ApplicationContext } from "./root-context.ts";
|
|
3
|
+
import { type ReactNode } from "./runtime.ts";
|
|
4
|
+
/** Root data, document components and request-scoped router for an application. */
|
|
5
|
+
export interface ApplicationProps extends ApplicationContext {
|
|
6
|
+
document?: boolean;
|
|
7
|
+
rootStore?: ReturnType<typeof createStore>;
|
|
8
|
+
}
|
|
9
|
+
/** Compose a persistent root layout with route views and observable root loader data. */
|
|
10
|
+
export declare function RouterApplication(props: ApplicationProps): ReactNode;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createElement, useMemo, useSyncExternalStore } from "./runtime.js";
|
|
2
|
+
import { RouterProvider } from "./context.js";
|
|
3
|
+
import { createStore } from "../core/store.js";
|
|
4
|
+
import { Application } from "./root-context.js";
|
|
5
|
+
import { Outlet } from "./outlet.js";
|
|
6
|
+
//#region .generated/react/router.ts
|
|
7
|
+
/** Compose a persistent root layout with route views and observable root loader data. */
|
|
8
|
+
function RouterApplication(props) {
|
|
9
|
+
const store = useMemo(() => props.rootStore ?? createStore(props.root), [props.rootStore, props.root]);
|
|
10
|
+
const root = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
11
|
+
const snapshot = useSyncExternalStore(props.router.subscribe, props.router.getSnapshot, props.router.getSnapshot);
|
|
12
|
+
const route = props.routes.find((item) => item.path === snapshot.path);
|
|
13
|
+
const Root = snapshot.error && (props.rootError || !route?.ErrorBoundary) ? props.rootModule?.ErrorBoundary ?? Outlet : props.rootModule?.default ?? Outlet;
|
|
14
|
+
const Layout = props.rootModule?.Layout;
|
|
15
|
+
const children = createElement(Root);
|
|
16
|
+
return createElement(RouterProvider, { router: props.router }, createElement(Application.Provider, { value: {
|
|
17
|
+
...props,
|
|
18
|
+
root
|
|
19
|
+
} }, props.document && Layout ? createElement(Layout, { children }) : children));
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { RouterApplication };
|
package/dist/react/runtime.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { AnchorHTMLAttributes, ComponentType, FormHTMLAttributes, MouseEvent, ReactNode, } from "react";
|
|
2
|
-
export { createContext, createElement, useContext, useEffect, useMemo, useSyncExternalStore, } from "react";
|
|
2
|
+
export { createContext, createElement, useCallback, useContext, useEffect, useMemo, useSyncExternalStore, } from "react";
|
package/dist/react/runtime.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createContext, createElement, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
1
|
+
export { createContext, createElement, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/** @jsxImportSource preact */
|
|
2
2
|
import type { AppRoute, RootModule } from "../preact/route-types.ts";
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
/** Render a Preact application response with root and route data, streaming deferred content when enabled. */
|
|
3
|
+
import { type RenderApplicationOptions } from "./render-application.ts";
|
|
4
|
+
export { dispatchRoute } from "./dispatch.ts";
|
|
5
|
+
export type { RenderApplicationOptions } from "./render-application.ts";
|
|
6
|
+
/** Render a Preact application response with root data and optional early deferred hydration. */
|
|
9
7
|
export declare function renderApplication(request: Request, routes: AppRoute[], rootModule: RootModule, entry: string, options?: RenderApplicationOptions): Promise<Response>;
|
|
10
|
-
/** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
|
|
11
|
-
export declare function dispatchRoute(request: Request, routes: AppRoute[], document?: boolean): Promise<Response>;
|
|
@@ -1,120 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { normalizeResult, resolveData } from "../core/responses.js";
|
|
5
|
-
import { RouterApplication, prepareRoutes } from "../preact/router.js";
|
|
6
|
-
import { privateResponse } from "./private-response.js";
|
|
7
|
-
import { loadRouter } from "./index.js";
|
|
8
|
-
import { deferredHydration } from "./deferred-hydration.js";
|
|
9
|
-
import { progressiveDocument } from "./progressive-document.js";
|
|
10
|
-
import { resolveHydration, streamDocument } from "./stream-document.js";
|
|
1
|
+
import { RouterApplication } from "../preact/router.js";
|
|
2
|
+
import { dispatchRoute } from "./dispatch.js";
|
|
3
|
+
import { renderDocument } from "./render-application.js";
|
|
11
4
|
import { Suspense } from "preact/compat";
|
|
12
5
|
import { jsx } from "preact/jsx-runtime";
|
|
13
6
|
import { renderToReadableStream } from "preact-render-to-string/stream";
|
|
14
7
|
//#region .generated/server/application.tsx
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
...route,
|
|
20
|
-
path: route.routePath ?? route.path
|
|
21
|
-
}));
|
|
22
|
-
const match = matchRoutes(routes, new URL(request.url).pathname);
|
|
23
|
-
if (match && !match.route.default) return dispatchRoute(request, routes, true);
|
|
24
|
-
let actionResponse;
|
|
25
|
-
let actionData;
|
|
26
|
-
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
27
|
-
actionResponse = await dispatchRoute(request, routes, true);
|
|
28
|
-
if (actionResponse.headers.has("Location") || !actionResponse.headers.get("Content-Type")?.includes("application/json")) return actionResponse;
|
|
29
|
-
actionData = await normalizeResult(await actionResponse.json());
|
|
30
|
-
}
|
|
31
|
-
const rootResult = safeWrapAsync(() => Promise.resolve().then(() => rootModule.loader?.({
|
|
32
|
-
request,
|
|
33
|
-
params: {},
|
|
34
|
-
signal: request.signal
|
|
35
|
-
})).then((value) => normalizeResult(value, false), (error) => error instanceof Response ? normalizeResult(error, false) : error instanceof Error ? error : new Error(String(error), { cause: error })));
|
|
36
|
-
let redirectResponse;
|
|
37
|
-
const prepared = prepareRoutes(routes.map((route) => ({
|
|
38
|
-
...route,
|
|
39
|
-
ErrorBoundary: route.ErrorBoundary ?? rootModule.ErrorBoundary
|
|
40
|
-
})), true).map((route) => {
|
|
41
|
-
if (!route.loader) return route;
|
|
42
|
-
const loader = route.loader;
|
|
43
|
-
return {
|
|
44
|
-
...route,
|
|
45
|
-
loader: async (args) => {
|
|
46
|
-
const value = await loader(args);
|
|
47
|
-
if (value instanceof Response && value.headers.has("Location")) redirectResponse = value;
|
|
48
|
-
return value;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
const [rootState, loadedRouter] = await Promise.all([rootResult, loadRouter(prepared, request)]);
|
|
53
|
-
let router = loadedRouter;
|
|
54
|
-
const rootError = rootState.error ?? (rootState.data instanceof Error ? rootState.data : void 0);
|
|
55
|
-
const root = rootError ? void 0 : rootState.data;
|
|
56
|
-
if (root instanceof Response && root.headers.has("Location")) {
|
|
57
|
-
router.dispose();
|
|
58
|
-
return root;
|
|
59
|
-
}
|
|
60
|
-
if (redirectResponse) {
|
|
61
|
-
router.dispose();
|
|
62
|
-
return redirectResponse;
|
|
63
|
-
}
|
|
64
|
-
if (actionResponse || rootError) {
|
|
65
|
-
const hydrationData = {
|
|
66
|
-
...router.getSnapshot(),
|
|
67
|
-
actionData: actionData instanceof Error ? void 0 : actionData,
|
|
68
|
-
...actionData instanceof Error && { error: actionData },
|
|
69
|
-
...rootError && { error: rootError }
|
|
70
|
-
};
|
|
71
|
-
router.dispose();
|
|
72
|
-
router = createDataRouter(router.routes, { hydrationData });
|
|
73
|
-
}
|
|
74
|
-
const snapshot = router.getSnapshot();
|
|
75
|
-
const hydrationData = {
|
|
76
|
-
root,
|
|
77
|
-
snapshot: {
|
|
78
|
-
...snapshot,
|
|
79
|
-
error: snapshot.error && {
|
|
80
|
-
...snapshot.error,
|
|
81
|
-
name: snapshot.error.name,
|
|
82
|
-
message: snapshot.error.message
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
...rootError && { rootError: true }
|
|
86
|
-
};
|
|
87
|
-
const deferred = options.earlyHydration ? deferredHydration(hydrationData, options.nonce) : void 0;
|
|
88
|
-
const stream = renderToReadableStream(/* @__PURE__ */ jsx(RouterApplication, {
|
|
89
|
-
router,
|
|
90
|
-
routes,
|
|
91
|
-
root,
|
|
8
|
+
/** Render a Preact application response with root data and optional early deferred hydration. */
|
|
9
|
+
function renderApplication(request, routes, rootModule, entry, options = {}) {
|
|
10
|
+
return renderDocument(request, routes, rootModule, entry, options, (props) => renderToReadableStream(/* @__PURE__ */ jsx(RouterApplication, {
|
|
11
|
+
...props,
|
|
92
12
|
rootModule,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
streaming: true,
|
|
96
|
-
deferredBoundary: options.earlyHydration ? void 0 : deferredBoundary,
|
|
97
|
-
initialData: deferred?.initial,
|
|
98
|
-
document: true
|
|
99
|
-
}), options);
|
|
100
|
-
stream.allReady.catch(() => {});
|
|
101
|
-
const headers = new Headers(actionResponse?.headers);
|
|
102
|
-
headers.delete("Content-Length");
|
|
103
|
-
headers.delete("Content-Encoding");
|
|
104
|
-
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
105
|
-
headers.set("Cache-Control", "no-store");
|
|
106
|
-
return new Response(deferred ? progressiveDocument(stream, deferred.stream, {
|
|
107
|
-
signal: request.signal,
|
|
108
|
-
dispose: () => router.dispose()
|
|
109
|
-
}) : streamDocument(stream, {
|
|
110
|
-
hydration: resolveHydration(hydrationData),
|
|
111
|
-
signal: request.signal,
|
|
112
|
-
nonce: options.nonce,
|
|
113
|
-
dispose: () => router.dispose()
|
|
114
|
-
}), {
|
|
115
|
-
status: snapshot.error ? Number("status" in snapshot.error ? snapshot.error.status : 500) : actionResponse?.status ?? 200,
|
|
116
|
-
headers
|
|
117
|
-
});
|
|
13
|
+
deferredBoundary: options.earlyHydration ? void 0 : deferredBoundary
|
|
14
|
+
}), options));
|
|
118
15
|
}
|
|
119
16
|
function deferredBoundary(children, fallback) {
|
|
120
17
|
return /* @__PURE__ */ jsx(Suspense, {
|
|
@@ -122,49 +19,5 @@ function deferredBoundary(children, fallback) {
|
|
|
122
19
|
children
|
|
123
20
|
});
|
|
124
21
|
}
|
|
125
|
-
async function dispatch(request, routes, document = false) {
|
|
126
|
-
const match = matchRoutes(routes.map((route) => ({
|
|
127
|
-
...route,
|
|
128
|
-
path: route.routePath ?? route.path
|
|
129
|
-
})), new URL(request.url).pathname);
|
|
130
|
-
if (!match) return Response.json({ error: "No route" }, { status: 404 });
|
|
131
|
-
const handler = request.method === "GET" || request.method === "HEAD" ? match.route.loader : match.route.action;
|
|
132
|
-
if (!handler) return Response.json({ error: "No handler" }, { status: 405 });
|
|
133
|
-
const value = await Promise.resolve().then(() => handler({
|
|
134
|
-
request,
|
|
135
|
-
params: match.params,
|
|
136
|
-
signal: request.signal
|
|
137
|
-
})).then((result) => result, (error) => error);
|
|
138
|
-
if (value instanceof Response) {
|
|
139
|
-
const location = value.headers.get("Location");
|
|
140
|
-
if (document) return value;
|
|
141
|
-
if (!location) {
|
|
142
|
-
if (value.ok) return value;
|
|
143
|
-
return Response.json({ __routeError: {
|
|
144
|
-
message: await value.text(),
|
|
145
|
-
status: value.status,
|
|
146
|
-
statusText: value.statusText
|
|
147
|
-
} });
|
|
148
|
-
}
|
|
149
|
-
const headers = new Headers(value.headers);
|
|
150
|
-
headers.delete("Location");
|
|
151
|
-
headers.set("X-Smolter-Redirect", location);
|
|
152
|
-
return new Response(null, { headers });
|
|
153
|
-
}
|
|
154
|
-
if (value instanceof Error) return Response.json({ __routeError: {
|
|
155
|
-
...value,
|
|
156
|
-
message: value.message
|
|
157
|
-
} });
|
|
158
|
-
if (value && typeof value === "object" && "__routeData" in value) {
|
|
159
|
-
const result = value;
|
|
160
|
-
return Response.json(await resolveData(result.value), document ? result.init : { headers: result.init.headers });
|
|
161
|
-
}
|
|
162
|
-
return Response.json(await normalizeResult(value) ?? null);
|
|
163
|
-
}
|
|
164
|
-
/** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
|
|
165
|
-
async function dispatchRoute(request, routes, document = false) {
|
|
166
|
-
const response = await dispatch(request, routes, document);
|
|
167
|
-
return document ? response : privateResponse(response);
|
|
168
|
-
}
|
|
169
22
|
//#endregion
|
|
170
23
|
export { dispatchRoute, renderApplication };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Route } from "../core/index.ts";
|
|
2
|
+
/** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
|
|
3
|
+
export declare function dispatchRoute(request: Request, routes: readonly (Route & {
|
|
4
|
+
routePath?: string;
|
|
5
|
+
})[], document?: boolean): Promise<Response>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { matchRoutes } from "../core/match.js";
|
|
2
|
+
import { normalizeResult, resolveData } from "../core/responses.js";
|
|
3
|
+
import { privateResponse } from "./private-response.js";
|
|
4
|
+
//#region .generated/server/dispatch.ts
|
|
5
|
+
async function dispatch(request, routes, document = false) {
|
|
6
|
+
const match = matchRoutes(routes.map((route) => ({
|
|
7
|
+
...route,
|
|
8
|
+
path: route.routePath ?? route.path
|
|
9
|
+
})), new URL(request.url).pathname);
|
|
10
|
+
if (!match) return Response.json({ error: "No route" }, { status: 404 });
|
|
11
|
+
const handler = request.method === "GET" || request.method === "HEAD" ? match.route.loader : match.route.action;
|
|
12
|
+
if (!handler) return Response.json({ error: "No handler" }, { status: 405 });
|
|
13
|
+
const value = await Promise.resolve().then(() => handler({
|
|
14
|
+
request,
|
|
15
|
+
params: match.params,
|
|
16
|
+
signal: request.signal
|
|
17
|
+
})).then((result) => result, (error) => error);
|
|
18
|
+
if (value instanceof Response) {
|
|
19
|
+
const location = value.headers.get("Location");
|
|
20
|
+
if (document) return value;
|
|
21
|
+
if (!location) {
|
|
22
|
+
if (value.ok) return value;
|
|
23
|
+
return Response.json({ __routeError: {
|
|
24
|
+
message: await value.text(),
|
|
25
|
+
status: value.status,
|
|
26
|
+
statusText: value.statusText
|
|
27
|
+
} });
|
|
28
|
+
}
|
|
29
|
+
const headers = new Headers(value.headers);
|
|
30
|
+
headers.delete("Location");
|
|
31
|
+
headers.set("X-Smolter-Redirect", location);
|
|
32
|
+
return new Response(null, { headers });
|
|
33
|
+
}
|
|
34
|
+
if (value instanceof Error) return Response.json({ __routeError: {
|
|
35
|
+
...value,
|
|
36
|
+
message: value.message
|
|
37
|
+
} });
|
|
38
|
+
if (value && typeof value === "object" && "__routeData" in value) {
|
|
39
|
+
const result = value;
|
|
40
|
+
return Response.json(await resolveData(result.value), document ? result.init : { headers: result.init.headers });
|
|
41
|
+
}
|
|
42
|
+
return Response.json(await normalizeResult(value) ?? null);
|
|
43
|
+
}
|
|
44
|
+
/** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
|
|
45
|
+
async function dispatchRoute(request, routes, document = false) {
|
|
46
|
+
const response = await dispatch(request, routes, document);
|
|
47
|
+
return document ? response : privateResponse(response);
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { dispatchRoute };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export declare function loadRouter(routes: readonly Route[], input: string | Req
|
|
|
8
8
|
export { serialize } from "../core/index.ts";
|
|
9
9
|
/** Dispatch a server data request with an explicit private cache policy. */
|
|
10
10
|
export declare function handleRequest(routes: readonly Route[], request: Request): Promise<Response>;
|
|
11
|
+
export { dispatchRoute } from "./dispatch.ts";
|
package/dist/server/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { matchRoutes } from "../core/match.js";
|
|
|
3
3
|
import { createDataRouter } from "../core/router.js";
|
|
4
4
|
import { serialize } from "../core/serialize.js";
|
|
5
5
|
import { privateResponse } from "./private-response.js";
|
|
6
|
+
import { dispatchRoute } from "./dispatch.js";
|
|
6
7
|
//#region .generated/server/index.ts
|
|
7
8
|
/**
|
|
8
9
|
* Request-scoped server rendering and HTTP data dispatch.
|
|
@@ -83,4 +84,4 @@ async function handleRequest(routes, request) {
|
|
|
83
84
|
return privateResponse(await dispatchRequest(routes, request));
|
|
84
85
|
}
|
|
85
86
|
//#endregion
|
|
86
|
-
export { handleRequest, loadRouter, serialize };
|
|
87
|
+
export { dispatchRoute, handleRequest, loadRouter, serialize };
|
package/dist/server/preact.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { serialize } from "../core/serialize.js";
|
|
2
|
+
import { dispatchRoute } from "./dispatch.js";
|
|
2
3
|
import { handleRequest, loadRouter } from "./index.js";
|
|
3
|
-
import {
|
|
4
|
+
import { renderApplication } from "./application.js";
|
|
4
5
|
export { dispatchRoute, handleRequest, loadRouter, renderApplication, serialize };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React SSR rendering and route data dispatch.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import type { AppRoute, RootModule } from "../react/route-types.ts";
|
|
6
|
+
import { type RenderApplicationOptions } from "./render-application.ts";
|
|
7
|
+
export * from "./index.ts";
|
|
8
|
+
export type { RenderApplicationOptions } from "./render-application.ts";
|
|
9
|
+
/** Render a React document with root and route data using React's streaming renderer. */
|
|
10
|
+
export declare function renderApplication(request: Request, routes: AppRoute[], rootModule: RootModule, entry: string, options?: RenderApplicationOptions): Promise<Response>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { serialize } from "../core/serialize.js";
|
|
2
|
+
import { RouterApplication } from "../react/router.js";
|
|
3
|
+
import { dispatchRoute } from "./dispatch.js";
|
|
4
|
+
import { handleRequest, loadRouter } from "./index.js";
|
|
5
|
+
import { renderDocument } from "./render-application.js";
|
|
6
|
+
import { createElement } from "react";
|
|
7
|
+
import * as ReactDOMServer from "react-dom/server";
|
|
8
|
+
//#region .generated/server/react.ts
|
|
9
|
+
/** Render a React document with root and route data using React's streaming renderer. */
|
|
10
|
+
async function renderApplication(request, routes, rootModule, entry, options = {}) {
|
|
11
|
+
const { renderToReadableStream } = ReactDOMServer;
|
|
12
|
+
if (!renderToReadableStream) throw new Error("renderApplication requires React DOM 19 or newer.");
|
|
13
|
+
return renderDocument(request, routes, rootModule, entry, options, (props) => renderToReadableStream(createElement(RouterApplication, {
|
|
14
|
+
...props,
|
|
15
|
+
rootModule
|
|
16
|
+
}), {
|
|
17
|
+
nonce: options.nonce,
|
|
18
|
+
signal: request.signal
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { dispatchRoute, handleRequest, loadRouter, renderApplication, serialize };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DataArgs, Route, Router } from "../core/index.ts";
|
|
2
|
+
interface ServerRoot {
|
|
3
|
+
loader?: (args: DataArgs) => unknown;
|
|
4
|
+
ErrorBoundary?: unknown;
|
|
5
|
+
}
|
|
6
|
+
export interface RenderProps {
|
|
7
|
+
router: Router;
|
|
8
|
+
routes: (Route & {
|
|
9
|
+
routePath?: string;
|
|
10
|
+
})[];
|
|
11
|
+
root: unknown;
|
|
12
|
+
entry: string;
|
|
13
|
+
rootError: boolean;
|
|
14
|
+
streaming: boolean;
|
|
15
|
+
initialData?: string;
|
|
16
|
+
document: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** SSR settings for CSP nonce and early deferred-data hydration. */
|
|
19
|
+
export interface RenderApplicationOptions {
|
|
20
|
+
nonce?: string;
|
|
21
|
+
earlyHydration?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Render a Preact application response with root and route data, streaming deferred content when enabled. */
|
|
24
|
+
export declare function renderDocument(request: Request, routes: (Route & {
|
|
25
|
+
routePath?: string;
|
|
26
|
+
})[], rootModule: ServerRoot, entry: string, options: RenderApplicationOptions, render: (props: RenderProps) => (ReadableStream<Uint8Array> & {
|
|
27
|
+
allReady: Promise<unknown>;
|
|
28
|
+
}) | Promise<ReadableStream<Uint8Array> & {
|
|
29
|
+
allReady: Promise<unknown>;
|
|
30
|
+
}>): Promise<Response>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { safeWrapAsync } from "../core/errors.js";
|
|
2
|
+
import { matchRoutes } from "../core/match.js";
|
|
3
|
+
import { createDataRouter } from "../core/router.js";
|
|
4
|
+
import { normalizeResult } from "../core/responses.js";
|
|
5
|
+
import { prepareRoutes } from "../core/prepare-routes.js";
|
|
6
|
+
import { dispatchRoute } from "./dispatch.js";
|
|
7
|
+
import { loadRouter } from "./index.js";
|
|
8
|
+
import { deferredHydration } from "./deferred-hydration.js";
|
|
9
|
+
import { progressiveDocument } from "./progressive-document.js";
|
|
10
|
+
import { resolveHydration, streamDocument } from "./stream-document.js";
|
|
11
|
+
//#region .generated/server/render-application.ts
|
|
12
|
+
/** Render a Preact application response with root and route data, streaming deferred content when enabled. */
|
|
13
|
+
async function renderDocument(request, routes, rootModule, entry, options, render) {
|
|
14
|
+
routes = routes.map((route) => ({
|
|
15
|
+
...route,
|
|
16
|
+
path: route.routePath ?? route.path
|
|
17
|
+
}));
|
|
18
|
+
const match = matchRoutes(routes, new URL(request.url).pathname);
|
|
19
|
+
if (match && !match.route.default) return dispatchRoute(request, routes, true);
|
|
20
|
+
let actionResponse;
|
|
21
|
+
let actionData;
|
|
22
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
23
|
+
actionResponse = await dispatchRoute(request, routes, true);
|
|
24
|
+
if (actionResponse.headers.has("Location") || !actionResponse.headers.get("Content-Type")?.includes("application/json")) return actionResponse;
|
|
25
|
+
actionData = await normalizeResult(await actionResponse.json());
|
|
26
|
+
}
|
|
27
|
+
const rootResult = safeWrapAsync(() => Promise.resolve().then(() => rootModule.loader?.({
|
|
28
|
+
request,
|
|
29
|
+
params: {},
|
|
30
|
+
signal: request.signal
|
|
31
|
+
})).then((value) => normalizeResult(value, false), (error) => error instanceof Response ? normalizeResult(error, false) : error instanceof Error ? error : new Error(String(error), { cause: error })));
|
|
32
|
+
let redirectResponse;
|
|
33
|
+
const prepared = prepareRoutes(routes.map((route) => ({
|
|
34
|
+
...route,
|
|
35
|
+
ErrorBoundary: route.ErrorBoundary ?? rootModule.ErrorBoundary
|
|
36
|
+
})), true).map((route) => {
|
|
37
|
+
if (!route.loader) return route;
|
|
38
|
+
const loader = route.loader;
|
|
39
|
+
return {
|
|
40
|
+
...route,
|
|
41
|
+
loader: async (args) => {
|
|
42
|
+
const value = await loader(args);
|
|
43
|
+
if (value instanceof Response && value.headers.has("Location")) redirectResponse = value;
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const [rootState, loadedRouter] = await Promise.all([rootResult, loadRouter(prepared, request)]);
|
|
49
|
+
let router = loadedRouter;
|
|
50
|
+
const rootError = rootState.error ?? (rootState.data instanceof Error ? rootState.data : void 0);
|
|
51
|
+
const root = rootError ? void 0 : rootState.data;
|
|
52
|
+
if (root instanceof Response && root.headers.has("Location")) {
|
|
53
|
+
router.dispose();
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
56
|
+
if (redirectResponse) {
|
|
57
|
+
router.dispose();
|
|
58
|
+
return redirectResponse;
|
|
59
|
+
}
|
|
60
|
+
if (actionResponse || rootError) {
|
|
61
|
+
const hydrationData = {
|
|
62
|
+
...router.getSnapshot(),
|
|
63
|
+
actionData: actionData instanceof Error ? void 0 : actionData,
|
|
64
|
+
...actionData instanceof Error && { error: actionData },
|
|
65
|
+
...rootError && { error: rootError }
|
|
66
|
+
};
|
|
67
|
+
router.dispose();
|
|
68
|
+
router = createDataRouter(router.routes, { hydrationData });
|
|
69
|
+
}
|
|
70
|
+
const snapshot = router.getSnapshot();
|
|
71
|
+
const hydrationData = {
|
|
72
|
+
root,
|
|
73
|
+
snapshot: {
|
|
74
|
+
...snapshot,
|
|
75
|
+
error: snapshot.error && {
|
|
76
|
+
...snapshot.error,
|
|
77
|
+
name: snapshot.error.name,
|
|
78
|
+
message: snapshot.error.message
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
...rootError && { rootError: true }
|
|
82
|
+
};
|
|
83
|
+
const deferred = options.earlyHydration ? deferredHydration(hydrationData, options.nonce) : void 0;
|
|
84
|
+
const stream = await Promise.resolve().then(() => render({
|
|
85
|
+
router,
|
|
86
|
+
routes,
|
|
87
|
+
root,
|
|
88
|
+
entry,
|
|
89
|
+
rootError: Boolean(rootError),
|
|
90
|
+
streaming: true,
|
|
91
|
+
initialData: deferred?.initial,
|
|
92
|
+
document: true
|
|
93
|
+
})).catch(async (error) => {
|
|
94
|
+
router.dispose();
|
|
95
|
+
await deferred?.stream.cancel();
|
|
96
|
+
throw error;
|
|
97
|
+
});
|
|
98
|
+
stream.allReady.catch(() => {});
|
|
99
|
+
const headers = new Headers(actionResponse?.headers);
|
|
100
|
+
headers.delete("Content-Length");
|
|
101
|
+
headers.delete("Content-Encoding");
|
|
102
|
+
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
103
|
+
headers.set("Cache-Control", "no-store");
|
|
104
|
+
return new Response(deferred ? progressiveDocument(stream, deferred.stream, {
|
|
105
|
+
signal: request.signal,
|
|
106
|
+
dispose: () => router.dispose()
|
|
107
|
+
}) : streamDocument(stream, {
|
|
108
|
+
hydration: resolveHydration(hydrationData),
|
|
109
|
+
signal: request.signal,
|
|
110
|
+
nonce: options.nonce,
|
|
111
|
+
dispose: () => router.dispose()
|
|
112
|
+
}), {
|
|
113
|
+
status: snapshot.error ? Number("status" in snapshot.error ? snapshot.error.status : 500) : actionResponse?.status ?? 200,
|
|
114
|
+
headers
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { renderDocument };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type Plugin } from "vite";
|
|
2
|
-
export declare function lazyFiles(directory: string, dataEndpoint?: string): Plugin;
|
|
2
|
+
export declare function lazyFiles(directory: string, dataEndpoint?: string, framework?: "react" | "preact"): Plugin;
|
|
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { transformWithOxc } from "vite";
|
|
5
5
|
//#region .generated/vite/application/lazy-files.ts
|
|
6
|
-
function lazyFiles(directory, dataEndpoint = "/_smolter") {
|
|
6
|
+
function lazyFiles(directory, dataEndpoint = "/_smolter", framework = "preact") {
|
|
7
7
|
let root;
|
|
8
8
|
const modules = /* @__PURE__ */ new Map();
|
|
9
9
|
return {
|
|
@@ -81,7 +81,7 @@ function lazyFiles(directory, dataEndpoint = "/_smolter") {
|
|
|
81
81
|
if (fields.default) modules.set(file, String(fields.path));
|
|
82
82
|
definitions.push(`lazyRoute(${JSON.stringify(fields)}, () => import(${JSON.stringify(file)}))`);
|
|
83
83
|
}
|
|
84
|
-
return [
|
|
84
|
+
return [`import { lazyRoute } from "@kasperrt/smolter/${framework}/lazy-route";`, `export default [${definitions.join(",\n")}];`].join("\n");
|
|
85
85
|
}
|
|
86
86
|
};
|
|
87
87
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Plugin, UserConfig } from "vite";
|
|
2
2
|
export interface ApplicationOptions {
|
|
3
3
|
directory?: string;
|
|
4
|
+
framework?: "react" | "preact";
|
|
4
5
|
dataEndpoint?: string;
|
|
5
6
|
ssr?: boolean;
|
|
6
7
|
splitRoutes?: boolean;
|
|
7
8
|
resourceRequest?: (url: string) => boolean;
|
|
8
9
|
}
|
|
9
10
|
export declare const preactConfig: UserConfig;
|
|
10
|
-
export declare function applicationPlugins({ directory, dataEndpoint, ssr, splitRoutes, resourceRequest, }?: ApplicationOptions): Plugin[];
|
|
11
|
+
export declare function applicationPlugins({ directory, framework, dataEndpoint, ssr, splitRoutes, resourceRequest, }?: ApplicationOptions): Plugin[];
|