@kasperrt/smolter 0.4.1 → 0.4.3
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/deployment.ts +40 -25
- package/.generated/core/environment.ts +1 -0
- package/.generated/core/navigation.ts +1 -0
- package/.generated/core/resource-types.ts +4 -0
- package/.generated/core/resources.ts +2 -0
- package/.generated/core/responses.ts +12 -0
- package/.generated/core/utils.ts +5 -2
- package/.generated/files/index.ts +5 -3
- package/.generated/preact/await-fallback.tsx +33 -0
- package/.generated/preact/await.tsx +23 -5
- package/.generated/preact/client.tsx +15 -4
- package/.generated/preact/deferred-hydration.ts +14 -1
- package/.generated/preact/document.tsx +10 -0
- package/.generated/preact/index.ts +6 -2
- package/.generated/preact/lazy-route.ts +7 -0
- package/.generated/preact/navigation.tsx +6 -0
- package/.generated/preact/root-context.ts +4 -0
- package/.generated/preact/route-hooks.tsx +12 -2
- package/.generated/preact/route-types.ts +14 -0
- package/.generated/preact/router.tsx +3 -0
- package/.generated/react/data.ts +5 -1
- package/.generated/react/index.ts +11 -1
- package/.generated/react/outlet.ts +48 -0
- package/.generated/react/resources/components.tsx +12 -0
- package/.generated/server/application.tsx +13 -0
- package/.generated/server/assets.ts +7 -0
- package/.generated/server/handler.ts +3 -0
- package/.generated/server/http.ts +2 -0
- package/.generated/server/node.ts +5 -2
- package/.generated/server/preact.ts +5 -2
- package/.generated/vite/application/lazy-files.ts +10 -17
- package/.generated/vite/index.ts +14 -0
- package/CHANGELOG.md +18 -0
- package/README.md +38 -18
- package/dist/client/deployment.js +30 -22
- package/dist/core/environment.d.ts +1 -0
- package/dist/core/navigation.d.ts +1 -0
- package/dist/core/resource-types.d.ts +4 -0
- package/dist/core/resources.d.ts +2 -0
- package/dist/core/resources.js +2 -0
- package/dist/core/responses.d.ts +12 -0
- package/dist/core/responses.js +5 -0
- package/dist/core/utils.d.ts +5 -2
- package/dist/core/utils.js +1 -2
- package/dist/files/index.js +4 -3
- package/dist/preact/await-fallback.d.ts +4 -0
- package/dist/preact/await-fallback.js +29 -0
- package/dist/preact/await.d.ts +5 -1
- package/dist/preact/await.js +22 -3
- package/dist/preact/client.d.ts +9 -1
- package/dist/preact/client.js +8 -3
- package/dist/preact/deferred-hydration.d.ts +1 -1
- package/dist/preact/deferred-hydration.js +7 -1
- package/dist/preact/document.d.ts +10 -0
- package/dist/preact/document.js +10 -1
- package/dist/preact/index.d.ts +6 -2
- package/dist/preact/lazy-route.d.ts +2 -0
- package/dist/preact/lazy-route.js +6 -0
- package/dist/preact/navigation.d.ts +6 -0
- package/dist/preact/navigation.js +2 -0
- package/dist/preact/root-context.d.ts +1 -0
- package/dist/preact/route-hooks.d.ts +10 -0
- package/dist/preact/route-hooks.js +13 -3
- package/dist/preact/route-types.d.ts +14 -0
- package/dist/preact/router.d.ts +3 -0
- package/dist/preact/router.js +3 -1
- package/dist/react/data.d.ts +5 -1
- package/dist/react/index.d.ts +6 -1
- package/dist/react/index.js +2 -1
- package/dist/react/outlet.d.ts +16 -0
- package/dist/react/outlet.js +26 -0
- package/dist/react/resources/components.d.ts +12 -0
- package/dist/react/resources/components.js +11 -0
- package/dist/server/application.d.ts +3 -0
- package/dist/server/application.js +10 -0
- package/dist/server/assets.d.ts +2 -0
- package/dist/server/assets.js +6 -0
- package/dist/server/handler.d.ts +3 -0
- package/dist/server/http.d.ts +2 -0
- package/dist/server/http.js +2 -0
- package/dist/server/node.d.ts +5 -1
- package/dist/server/preact.d.ts +5 -1
- package/dist/vite/application/lazy-files.js +5 -11
- package/dist/vite/index.js +6 -0
- package/jsr.json +1 -1
- package/package.json +6 -5
|
@@ -7,30 +7,38 @@ import type {
|
|
|
7
7
|
import type { DataResponse } from "../core/responses.ts";
|
|
8
8
|
import type { ComponentType, ReactNode } from "preact/compat";
|
|
9
9
|
|
|
10
|
+
/** Request, route parameters and cancellation signal supplied to a loader. */
|
|
10
11
|
export interface LoaderFunctionArgs extends DataArgs {
|
|
11
12
|
context?: unknown;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
/** Request, route parameters and cancellation signal supplied to an action. */
|
|
14
16
|
export type ActionFunctionArgs = LoaderFunctionArgs;
|
|
15
17
|
|
|
18
|
+
/** Loader arguments with a serverLoader callback for the server transport. */
|
|
16
19
|
export interface ClientLoaderFunctionArgs extends LoaderFunctionArgs {
|
|
17
20
|
serverLoader: <T = unknown>() => Promise<T>;
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
/** Action arguments with a serverAction callback for the server transport. */
|
|
20
24
|
export interface ClientActionFunctionArgs extends LoaderFunctionArgs {
|
|
21
25
|
serverAction: <T = unknown>() => Promise<T>;
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/** Navigation or mutation context used to decide whether loader data should refresh. */
|
|
24
29
|
export type ShouldRevalidateFunctionArgs = RevalidationArgs;
|
|
25
30
|
|
|
26
31
|
type UnwrapData<T> = T extends DataResponse<infer V> ? V : T;
|
|
27
32
|
|
|
33
|
+
/** Infer successful loader data, unwrapping promises and data responses. */
|
|
28
34
|
export type RouteData<T> = T extends (...args: never[]) => infer R
|
|
29
35
|
? UnwrapData<Exclude<Awaited<R>, Response | Error>>
|
|
30
36
|
: T;
|
|
31
37
|
|
|
38
|
+
/** A document metadata descriptor, such as a title, meta tag or structured-data record. */
|
|
32
39
|
export type MetaDescriptor = Record<string, unknown>;
|
|
33
40
|
|
|
41
|
+
/** Loader data, error, parameters and location supplied to a route metadata function. */
|
|
34
42
|
export interface MetaArgs<T> {
|
|
35
43
|
data: RouteData<T> | undefined;
|
|
36
44
|
loaderData: RouteData<T> | undefined;
|
|
@@ -39,10 +47,13 @@ export interface MetaArgs<T> {
|
|
|
39
47
|
location: URL;
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
/** Derive document metadata descriptors from the current route data and location. */
|
|
42
51
|
export type MetaFunction<T = unknown> = (args: MetaArgs<T>) => MetaDescriptor[];
|
|
43
52
|
|
|
53
|
+
/** Return link descriptors rendered in the document head. */
|
|
44
54
|
export type LinksFunction = () => Record<string, string>[];
|
|
45
55
|
|
|
56
|
+
/** Data route with optional lazy component, preload and document metadata integration. */
|
|
46
57
|
export interface AppRoute extends Route {
|
|
47
58
|
resolveComponent?: () => ComponentType | undefined;
|
|
48
59
|
preload?: () => Promise<void>;
|
|
@@ -51,6 +62,7 @@ export interface AppRoute extends Route {
|
|
|
51
62
|
meta?: MetaFunction;
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
/** Root document components, loader, metadata and revalidation policy for a Preact application. */
|
|
54
66
|
export interface RootModule {
|
|
55
67
|
/** Route identity used by targeted revalidation. Defaults to root. */
|
|
56
68
|
id?: string;
|
|
@@ -65,12 +77,14 @@ export interface RootModule {
|
|
|
65
77
|
shouldRevalidate?: (args: RevalidationArgs) => boolean;
|
|
66
78
|
}
|
|
67
79
|
|
|
80
|
+
/** Serialized root data and route snapshot used to hydrate without repeating initial loaders. */
|
|
68
81
|
export interface Hydration {
|
|
69
82
|
rootError?: boolean;
|
|
70
83
|
root: unknown;
|
|
71
84
|
snapshot: Snapshot;
|
|
72
85
|
}
|
|
73
86
|
|
|
87
|
+
/** Transform a data transport response using the current request context. */
|
|
74
88
|
export type HandleDataRequestFunction = (
|
|
75
89
|
response: Response,
|
|
76
90
|
args: LoaderFunctionArgs,
|
|
@@ -14,11 +14,13 @@ import type { AppRoute } from "./route-types.ts";
|
|
|
14
14
|
import { Routes } from "./routes.ts";
|
|
15
15
|
import { useContext, useMemo, useSyncExternalStore } from "./runtime.ts";
|
|
16
16
|
|
|
17
|
+
/** Optional children and context shared with descendants through useOutletContext. */
|
|
17
18
|
export interface OutletProps {
|
|
18
19
|
context?: unknown;
|
|
19
20
|
children?: ReactNode;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/** Render supplied children or the active route, and provide outlet context to descendants. */
|
|
22
24
|
export function Outlet({
|
|
23
25
|
context,
|
|
24
26
|
children,
|
|
@@ -37,6 +39,7 @@ export interface ApplicationProps extends ApplicationContext {
|
|
|
37
39
|
rootStore?: ReturnType<typeof createRootStore>;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
/** Render a Preact root document and active route with shared root data and application context. */
|
|
40
43
|
export function RouterApplication(
|
|
41
44
|
props: ApplicationProps,
|
|
42
45
|
): import("preact").JSX.Element {
|
package/.generated/react/data.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* React routing components, typed data hooks and forms.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
2
6
|
export type * from "../core/index.ts";
|
|
3
7
|
export {
|
|
4
8
|
createDataRouter,
|
|
@@ -14,4 +18,10 @@ export * from "./fetcher.ts";
|
|
|
14
18
|
export * from "./form.ts";
|
|
15
19
|
export * from "./hooks.ts";
|
|
16
20
|
export * from "./link.ts";
|
|
21
|
+
export {
|
|
22
|
+
Outlet,
|
|
23
|
+
type OutletProps,
|
|
24
|
+
useMatches,
|
|
25
|
+
useOutletContext,
|
|
26
|
+
} from "./outlet.ts";
|
|
17
27
|
export * from "./routes.ts";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useRouter, useSnapshot } from "./context.ts";
|
|
2
|
+
import { Routes } from "./routes.ts";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
createElement,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
useContext,
|
|
8
|
+
} from "./runtime.ts";
|
|
9
|
+
|
|
10
|
+
const OutletContext = createContext<unknown>(undefined);
|
|
11
|
+
|
|
12
|
+
/** Optional children and context shared with descendants through useOutletContext. */
|
|
13
|
+
export interface OutletProps {
|
|
14
|
+
context?: unknown;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Render the active route under a layout's shared context. */
|
|
19
|
+
export function Outlet({ context, children }: OutletProps): ReactNode {
|
|
20
|
+
const inherited = useContext(OutletContext);
|
|
21
|
+
return createElement(
|
|
22
|
+
OutletContext.Provider,
|
|
23
|
+
{ value: context ?? inherited },
|
|
24
|
+
children ??
|
|
25
|
+
createElement(Routes, {
|
|
26
|
+
fallback: createElement("p", { role: "status" }, "Loading…"),
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Read the nearest Outlet's context. */
|
|
32
|
+
export function useOutletContext<T>(): T {
|
|
33
|
+
return useContext(OutletContext) as T;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Read the active flat route match; this is not a nested layout match chain. */
|
|
37
|
+
export function useMatches(): {
|
|
38
|
+
id: string;
|
|
39
|
+
data: unknown;
|
|
40
|
+
params: import("../core/index.ts").Params;
|
|
41
|
+
}[] {
|
|
42
|
+
const router = useRouter();
|
|
43
|
+
const snapshot = useSnapshot();
|
|
44
|
+
const route = router.routes.find((item) => item.path === snapshot.path);
|
|
45
|
+
return [
|
|
46
|
+
{ id: route?.id ?? "", data: snapshot.data, params: snapshot.params },
|
|
47
|
+
];
|
|
48
|
+
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
useSyncExternalStore,
|
|
24
24
|
} from "react";
|
|
25
25
|
|
|
26
|
+
/** Resource handlers with optional React content, pending UI and retry boundary. */
|
|
26
27
|
export interface ResourceRouteModule extends ResourceModule {
|
|
27
28
|
default?: ComponentType;
|
|
28
29
|
ErrorBoundary?: ComponentType<{ retry: () => void }>;
|
|
@@ -42,6 +43,7 @@ interface RouterProviderProps extends PropsWithChildren {
|
|
|
42
43
|
router: ResourceRouter;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/** Provide a resource router and load initial matches; dispose it when unmounted. */
|
|
45
47
|
export function RouterProvider({
|
|
46
48
|
children,
|
|
47
49
|
initialMatches = EMPTY_MATCHES,
|
|
@@ -66,6 +68,7 @@ interface RouteProps extends PropsWithChildren {
|
|
|
66
68
|
routeId: string;
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
/** Provide a resource route and render its content, loading state or retry boundary. */
|
|
69
72
|
export function Route({
|
|
70
73
|
children,
|
|
71
74
|
params = {},
|
|
@@ -121,12 +124,14 @@ export function Route({
|
|
|
121
124
|
);
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
/** Read the current resource route loader result. */
|
|
124
128
|
export function useLoaderData<Data>(): Data | null {
|
|
125
129
|
const route = useCurrentRoute();
|
|
126
130
|
|
|
127
131
|
return useRouteLoaderData<Data>(route.routeId, route.params);
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
/** Read loader data for a registered resource route. */
|
|
130
135
|
export function useRouteLoaderData<Data>(
|
|
131
136
|
routeId: string,
|
|
132
137
|
params: Params = {},
|
|
@@ -136,6 +141,7 @@ export function useRouteLoaderData<Data>(
|
|
|
136
141
|
return state.loaderData as Data | null;
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
/** Read the current resource route action result. */
|
|
139
145
|
export function useActionData<Data>(): Data | null {
|
|
140
146
|
const route = useCurrentRoute();
|
|
141
147
|
const state = useRouteState(route.routeId, route.params, false);
|
|
@@ -143,6 +149,7 @@ export function useActionData<Data>(): Data | null {
|
|
|
143
149
|
return state.actionData as Data | null;
|
|
144
150
|
}
|
|
145
151
|
|
|
152
|
+
/** Read the current page navigation or submission state. */
|
|
146
153
|
export function useNavigation(): { state: NavigationState } {
|
|
147
154
|
const route = useCurrentRoute();
|
|
148
155
|
const state = useRouteState(route.routeId, route.params, false);
|
|
@@ -150,6 +157,7 @@ export function useNavigation(): { state: NavigationState } {
|
|
|
150
157
|
return { state: state.navigationState };
|
|
151
158
|
}
|
|
152
159
|
|
|
160
|
+
/** Return the resource route revalidation function. */
|
|
153
161
|
export function useRevalidator(): {
|
|
154
162
|
revalidate: () => Promise<DataResult<unknown>>;
|
|
155
163
|
state: string;
|
|
@@ -183,6 +191,7 @@ interface FormProps extends PropsWithChildren {
|
|
|
183
191
|
params?: Params;
|
|
184
192
|
}
|
|
185
193
|
|
|
194
|
+
/** Submit a resource action from a form without document navigation. */
|
|
186
195
|
export function Form({
|
|
187
196
|
action,
|
|
188
197
|
children,
|
|
@@ -208,6 +217,7 @@ export function Form({
|
|
|
208
217
|
return <FormContext.Provider value={submit}>{children}</FormContext.Provider>;
|
|
209
218
|
}
|
|
210
219
|
|
|
220
|
+
/** Return a function that submits form data through the current router. */
|
|
211
221
|
export function useSubmit(): (input?: unknown) => Promise<DataResult<unknown>> {
|
|
212
222
|
const submit = useContext(FormContext);
|
|
213
223
|
|
|
@@ -216,6 +226,7 @@ export function useSubmit(): (input?: unknown) => Promise<DataResult<unknown>> {
|
|
|
216
226
|
return submit;
|
|
217
227
|
}
|
|
218
228
|
|
|
229
|
+
/** Read or mutate a resource independently of the rendered route. */
|
|
219
230
|
export function useFetcher<Data = unknown>(
|
|
220
231
|
options: FetcherOptions = {},
|
|
221
232
|
): readonly [
|
|
@@ -312,6 +323,7 @@ export function useFetcher<Data = unknown>(
|
|
|
312
323
|
return [fetcherState, fetcherOperations] as const;
|
|
313
324
|
}
|
|
314
325
|
|
|
326
|
+
/** Read the resource router supplied by its provider. */
|
|
315
327
|
export function useRouter(): ResourceRouter {
|
|
316
328
|
const router = useContext(RouterContext);
|
|
317
329
|
|
|
@@ -9,17 +9,21 @@ import {
|
|
|
9
9
|
import type { AppRoute, RootModule } from "../preact/route-types.ts";
|
|
10
10
|
import { prepareRoutes, RouterApplication } from "../preact/router.tsx";
|
|
11
11
|
import { loadRouter } from "./index.ts";
|
|
12
|
+
import type { ComponentChildren } from "preact";
|
|
13
|
+
import { Suspense } from "preact/compat";
|
|
12
14
|
import { renderToReadableStream } from "preact-render-to-string/stream";
|
|
13
15
|
import { deferredHydration } from "./deferred-hydration.ts";
|
|
14
16
|
import { privateResponse } from "./private-response.ts";
|
|
15
17
|
import { progressiveDocument } from "./progressive-document.ts";
|
|
16
18
|
import { resolveHydration, streamDocument } from "./stream-document.ts";
|
|
17
19
|
|
|
20
|
+
/** SSR settings for CSP nonce and early deferred-data hydration. */
|
|
18
21
|
export interface RenderApplicationOptions {
|
|
19
22
|
nonce?: string;
|
|
20
23
|
earlyHydration?: boolean;
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
/** Render a Preact application response with root and route data, streaming deferred content when enabled. */
|
|
23
27
|
export async function renderApplication(
|
|
24
28
|
request: Request,
|
|
25
29
|
routes: AppRoute[],
|
|
@@ -147,6 +151,7 @@ export async function renderApplication(
|
|
|
147
151
|
entry={entry}
|
|
148
152
|
rootError={Boolean(rootError)}
|
|
149
153
|
streaming
|
|
154
|
+
deferredBoundary={options.earlyHydration ? undefined : deferredBoundary}
|
|
150
155
|
initialData={deferred?.initial}
|
|
151
156
|
document
|
|
152
157
|
/>,
|
|
@@ -181,6 +186,13 @@ export async function renderApplication(
|
|
|
181
186
|
);
|
|
182
187
|
}
|
|
183
188
|
|
|
189
|
+
function deferredBoundary(
|
|
190
|
+
children: ComponentChildren,
|
|
191
|
+
fallback: ComponentChildren,
|
|
192
|
+
) {
|
|
193
|
+
return <Suspense fallback={fallback}>{children}</Suspense>;
|
|
194
|
+
}
|
|
195
|
+
|
|
184
196
|
async function dispatch(
|
|
185
197
|
request: Request,
|
|
186
198
|
routes: AppRoute[],
|
|
@@ -250,6 +262,7 @@ async function dispatch(
|
|
|
250
262
|
return Response.json((await normalizeResult(value)) ?? null);
|
|
251
263
|
}
|
|
252
264
|
|
|
265
|
+
/** Dispatch a normalized application URL to its route loader or action and return an HTTP response. */
|
|
253
266
|
export async function dispatchRoute(
|
|
254
267
|
request: Request,
|
|
255
268
|
routes: AppRoute[],
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production route asset discovery for SSR preload hints.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
1
6
|
import { matchRoutes } from "../core/index.ts";
|
|
2
7
|
|
|
8
|
+
/** Emitted JavaScript and stylesheet URLs associated with a route. */
|
|
3
9
|
export interface RouteAssets {
|
|
4
10
|
path: string;
|
|
5
11
|
files: string[];
|
|
6
12
|
styles?: string[];
|
|
7
13
|
}
|
|
8
14
|
|
|
15
|
+
/** Collect matched-route JavaScript and stylesheet assets from the production route manifest. */
|
|
9
16
|
export function routePreloads(
|
|
10
17
|
routes: RouteAssets[],
|
|
11
18
|
pathname: string,
|
|
@@ -8,6 +8,7 @@ export interface ServerBuild {
|
|
|
8
8
|
handleDataRequest: (request: Request) => Response | Promise<Response>;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** SSR build, browser entry URL and application-scoped data endpoint for Node or Express. */
|
|
11
12
|
export interface RequestHandlerOptions {
|
|
12
13
|
build: ServerBuild;
|
|
13
14
|
/** Browser entry URL resolved from Vite's production manifest. */
|
|
@@ -16,12 +17,14 @@ export interface RequestHandlerOptions {
|
|
|
16
17
|
dataEndpoint?: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
/** Node request with optional Express protocol and original URL information. */
|
|
19
21
|
export interface ServerIncomingMessage extends IncomingMessage {
|
|
20
22
|
/** Express supplies this using the application's trust proxy policy. */
|
|
21
23
|
protocol?: string;
|
|
22
24
|
originalUrl?: string;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/** Express-compatible callback for forwarding middleware errors. */
|
|
25
28
|
export type NextHandler = (error?: unknown) => void;
|
|
26
29
|
|
|
27
30
|
/** Node/Express middleware; mount after metrics, tracing and static assets. */
|
|
@@ -3,6 +3,7 @@ import { Readable, Transform } from "node:stream";
|
|
|
3
3
|
import { pipeline } from "node:stream/promises";
|
|
4
4
|
import type { ServerIncomingMessage } from "./handler.ts";
|
|
5
5
|
|
|
6
|
+
/** Convert a Node request to a Web Request with cancellation and an optional normalized route target. */
|
|
6
7
|
export async function webRequest(
|
|
7
8
|
incoming: ServerIncomingMessage,
|
|
8
9
|
outgoing: ServerResponse,
|
|
@@ -40,6 +41,7 @@ interface StreamingResponse extends ServerResponse {
|
|
|
40
41
|
flush?: () => void;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/** Write a Web Response to Node with streaming backpressure and disconnect cancellation. */
|
|
43
45
|
export async function send(response: Response, outgoing: StreamingResponse) {
|
|
44
46
|
outgoing.statusCode = response.status;
|
|
45
47
|
response.headers.forEach((value, name) => {
|
|
@@ -87,10 +87,7 @@ export function lazyFiles(
|
|
|
87
87
|
const source = await readFile(route.file, "utf8");
|
|
88
88
|
const transformed = await transformWithOxc(source, route.file);
|
|
89
89
|
const ast = this.parse(transformed.code);
|
|
90
|
-
const fields: Record<string, string | boolean> = {
|
|
91
|
-
path: route.path,
|
|
92
|
-
dataEndpoint,
|
|
93
|
-
};
|
|
90
|
+
const fields: Record<string, string | boolean> = {};
|
|
94
91
|
|
|
95
92
|
for (const statement of ast.body) {
|
|
96
93
|
if (statement.type === "ExportDefaultDeclaration") {
|
|
@@ -120,22 +117,18 @@ export function lazyFiles(
|
|
|
120
117
|
for (const declaration of statement.declaration.declarations) {
|
|
121
118
|
if (declaration.id.type === "Identifier")
|
|
122
119
|
fields[declaration.id.name] = true;
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
declaration.id.type !== "Identifier" ||
|
|
126
|
-
declaration.init?.type !== "Literal" ||
|
|
127
|
-
typeof declaration.init.value !== "string"
|
|
128
|
-
)
|
|
129
|
-
continue;
|
|
130
|
-
|
|
131
|
-
const name = declaration.id.name;
|
|
132
|
-
|
|
133
|
-
if (name === "id") fields.id = declaration.init.value;
|
|
134
|
-
|
|
135
|
-
if (name === "routePath") fields.path = declaration.init.value;
|
|
136
120
|
}
|
|
137
121
|
}
|
|
138
122
|
|
|
123
|
+
if (fields.id || fields.routePath)
|
|
124
|
+
return this.error(
|
|
125
|
+
`File routes derive identity and paths from filenames: remove id/routePath exports from ${route.file}. Use [.] for a literal dot.`,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Export discovery records capabilities, never routing configuration.
|
|
129
|
+
fields.path = route.path;
|
|
130
|
+
fields.dataEndpoint = dataEndpoint;
|
|
131
|
+
|
|
139
132
|
const file = `${route.file.replaceAll("\\", "/")}?smolter-client`;
|
|
140
133
|
|
|
141
134
|
if (fields.default) modules.set(file, String(fields.path));
|
package/.generated/vite/index.ts
CHANGED
|
@@ -152,6 +152,20 @@ export function routerPlugin({
|
|
|
152
152
|
for (const statement of ast.body) {
|
|
153
153
|
if (statement.type !== "ExportNamedDeclaration") continue;
|
|
154
154
|
|
|
155
|
+
const declared = statement.declaration;
|
|
156
|
+
const names = statement.specifiers.map((specifier) =>
|
|
157
|
+
specifier.exported.type === "Identifier"
|
|
158
|
+
? specifier.exported.name
|
|
159
|
+
: specifier.exported.value,
|
|
160
|
+
);
|
|
161
|
+
if (declared?.type === "VariableDeclaration")
|
|
162
|
+
for (const item of declared.declarations)
|
|
163
|
+
if (item.id.type === "Identifier") names.push(item.id.name);
|
|
164
|
+
if (names.some((name) => name === "id" || name === "routePath"))
|
|
165
|
+
return this.error(
|
|
166
|
+
`File routes derive identity and paths from filenames: remove id/routePath exports from ${file}. Use [.] for a literal dot.`,
|
|
167
|
+
);
|
|
168
|
+
|
|
155
169
|
for (const specifier of statement.specifiers) {
|
|
156
170
|
const name =
|
|
157
171
|
specifier.exported.type === "Identifier"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.3
|
|
4
|
+
|
|
5
|
+
- Prefetch individual guide articles on pointer, keyboard and touch intent. Keep the shared header, sidebar and current article visible during cold guide navigation; show a quiet pending status without replacing the layout. Link the header directly to the canonical Getting started guide.
|
|
6
|
+
- Publish compact React/Preact and SSR compatibility indicators in guide navigation and detailed support information on each page.
|
|
7
|
+
- Document every public JSR entrypoint and exported symbol. Validate the generated documentation before publishing, preserving JSR-before-npm release ordering.
|
|
8
|
+
|
|
9
|
+
- Add React `Outlet`, `useMatches` and `useOutletContext`; allow Preact matches under a plain RouterProvider. Show renderer and SSR compatibility in documentation navigation and on each page. Integrated document/early-hydration APIs remain Preact-only.
|
|
10
|
+
|
|
11
|
+
- Remove the public `safeWrapAsync` utility; retain it internally for router and server error handling. Existing consumers of this export must provide their own error handling. `useMatches` and `useOutletContext` remain public Preact APIs.
|
|
12
|
+
|
|
13
|
+
## 0.4.2
|
|
14
|
+
|
|
15
|
+
- Add integrated `Await fallback` for early hydration: pending and resolved content belongs to the mounted component, so navigation, root revalidation and late settlement cannot leave abandoned content behind. Migrate early-hydrated deferred sections from outer Preact `Suspense` to this prop, including root sections. Native Preact streamed Suspense remains unsupported for this ownership contract; it is not patched. Ordinary non-early SSR still streams resolved HTML.
|
|
16
|
+
- Preserve deployment recovery ownership across overlapping page/fetcher submissions and watcher replacement, including old cleanup calls. Mutations are not replayed and pending submissions continue to prevent recovery reloads.
|
|
17
|
+
- Bound initial deferred-data hydration with one total deadline (30 seconds by default); validate integer millisecond budgets, reject outstanding promises on expiry and clear lifecycle state on disposal. This is not an inactivity timer or an immediate network-disconnect detector.
|
|
18
|
+
- Support literal dots in flat route filenames and folders: `robots[.]txt` maps to `/robots.txt`. Generated file routes reject `id` and `routePath` overrides; derive identity from their filename and use their path for targeted revalidation. Manually defined route arrays retain explicit identities.
|
|
19
|
+
- Keep the thirteen-case ownership matrix, streaming/CSP/head-preservation checks, scoped transports, deployment recovery and route HMR as production regression gates. Preserve the native Preact diagnostic separately; passing Smolter tests do not claim native Preact or Vibes/device parity.
|
|
20
|
+
|
|
3
21
|
## 0.4.1
|
|
4
22
|
|
|
5
23
|
- Invalidate pending deployment recovery when an independent fetcher submits. Prevent recovery reloads while any tracked fetcher submission is pending, without blocking ordinary client navigation. Install the watcher before creating fetchers; mountApplication already does this.
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# smolter _
|
|
2
2
|
|
|
3
3
|
<!-- bundlejs:start -->
|
|
4
|
-
[](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.3%2Freact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22react%2F*%22%5D%7D%7D&bundle)
|
|
5
|
+
[](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.4.3%2Fpreact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22preact%22%2C%22preact%2F*%22%5D%7D%7D&bundle)
|
|
6
6
|
<!-- bundlejs:end -->
|
|
7
7
|
|
|
8
8
|
A typed data router for React and Preact. **Zero runtime dependencies.**
|
|
@@ -14,9 +14,9 @@ loader; `shouldRevalidate` decides when that work is needed.
|
|
|
14
14
|
Inspired by [wouter](https://github.com/molefrog/wouter)
|
|
15
15
|
and [React Router](https://reactrouter.com/).
|
|
16
16
|
|
|
17
|
-
[Read the documentation](https://kasperrt.github.io/smolter/
|
|
17
|
+
[Read the documentation](https://kasperrt.github.io/smolter/docs/) for guides and the complete API reference. The prerendered site uses Preact and Smolter routes. Articles load individually; React examples remain available in the repository.
|
|
18
18
|
|
|
19
|
-
Start with [loaders](https://kasperrt.github.io/smolter/
|
|
19
|
+
Start with [loaders](https://kasperrt.github.io/smolter/docs/loaders/), [actions](https://kasperrt.github.io/smolter/docs/actions/), [forms and validation](https://kasperrt.github.io/smolter/docs/forms/), or [fetcher reads and mutations](https://kasperrt.github.io/smolter/docs/fetchers/). Each guide connects the handler to an actual route URL and its consuming view.
|
|
20
20
|
|
|
21
21
|
## Contents
|
|
22
22
|
|
|
@@ -123,7 +123,11 @@ route folders are ignored. `.ts`, `.tsx`, `.js`, and `.jsx` are supported.
|
|
|
123
123
|
**Only flat matching:** there are no nested outlets, inherited loaders, or layout
|
|
124
124
|
chains. A pathless group module itself is not rendered or executed. Group names
|
|
125
125
|
do not provide authentication. Static paths are ordered before parameters, then
|
|
126
|
-
splats.
|
|
126
|
+
splats. Escape a literal dot with `[.]`: `robots[.]txt.ts` and
|
|
127
|
+
`robots[.]txt/route.ts` both map to `/robots.txt`. File routes derive their
|
|
128
|
+
identity and URL from their filename; do not export `id` or `routePath`.
|
|
129
|
+
Target revalidation with the generated path (or `root` for root data).
|
|
130
|
+
Conflicting patterns and unsupported optional/bracket syntax are rejected.
|
|
127
131
|
|
|
128
132
|
For the virtual module, add this declaration to your app:
|
|
129
133
|
|
|
@@ -441,18 +445,18 @@ Choose one renderer for your application. Both expose the same routing API:
|
|
|
441
445
|
| SSR and HTTP data dispatch | `@kasperrt/smolter/react/server` | `@kasperrt/smolter/preact/server` |
|
|
442
446
|
| Vite plugin and flat-file discovery | `@kasperrt/smolter/react/vite` | `@kasperrt/smolter/preact/vite` |
|
|
443
447
|
|
|
444
|
-
`@kasperrt/smolter/utils` contains browser-safe matching and
|
|
448
|
+
`@kasperrt/smolter/utils` contains browser-safe matching and URL helpers.
|
|
445
449
|
The main renderer entries never import server or build modules. Named imports
|
|
446
450
|
are tree-shaken; optional transport is included only when you import `/client`.
|
|
447
451
|
Server entries do not import either UI renderer. Vite entries are build-time only.
|
|
448
452
|
|
|
449
453
|
**Migrating renderer subpaths:** replace `/core` imports for types, router factories, and
|
|
450
454
|
`defineRoute` with your renderer entry. Use `/utils` for `matchRoutes`,
|
|
451
|
-
|
|
455
|
+
and `internalURL`. Move `/client`, `/server`,
|
|
452
456
|
`/files`, and `/vite` imports to the corresponding renderer-prefixed path above.
|
|
453
457
|
The bare package root and old top-level subpaths are no longer exported.
|
|
454
458
|
|
|
455
|
-
See the [0.3 migration guide](https://kasperrt.github.io/smolter/
|
|
459
|
+
See the [0.3 migration guide](https://kasperrt.github.io/smolter/docs/migration-0.3/) and [changelog](./CHANGELOG.md).
|
|
456
460
|
|
|
457
461
|
**Router factory migration:** code that previously used `createRouter` with
|
|
458
462
|
loaders, actions, fetchers or revalidation must now use `createDataRouter`.
|
|
@@ -521,7 +525,7 @@ re-exports and installs server proxies. `entry.client.tsx` and `entry.server.tsx
|
|
|
521
525
|
are application-owned files wiring those manifests to hydration and SSR.
|
|
522
526
|
Manual tables can be imported directly instead.
|
|
523
527
|
|
|
524
|
-
See the [virtual routes guide](https://kasperrt.github.io/smolter/
|
|
528
|
+
See the [virtual routes guide](https://kasperrt.github.io/smolter/docs/virtual-routes)
|
|
525
529
|
for the exact contract, TypeScript declaration, and both entrypoint examples.
|
|
526
530
|
|
|
527
531
|
### Express hosting
|
|
@@ -533,7 +537,7 @@ server entry and `entry` is the browser entry URL from Vite's manifest. Do not
|
|
|
533
537
|
consume request bodies with Express body parsers before this handler.
|
|
534
538
|
The adapter has no Express dependency and forwards failures to `next(error)`.
|
|
535
539
|
|
|
536
|
-
See the [Express guide](https://kasperrt.github.io/smolter/
|
|
540
|
+
See the [Express guide](https://kasperrt.github.io/smolter/docs/express)
|
|
537
541
|
for a complete server example and streaming and hydration behavior.
|
|
538
542
|
|
|
539
543
|
## Resource subscriptions
|
|
@@ -603,12 +607,12 @@ a claim of identical features.
|
|
|
603
607
|
| `React Router: data routing subset` | 29780 |
|
|
604
608
|
| `React: routing-only named exports` | 1245 |
|
|
605
609
|
| `Preact: routing-only named exports` | 1405 |
|
|
606
|
-
| `@kasperrt/smolter/react` |
|
|
607
|
-
| `@kasperrt/smolter/preact` |
|
|
608
|
-
| `@kasperrt/smolter/react + @kasperrt/smolter/react/client` |
|
|
609
|
-
| `@kasperrt/smolter/preact + server transport` |
|
|
610
|
-
| `@kasperrt/smolter/preact + client + document (application without lazy routes)` |
|
|
611
|
-
| `@kasperrt/smolter/preact + client + document + lazy-route (file-routed application)` |
|
|
610
|
+
| `@kasperrt/smolter/react` | 3082 |
|
|
611
|
+
| `@kasperrt/smolter/preact` | 5016 |
|
|
612
|
+
| `@kasperrt/smolter/react + @kasperrt/smolter/react/client` | 4419 |
|
|
613
|
+
| `@kasperrt/smolter/preact + server transport` | 6291 |
|
|
614
|
+
| `@kasperrt/smolter/preact + client + document (application without lazy routes)` | 9398 |
|
|
615
|
+
| `@kasperrt/smolter/preact + client + document + lazy-route (file-routed application)` | 9685 |
|
|
612
616
|
| `@kasperrt/smolter/react/data (optional resource API)` | 2962 |
|
|
613
617
|
| `React: common routing/data exports` | 2761 |
|
|
614
618
|
| `Preact: common routing/data exports` | 3046 |
|
|
@@ -731,7 +735,7 @@ normally, and a session marker prevents reload loops. Development/SSR builds
|
|
|
731
735
|
disable the helper. This optional client module adds nothing to the routing-only
|
|
732
736
|
entrypoints and never replays form submissions.
|
|
733
737
|
|
|
734
|
-
See [deployment updates](https://kasperrt.github.io/smolter/
|
|
738
|
+
See [deployment updates](https://kasperrt.github.io/smolter/docs/deployments/)
|
|
735
739
|
for Express, CDN paths, TypeScript declarations and unsaved-state handling.
|
|
736
740
|
|
|
737
741
|
|
|
@@ -748,7 +752,7 @@ import { mountApplication } from "@kasperrt/smolter/preact/client";
|
|
|
748
752
|
await mountApplication(routes, root, { deployment });
|
|
749
753
|
```
|
|
750
754
|
|
|
751
|
-
Preact SSR starts root and route loaders concurrently. Return nested promises and
|
|
755
|
+
Preact SSR starts root and route loaders concurrently. Return nested promises and use `Await` with an explicit `fallback` for component-owned pending content during early hydration. The Node/Express adapter streams the response; hydration starts after the document stream finishes by default. Pass `{ earlyHydration: true }` to `renderApplication` to hydrate the shell while optional promises stream independently. Initial deferred values share a total 30-second budget after their payload is read, not an inactivity timeout or reliable disconnect detector. Settlements do not extend it; configure `mountApplication` with `{ deferredTimeout: 60_000 }` for intentionally slower data (integer milliseconds from 1 through 2147483647; invalid values throw RangeError). A broken document can emit no completion event, so this deadline rejects unfinished values without retrying requests. Root-loader failures render the root error boundary. Vite development HTML transformation still buffers the document.
|
|
752
756
|
|
|
753
757
|
Successful action redirects refresh registered root data without replaying the mutation. Loader arguments expose `reason` to distinguish initial loads, navigation, revalidation and fetcher reads without comparing browser URLs.
|
|
754
758
|
|
|
@@ -780,3 +784,19 @@ The generated route asset manifest follows static imports for each route and lea
|
|
|
780
784
|
The file-router build emits `.vite/route-assets.json` with each route's static JavaScript dependency closure and its CSS in `styles`. `routePreloads` from `@kasperrt/smolter/utils/assets` produces modulepreload hints for JavaScript and `preload; as=style; crossorigin` hints for CSS (matching Vite’s anonymous CORS mode), respecting your CDN/base URL. Set these hints on the document response or send Early Hints from your host. Stylesheet preloads fetch files; they do not apply styles. Keep the application stylesheet links and Vite's CSS handling in place. Optional dynamic imports are excluded, and unrelated routes stay lazy.
|
|
781
785
|
|
|
782
786
|
Root stylesheet URL imports such as `import stylesUrl from "./index.css?url"` are not route CSS imports. Keep rendering `<link rel="stylesheet" href={stylesUrl}>` in the root document; an empty route `styles` array is expected when all CSS is loaded this way. Smolter does not infer stylesheet use from arbitrary asset URLs. If measurements show late root CSS discovery, your host can send an explicit preload or Early Hint for that same resolved URL, with the same CORS mode as the stylesheet link. Preloading does not replace the stylesheet link. Do not reorganize CSS merely to populate the route manifest.
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
### Component-owned deferred loading
|
|
790
|
+
|
|
791
|
+
For early Preact hydration, pass pending content directly to the built-in `Await`:
|
|
792
|
+
|
|
793
|
+
```tsx
|
|
794
|
+
import { Await } from "@kasperrt/smolter/preact";
|
|
795
|
+
|
|
796
|
+
<Await resolve={data.stats} fallback={<p>Loading statistics…</p>}
|
|
797
|
+
errorElement={<p>Statistics unavailable.</p>}>
|
|
798
|
+
{(stats) => <Stats value={stats} />}
|
|
799
|
+
</Await>
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
Replace the surrounding native `Suspense` for each deferred section, including root sections. This uses ordinary component-owned fallback HTML and the existing deferred data stream, avoiding Preact 11.0.1's abandoned streamed-boundary problem. No separate entrypoint, dependency patch, DOM cleanup, extra request or polling is needed. Late results cannot update an unmounted owner. Early-mode resolved content requires JavaScript; ordinary SSR without early hydration still streams resolved HTML. Existing `Await` without `fallback` keeps its Suspense semantics and its known early-hydration limitation. This does not repair arbitrary native Suspense/lazy boundaries.
|