Skip to content

Commit b0d05fb

Browse files
committed
Add typegen support for routes outside appDirectory
1 parent 6ff0bb3 commit b0d05fb

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

integration/typegen-test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs from "node:fs/promises";
22

3+
import { expect } from "@playwright/test";
34
import tsx from "dedent";
45
import * as Path from "pathe";
56

@@ -336,6 +337,57 @@ test.describe("typegen", () => {
336337
await $("pnpm typecheck");
337338
});
338339

340+
test("routes outside app dir", async ({ cwd, edit, $ }) => {
341+
// Create the subdirectories
342+
await fs.mkdir(Path.join(cwd, "app/router"), { recursive: true });
343+
await fs.mkdir(Path.join(cwd, "app/pages"), { recursive: true });
344+
345+
await edit({
346+
"react-router.config.ts": tsx`
347+
export default {
348+
appDirectory: "app/router",
349+
}
350+
`,
351+
"app/router/routes.ts": tsx`
352+
import { type RouteConfig, route } from "@react-router/dev/routes";
353+
354+
export default [
355+
route("products/:id", "../pages/product.tsx")
356+
] satisfies RouteConfig;
357+
`,
358+
"app/router/root.tsx": tsx`
359+
import { Outlet } from "react-router";
360+
361+
export default function Root() {
362+
return <Outlet />;
363+
}
364+
`,
365+
"app/pages/product.tsx": tsx`
366+
import type { Expect, Equal } from "../expect-type"
367+
import type { Route } from "./+types/product"
368+
369+
export function loader({ params }: Route.LoaderArgs) {
370+
type Test = Expect<Equal<typeof params, { id: string }>>
371+
return { planet: "world" }
372+
}
373+
374+
export default function Component({ loaderData }: Route.ComponentProps) {
375+
type Test = Expect<Equal<typeof loaderData.planet, string>>
376+
return <h1>Hello, {loaderData.planet}!</h1>
377+
}
378+
`,
379+
});
380+
await $("pnpm typecheck");
381+
382+
// Verify that the types file was generated in the correct location
383+
const annotationPath = Path.join(
384+
cwd,
385+
".react-router/types/app/pages/+types/product.ts",
386+
);
387+
const annotation = await fs.readFile(annotationPath, "utf8");
388+
expect(annotation).toContain("export namespace Route");
389+
});
390+
339391
test("matches", async ({ edit, $ }) => {
340392
await edit({
341393
"app/routes.ts": tsx`

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export function generateRoutes(ctx: Context): Array<VirtualFile> {
119119

120120
// **/+types/*.ts
121121
const allAnnotations: Array<VirtualFile> = Array.from(fileToRoutes.entries())
122-
.filter(([file]) => isInAppDirectory(ctx, file))
122+
.filter(([file]) => isInRootDirectory(ctx, file))
123123
.map(([file, routeIds]) =>
124124
getRouteAnnotations({ ctx, file, routeIds, lineages }),
125125
);
@@ -219,9 +219,9 @@ function routeModulesType(ctx: Context) {
219219
);
220220
}
221221

222-
function isInAppDirectory(ctx: Context, routeFile: string): boolean {
222+
function isInRootDirectory(ctx: Context, routeFile: string): boolean {
223223
const path = Path.resolve(ctx.config.appDirectory, routeFile);
224-
return path.startsWith(ctx.config.appDirectory);
224+
return path.startsWith(ctx.rootDirectory);
225225
}
226226

227227
function getRouteAnnotations({

0 commit comments

Comments
 (0)