Skip to content

Commit 11e28a9

Browse files
Typegen server-first routes in RSC Framework Mode (#14309)
1 parent 8ea6f14 commit 11e28a9

File tree

8 files changed

+398
-42
lines changed

8 files changed

+398
-42
lines changed

integration/typegen-test.ts

Lines changed: 308 additions & 17 deletions
Large diffs are not rendered by default.

packages/react-router-dev/cli/commands.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,22 @@ export async function typegen(
247247
) {
248248
root = resolveRootDirectory(root, flags);
249249

250+
const rsc = await hasReactRouterRscPlugin({
251+
root,
252+
viteBuildOptions: {
253+
config: flags.config,
254+
mode: flags.mode,
255+
},
256+
});
257+
250258
if (flags.watch) {
251259
await preloadVite();
252260
const vite = getVite();
253261
const logger = vite.createLogger("info", { prefix: "[react-router]" });
254262

255263
await Typegen.watch(root, {
256264
mode: flags.mode ?? "development",
265+
rsc,
257266
logger,
258267
});
259268
await new Promise(() => {}); // keep alive
@@ -262,5 +271,6 @@ export async function typegen(
262271

263272
await Typegen.run(root, {
264273
mode: flags.mode ?? "production",
274+
rsc,
265275
});
266276
}

packages/react-router-dev/typegen/context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ export type Context = {
88
rootDirectory: string;
99
configLoader: ConfigLoader;
1010
config: ResolvedReactRouterConfig;
11+
rsc: boolean;
1112
};
1213

1314
export async function createContext({
1415
rootDirectory,
1516
watch,
1617
mode,
18+
rsc,
1719
}: {
1820
rootDirectory: string;
1921
watch: boolean;
2022
mode: string;
23+
rsc: boolean;
2124
}): Promise<Context> {
2225
const configLoader = await createConfigLoader({ rootDirectory, mode, watch });
2326
const configResult = await configLoader.getConfig();
@@ -32,5 +35,6 @@ export async function createContext({
3235
configLoader,
3336
rootDirectory,
3437
config,
38+
rsc,
3539
};
3640
}

packages/react-router-dev/typegen/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ function getRouteAnnotations({
274274
Babel.generate(matchesType).code +
275275
"\n\n" +
276276
ts`
277-
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
277+
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, ${ctx.rsc}>;
278278
279279
export namespace Route {
280280
// links

packages/react-router-dev/typegen/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ async function write(...files: Array<VirtualFile>) {
2929
);
3030
}
3131

32-
export async function run(rootDirectory: string, { mode }: { mode: string }) {
33-
const ctx = await createContext({ rootDirectory, mode, watch: false });
32+
export async function run(
33+
rootDirectory: string,
34+
{ mode, rsc }: { mode: string; rsc: boolean },
35+
) {
36+
const ctx = await createContext({ rootDirectory, mode, rsc, watch: false });
3437
await fs.rm(typesDirectory(ctx), { recursive: true, force: true });
3538
await write(
3639
generateFuture(ctx),
@@ -45,9 +48,9 @@ export type Watcher = {
4548

4649
export async function watch(
4750
rootDirectory: string,
48-
{ mode, logger }: { mode: string; logger?: vite.Logger },
51+
{ mode, logger, rsc }: { mode: string; logger?: vite.Logger; rsc: boolean },
4952
): Promise<Watcher> {
50-
const ctx = await createContext({ rootDirectory, mode, watch: true });
53+
const ctx = await createContext({ rootDirectory, mode, rsc, watch: true });
5154
await fs.rm(typesDirectory(ctx), { recursive: true, force: true });
5255
await write(
5356
generateFuture(ctx),

packages/react-router-dev/vite/plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
11971197
if (viteCommand === "serve") {
11981198
typegenWatcherPromise = Typegen.watch(rootDirectory, {
11991199
mode,
1200+
rsc: false,
12001201
// ignore `info` logs from typegen since they are redundant when Vite plugin logs are active
12011202
logger: vite.createLogger("warn", { prefix: "[react-router]" }),
12021203
});

packages/react-router-dev/vite/rsc/plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
267267
getRootDirectory(viteUserConfig),
268268
{
269269
mode,
270+
rsc: true,
270271
// ignore `info` logs from typegen since they are
271272
// redundant when Vite plugin logs are active
272273
logger: vite.createLogger("warn", {

packages/react-router/lib/types/route-module-annotations.ts

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,33 @@ type CreateClientActionArgs<T extends RouteInfo> = ClientDataFunctionArgs<
120120
serverAction: () => Promise<ServerDataFrom<T["module"]["action"]>>;
121121
};
122122

123-
type CreateHydrateFallbackProps<T extends RouteInfo> = {
123+
type IsServerFirstRoute<
124+
T extends RouteInfo,
125+
RSCEnabled extends boolean,
126+
> = RSCEnabled extends true
127+
? T["module"] extends { ServerComponent: Func }
128+
? true
129+
: false
130+
: false;
131+
132+
type CreateHydrateFallbackProps<
133+
T extends RouteInfo,
134+
RSCEnabled extends boolean,
135+
> = {
124136
params: T["params"];
125-
loaderData?: T["loaderData"];
126-
actionData?: T["actionData"];
127-
};
137+
} & (IsServerFirstRoute<T, RSCEnabled> extends true
138+
? {
139+
/** The data returned from the `loader` */
140+
loaderData?: ServerDataFrom<T["module"]["loader"]>;
141+
/** The data returned from the `action` following an action submission. */
142+
actionData?: ServerDataFrom<T["module"]["action"]>;
143+
}
144+
: {
145+
/** The data returned from the `loader` or `clientLoader` */
146+
loaderData?: T["loaderData"];
147+
/** The data returned from the `action` or `clientAction` following an action submission. */
148+
actionData?: T["actionData"];
149+
});
128150

129151
type Match<T extends MatchInfo> = Pretty<{
130152
id: T["id"];
@@ -142,7 +164,7 @@ type Matches<T extends Array<MatchInfo>> =
142164
? [Match<F>, ...Matches<R>]
143165
: Array<Match<MatchInfo> | undefined>;
144166

145-
type CreateComponentProps<T extends RouteInfo> = {
167+
type CreateComponentProps<T extends RouteInfo, RSCEnabled extends boolean> = {
146168
/**
147169
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route.
148170
* @example
@@ -158,15 +180,26 @@ type CreateComponentProps<T extends RouteInfo> = {
158180
* }
159181
**/
160182
params: T["params"];
161-
/** The data returned from the `loader` or `clientLoader` */
162-
loaderData: T["loaderData"];
163-
/** The data returned from the `action` or `clientAction` following an action submission. */
164-
actionData?: T["actionData"];
165183
/** An array of the current {@link https://api.reactrouter.com/v7/interfaces/react_router.UIMatch.html route matches}, including parent route matches. */
166184
matches: Matches<T["matches"]>;
167-
};
168-
169-
type CreateErrorBoundaryProps<T extends RouteInfo> = {
185+
} & (IsServerFirstRoute<T, RSCEnabled> extends true
186+
? {
187+
/** The data returned from the `loader` */
188+
loaderData: ServerDataFrom<T["module"]["loader"]>;
189+
/** The data returned from the `action` following an action submission. */
190+
actionData?: ServerDataFrom<T["module"]["action"]>;
191+
}
192+
: {
193+
/** The data returned from the `loader` or `clientLoader` */
194+
loaderData: T["loaderData"];
195+
/** The data returned from the `action` or `clientAction` following an action submission. */
196+
actionData?: T["actionData"];
197+
});
198+
199+
type CreateErrorBoundaryProps<
200+
T extends RouteInfo,
201+
RSCEnabled extends boolean,
202+
> = {
170203
/**
171204
* {@link https://reactrouter.com/start/framework/routing#dynamic-segments Dynamic route params} for the current route.
172205
* @example
@@ -183,11 +216,24 @@ type CreateErrorBoundaryProps<T extends RouteInfo> = {
183216
**/
184217
params: T["params"];
185218
error: unknown;
186-
loaderData?: T["loaderData"];
187-
actionData?: T["actionData"];
188-
};
189-
190-
export type GetAnnotations<Info extends RouteInfo> = {
219+
} & (IsServerFirstRoute<T, RSCEnabled> extends true
220+
? {
221+
/** The data returned from the `loader` */
222+
loaderData?: ServerDataFrom<T["module"]["loader"]>;
223+
/** The data returned from the `action` following an action submission. */
224+
actionData?: ServerDataFrom<T["module"]["action"]>;
225+
}
226+
: {
227+
/** The data returned from the `loader` or `clientLoader` */
228+
loaderData?: T["loaderData"];
229+
/** The data returned from the `action` or `clientAction` following an action submission. */
230+
actionData?: T["actionData"];
231+
});
232+
233+
export type GetAnnotations<
234+
Info extends RouteInfo,
235+
RSCEnabled extends boolean,
236+
> = {
191237
// links
192238
LinkDescriptors: LinkDescriptor[];
193239
LinksFunction: () => LinkDescriptor[];
@@ -220,13 +266,13 @@ export type GetAnnotations<Info extends RouteInfo> = {
220266
ClientActionArgs: CreateClientActionArgs<Info>;
221267

222268
// HydrateFallback
223-
HydrateFallbackProps: CreateHydrateFallbackProps<Info>;
269+
HydrateFallbackProps: CreateHydrateFallbackProps<Info, RSCEnabled>;
224270

225271
// default (Component)
226-
ComponentProps: CreateComponentProps<Info>;
272+
ComponentProps: CreateComponentProps<Info, RSCEnabled>;
227273

228274
// ErrorBoundary
229-
ErrorBoundaryProps: CreateErrorBoundaryProps<Info>;
275+
ErrorBoundaryProps: CreateErrorBoundaryProps<Info, RSCEnabled>;
230276
};
231277

232278
// eslint-disable-next-line @typescript-eslint/no-unused-vars

0 commit comments

Comments
 (0)