rwsdk 0.3.6 → 0.3.7-test.20250910150814
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/dist/runtime/client/client.js +1 -0
- package/dist/runtime/lib/router.d.ts +3 -1
- package/dist/runtime/lib/router.js +51 -63
- package/dist/runtime/requestInfo/types.d.ts +1 -0
- package/dist/runtime/worker.js +4 -5
- package/dist/vite/transformClientComponents.mjs +3 -0
- package/dist/vite/transformClientComponents.test.mjs +57 -0
- package/package.json +1 -1
|
@@ -20,6 +20,7 @@ export const fetchTransport = (transportContext) => {
|
|
|
20
20
|
}
|
|
21
21
|
const fetchPromise = fetch(url, {
|
|
22
22
|
method: "POST",
|
|
23
|
+
redirect: "manual",
|
|
23
24
|
body: args != null ? await encodeReply(args) : null,
|
|
24
25
|
});
|
|
25
26
|
// If there's a response handler, check the response first
|
|
@@ -17,6 +17,7 @@ export type RwContext = {
|
|
|
17
17
|
databases: Map<string, Kysely<any>>;
|
|
18
18
|
scriptsToBeLoaded: Set<string>;
|
|
19
19
|
pageRouteResolved: PromiseWithResolvers<void> | undefined;
|
|
20
|
+
actionResult?: unknown;
|
|
20
21
|
};
|
|
21
22
|
export type RouteMiddleware<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
|
|
22
23
|
type RouteFunction<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<Response>;
|
|
@@ -32,12 +33,13 @@ export type RouteDefinition<T extends RequestInfo = RequestInfo> = {
|
|
|
32
33
|
export declare function matchPath<T extends RequestInfo = RequestInfo>(routePath: string, requestPath: string): T["params"] | null;
|
|
33
34
|
export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: Route<T>[]): {
|
|
34
35
|
routes: Route<T>[];
|
|
35
|
-
handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, }: {
|
|
36
|
+
handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }: {
|
|
36
37
|
request: Request;
|
|
37
38
|
renderPage: (requestInfo: T, Page: React.FC, onError: (error: unknown) => void) => Promise<Response>;
|
|
38
39
|
getRequestInfo: () => T;
|
|
39
40
|
onError: (error: unknown) => void;
|
|
40
41
|
runWithRequestInfoOverrides: <Result>(overrides: Partial<T>, fn: () => Promise<Result>) => Promise<Result>;
|
|
42
|
+
rscActionHandler: (request: Request) => Promise<unknown>;
|
|
41
43
|
}) => Response | Promise<Response>;
|
|
42
44
|
};
|
|
43
45
|
export declare function route<T extends RequestInfo = RequestInfo>(path: string, handler: RouteHandler<T>): RouteDefinition<T>;
|
|
@@ -64,49 +64,15 @@ export function defineRoutes(routes) {
|
|
|
64
64
|
const flattenedRoutes = flattenRoutes(routes);
|
|
65
65
|
return {
|
|
66
66
|
routes: flattenedRoutes,
|
|
67
|
-
async handle({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, }) {
|
|
67
|
+
async handle({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }) {
|
|
68
68
|
const url = new URL(request.url);
|
|
69
69
|
let path = url.pathname;
|
|
70
70
|
// Must end with a trailing slash.
|
|
71
71
|
if (path !== "/" && !path.endsWith("/")) {
|
|
72
72
|
path = path + "/";
|
|
73
73
|
}
|
|
74
|
-
// Flow below; helpers are declared after the main flow for readability
|
|
75
|
-
// 1) Global middlewares: run any middleware functions encountered (Response short-circuits, element renders)
|
|
76
|
-
// 2) Route matching: skip non-matching route definitions; stop at first match
|
|
77
|
-
// 3) Route-specific middlewares: run middlewares attached to the matched route
|
|
78
|
-
// 4) Final component: render the matched route's final component (layouts apply only here)
|
|
79
|
-
for (const route of flattenedRoutes) {
|
|
80
|
-
// 1) Global middlewares (encountered before a match)
|
|
81
|
-
if (typeof route === "function") {
|
|
82
|
-
const result = await route(getRequestInfo());
|
|
83
|
-
const handled = await handleMiddlewareResult(result);
|
|
84
|
-
if (handled) {
|
|
85
|
-
return handled;
|
|
86
|
-
}
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
// 2) Route matching (skip if not matched)
|
|
90
|
-
const params = matchPath(route.path, path);
|
|
91
|
-
if (!params) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// Found a match: 3) route middlewares, then 4) final component, then stop
|
|
95
|
-
return await runWithRequestInfoOverrides({ params }, async () => {
|
|
96
|
-
const { routeMiddlewares, componentHandler } = parseHandlers(route.handler);
|
|
97
|
-
// 3) Route-specific middlewares
|
|
98
|
-
const mwHandled = await handleRouteMiddlewares(routeMiddlewares);
|
|
99
|
-
if (mwHandled) {
|
|
100
|
-
return mwHandled;
|
|
101
|
-
}
|
|
102
|
-
// 4) Final component (always last item)
|
|
103
|
-
return await handleRouteComponent(componentHandler, route.layouts || []);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
// No route matched and no middleware handled the request
|
|
107
|
-
// todo(peterp, 2025-01-28): Allow the user to define their own "not found" route.
|
|
108
|
-
return new Response("Not Found", { status: 404 });
|
|
109
74
|
// --- Helpers ---
|
|
75
|
+
// (Hoisted for readability)
|
|
110
76
|
function parseHandlers(handler) {
|
|
111
77
|
const handlers = Array.isArray(handler) ? handler : [handler];
|
|
112
78
|
const routeMiddlewares = handlers.slice(0, Math.max(handlers.length - 1, 0));
|
|
@@ -130,38 +96,60 @@ export function defineRoutes(routes) {
|
|
|
130
96
|
}
|
|
131
97
|
return undefined;
|
|
132
98
|
}
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
99
|
+
// --- Main flow ---
|
|
100
|
+
const globalMiddlewares = flattenedRoutes.filter((route) => typeof route === "function");
|
|
101
|
+
const routeDefinitions = flattenedRoutes.filter((route) => typeof route !== "function");
|
|
102
|
+
// 1. Run global middlewares
|
|
103
|
+
for (const middleware of globalMiddlewares) {
|
|
104
|
+
const result = await middleware(getRequestInfo());
|
|
105
|
+
const handled = await handleMiddlewareResult(result);
|
|
106
|
+
if (handled) {
|
|
107
|
+
return handled;
|
|
141
108
|
}
|
|
142
|
-
return undefined;
|
|
143
109
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
return await renderPage(requestInfo, WrappedComponent, onError);
|
|
110
|
+
// 2. Handle RSC actions
|
|
111
|
+
if (url.searchParams.has("__rsc_action_id")) {
|
|
112
|
+
getRequestInfo().rw.actionResult = await rscActionHandler(request);
|
|
113
|
+
}
|
|
114
|
+
// 3. Match and handle routes
|
|
115
|
+
for (const route of routeDefinitions) {
|
|
116
|
+
const params = matchPath(route.path, path);
|
|
117
|
+
if (!params) {
|
|
118
|
+
continue;
|
|
155
119
|
}
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
120
|
+
// Found a match: run route-specific middlewares, then the final component
|
|
121
|
+
return await runWithRequestInfoOverrides({ params }, async () => {
|
|
122
|
+
const { routeMiddlewares, componentHandler } = parseHandlers(route.handler);
|
|
123
|
+
// 3a. Route-specific middlewares
|
|
124
|
+
for (const mw of routeMiddlewares) {
|
|
125
|
+
const result = await mw(getRequestInfo());
|
|
126
|
+
const handled = await handleMiddlewareResult(result);
|
|
127
|
+
if (handled) {
|
|
128
|
+
return handled;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// 3b. Final component/handler
|
|
132
|
+
if (isRouteComponent(componentHandler)) {
|
|
133
|
+
const requestInfo = getRequestInfo();
|
|
134
|
+
const WrappedComponent = wrapWithLayouts(wrapHandlerToThrowResponses(componentHandler), route.layouts || [], requestInfo);
|
|
135
|
+
if (!isClientReference(componentHandler)) {
|
|
136
|
+
requestInfo.rw.pageRouteResolved = Promise.withResolvers();
|
|
137
|
+
}
|
|
138
|
+
return await renderPage(requestInfo, WrappedComponent, onError);
|
|
139
|
+
}
|
|
140
|
+
// Handle non-component final handler (e.g., returns new Response)
|
|
141
|
+
const tailResult = await componentHandler(getRequestInfo());
|
|
142
|
+
const handledTail = await handleMiddlewareResult(tailResult);
|
|
143
|
+
if (handledTail) {
|
|
144
|
+
return handledTail;
|
|
145
|
+
}
|
|
146
|
+
return new Response("Response not returned from route handler", {
|
|
147
|
+
status: 500,
|
|
148
|
+
});
|
|
163
149
|
});
|
|
164
150
|
}
|
|
151
|
+
// No route matched
|
|
152
|
+
return new Response("Not Found", { status: 404 });
|
|
165
153
|
},
|
|
166
154
|
};
|
|
167
155
|
}
|
package/dist/runtime/worker.js
CHANGED
|
@@ -33,6 +33,7 @@ export const defineApp = (routes) => {
|
|
|
33
33
|
const url = new URL(request.url);
|
|
34
34
|
const isRSCRequest = url.searchParams.has("__rsc") ||
|
|
35
35
|
request.headers.get("accept")?.includes("text/x-component");
|
|
36
|
+
const isAction = url.searchParams.has("__rsc_action_id");
|
|
36
37
|
const userHeaders = new Headers();
|
|
37
38
|
const rw = {
|
|
38
39
|
Document: DefaultDocument,
|
|
@@ -55,6 +56,7 @@ export const defineApp = (routes) => {
|
|
|
55
56
|
ctx: {},
|
|
56
57
|
rw,
|
|
57
58
|
response: userResponseInit,
|
|
59
|
+
isAction,
|
|
58
60
|
};
|
|
59
61
|
const createPageElement = (requestInfo, Page) => {
|
|
60
62
|
let pageElement;
|
|
@@ -77,11 +79,7 @@ export const defineApp = (routes) => {
|
|
|
77
79
|
status: 500,
|
|
78
80
|
});
|
|
79
81
|
}
|
|
80
|
-
|
|
81
|
-
const isRSCActionHandler = url.searchParams.has("__rsc_action_id");
|
|
82
|
-
if (isRSCActionHandler) {
|
|
83
|
-
actionResult = await rscActionHandler(request);
|
|
84
|
-
}
|
|
82
|
+
const actionResult = requestInfo.rw.actionResult;
|
|
85
83
|
const pageElement = createPageElement(requestInfo, Page);
|
|
86
84
|
const { rscPayload: shouldInjectRSCPayload } = rw;
|
|
87
85
|
let rscPayloadStream = renderToRscStream({
|
|
@@ -131,6 +129,7 @@ export const defineApp = (routes) => {
|
|
|
131
129
|
getRequestInfo: getRequestInfo,
|
|
132
130
|
runWithRequestInfoOverrides,
|
|
133
131
|
onError: reject,
|
|
132
|
+
rscActionHandler,
|
|
134
133
|
}));
|
|
135
134
|
}
|
|
136
135
|
catch (e) {
|
|
@@ -32,6 +32,9 @@ export async function transformClientComponents(code, normalizedId, ctx) {
|
|
|
32
32
|
// - local: "MyComponent" (the original name)
|
|
33
33
|
// - exported: "CustomName" (the alias name)
|
|
34
34
|
// - alias: "CustomName" (to generate MyComponent_CustomName)
|
|
35
|
+
if (!exportInfo.name) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
35
38
|
const hasAlias = exportInfo.alias && exportInfo.originalName;
|
|
36
39
|
processedExports.push({
|
|
37
40
|
local: exportInfo.originalName || exportInfo.name, // Use originalName if available
|
|
@@ -243,6 +243,63 @@ export { Slot, Slot as Root }
|
|
|
243
243
|
const Slot = registerClientReference("/test/file.tsx", "Slot");
|
|
244
244
|
const Slot_Root = registerClientReference("/test/file.tsx", "Root");
|
|
245
245
|
export { Slot, Slot_Root as Root };
|
|
246
|
+
`);
|
|
247
|
+
});
|
|
248
|
+
it("handles a large number of named exports from a single module", async () => {
|
|
249
|
+
const code = `"use client";
|
|
250
|
+
import * as React from "react";
|
|
251
|
+
const SidebarContext = React.createContext(null);
|
|
252
|
+
function SidebarProvider() { return jsx("div", {}); }
|
|
253
|
+
function useSidebar() {}
|
|
254
|
+
function Sidebar() { return jsx("div", {}); }
|
|
255
|
+
function SidebarTrigger() { return jsx("div", {}); }
|
|
256
|
+
function SidebarRail() { return jsx("div", {}); }
|
|
257
|
+
function SidebarInset() { return jsx("div", {}); }
|
|
258
|
+
function SidebarInput() { return jsx("div", {}); }
|
|
259
|
+
function SidebarHeader() { return jsx("div", {}); }
|
|
260
|
+
function SidebarFooter() { return jsx("div", {}); }
|
|
261
|
+
function SidebarSeparator() { return jsx("div", {}); }
|
|
262
|
+
function SidebarContent() { return jsx("div", {}); }
|
|
263
|
+
function SidebarGroup() { return jsx("div", {}); }
|
|
264
|
+
function SidebarGroupLabel() { return jsx("div", {}); }
|
|
265
|
+
function SidebarGroupAction() { return jsx("div", {}); }
|
|
266
|
+
function SidebarGroupContent() { return jsx("div", {}); }
|
|
267
|
+
function SidebarMenu() { return jsx("div", {}); }
|
|
268
|
+
function SidebarMenuItem() { return jsx("div", {}); }
|
|
269
|
+
function SidebarMenuButton() { return jsx("div", {}); }
|
|
270
|
+
function SidebarMenuAction() { return jsx("div", {}); }
|
|
271
|
+
function SidebarMenuBadge() { return jsx("div", {}); }
|
|
272
|
+
function SidebarMenuSkeleton() { return jsx("div", {}); }
|
|
273
|
+
function SidebarMenuSub() { return jsx("div", {}); }
|
|
274
|
+
function SidebarMenuSubItem() { return jsx("div", {}); }
|
|
275
|
+
function SidebarMenuSubButton() { return jsx("div", {}); }
|
|
276
|
+
export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, };`;
|
|
277
|
+
expect((await transform(code)) ?? "").toEqual(`import { registerClientReference } from "rwsdk/worker";
|
|
278
|
+
const Sidebar = registerClientReference("/test/file.tsx", "Sidebar");
|
|
279
|
+
const SidebarContent = registerClientReference("/test/file.tsx", "SidebarContent");
|
|
280
|
+
const SidebarFooter = registerClientReference("/test/file.tsx", "SidebarFooter");
|
|
281
|
+
const SidebarGroup = registerClientReference("/test/file.tsx", "SidebarGroup");
|
|
282
|
+
const SidebarGroupAction = registerClientReference("/test/file.tsx", "SidebarGroupAction");
|
|
283
|
+
const SidebarGroupContent = registerClientReference("/test/file.tsx", "SidebarGroupContent");
|
|
284
|
+
const SidebarGroupLabel = registerClientReference("/test/file.tsx", "SidebarGroupLabel");
|
|
285
|
+
const SidebarHeader = registerClientReference("/test/file.tsx", "SidebarHeader");
|
|
286
|
+
const SidebarInput = registerClientReference("/test/file.tsx", "SidebarInput");
|
|
287
|
+
const SidebarInset = registerClientReference("/test/file.tsx", "SidebarInset");
|
|
288
|
+
const SidebarMenu = registerClientReference("/test/file.tsx", "SidebarMenu");
|
|
289
|
+
const SidebarMenuAction = registerClientReference("/test/file.tsx", "SidebarMenuAction");
|
|
290
|
+
const SidebarMenuBadge = registerClientReference("/test/file.tsx", "SidebarMenuBadge");
|
|
291
|
+
const SidebarMenuButton = registerClientReference("/test/file.tsx", "SidebarMenuButton");
|
|
292
|
+
const SidebarMenuItem = registerClientReference("/test/file.tsx", "SidebarMenuItem");
|
|
293
|
+
const SidebarMenuSkeleton = registerClientReference("/test/file.tsx", "SidebarMenuSkeleton");
|
|
294
|
+
const SidebarMenuSub = registerClientReference("/test/file.tsx", "SidebarMenuSub");
|
|
295
|
+
const SidebarMenuSubButton = registerClientReference("/test/file.tsx", "SidebarMenuSubButton");
|
|
296
|
+
const SidebarMenuSubItem = registerClientReference("/test/file.tsx", "SidebarMenuSubItem");
|
|
297
|
+
const SidebarProvider = registerClientReference("/test/file.tsx", "SidebarProvider");
|
|
298
|
+
const SidebarRail = registerClientReference("/test/file.tsx", "SidebarRail");
|
|
299
|
+
const SidebarSeparator = registerClientReference("/test/file.tsx", "SidebarSeparator");
|
|
300
|
+
const SidebarTrigger = registerClientReference("/test/file.tsx", "SidebarTrigger");
|
|
301
|
+
const useSidebar = registerClientReference("/test/file.tsx", "useSidebar");
|
|
302
|
+
export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar };
|
|
246
303
|
`);
|
|
247
304
|
});
|
|
248
305
|
});
|