Skip to content

Commit 98b5df5

Browse files
committed
feat: add experimental codegen feature for automatic type annotations in route exports
- Introduced a new experimental feature in the Vite plugin to automatically add type annotations to route exports. - Implemented a watcher that processes file changes and modifies route files to include appropriate TypeScript types. - Updated the configuration to enable this feature and added necessary types in the code generation logic. - Enhanced existing route files with type annotations for loaders, actions, middleware, and other exports. - Added comprehensive tests to ensure the correctness of the code generation logic.
1 parent fb18475 commit 98b5df5

File tree

10 files changed

+1196
-47
lines changed

10 files changed

+1196
-47
lines changed

docs/content/03-configuration/03-general.mdx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ type ReactRouterViteConfig = {
2121
tanstackConfig?: Omit<Partial<TanStackDevtoolsConfig>, "customTrigger">
2222
tanstackClientBusConfig?: Partial<ClientEventBusConfig>
2323
tanstackViteConfig?: TanStackDevtoolsViteConfig
24+
experimental_codegen?: {
25+
enabled: boolean
26+
}
2427
}
2528
```
2629
@@ -188,6 +191,83 @@ You can find more info on TanStack specific configuration options here:
188191
https://tanstack.com/devtools/latest/docs/plugin-configuration
189192
</InfoAlert>
190193

194+
## Experimental Codegen
195+
196+
The `experimental_codegen` option enables automatic type annotation generation for your React Router route exports. When enabled, the plugin will automatically add type annotations to your route exports (loader, action, default component, etc.) when files are created or modified.
197+
198+
```ts
199+
experimental_codegen?: {
200+
enabled: boolean // Default: false
201+
}
202+
```
203+
204+
### What it does
205+
206+
When you create or edit a route file, the plugin will automatically:
207+
208+
1. Add the Route type import from `./+types/{filename}`
209+
2. Add type annotations to exported functions like `loader`, `action`, `clientLoader`, `clientAction`, `meta`, `links`, `headers`, `ErrorBoundary`, and `HydrateFallback`
210+
3. Add type annotations to the default exported component
211+
4. Add type annotations to `middleware` and `clientMiddleware` array exports
212+
213+
### Example
214+
215+
**Before (what you write):**
216+
```tsx
217+
export const loader = ({ request }) => {
218+
return { data: "hello" }
219+
}
220+
221+
export const middleware = []
222+
223+
export default function MyRoute({ loaderData }) {
224+
return <div>{loaderData.data}</div>
225+
}
226+
```
227+
228+
**After (automatically transformed):**
229+
```tsx
230+
import type { Route } from "./+types/my-route";
231+
232+
export const loader = ({ request }: Route.LoaderArgs) => {
233+
return { data: "hello" }
234+
}
235+
236+
export const middleware: Route.MiddlewareFunction[] = []
237+
238+
export default function MyRoute({ loaderData }: Route.ComponentProps) {
239+
return <div>{loaderData.data}</div>
240+
}
241+
```
242+
243+
### Configuration
244+
245+
To enable this feature, add the `experimental_codegen` option to your config:
246+
247+
```ts
248+
import { reactRouterDevTools } from "react-router-devtools";
249+
250+
export default defineConfig({
251+
plugins: [
252+
reactRouterDevTools({
253+
experimental_codegen: {
254+
enabled: true
255+
}
256+
}),
257+
reactRouter(),
258+
],
259+
});
260+
```
261+
262+
<WarningAlert title="Experimental Feature">
263+
This feature is experimental and may change in future versions. It modifies your source files directly, so make sure you have version control in place before enabling it.
264+
265+
**Notes:**
266+
- Only functions with parameters will have types added (functions with no parameters are skipped)
267+
- Exports that already have type annotations are skipped
268+
- The type import path follows React Router v7 conventions: `./+types/{filename}`
269+
</WarningAlert>
270+
191271
## Complete Configuration Example
192272

193273
Here's a complete example showing all general configuration options:
@@ -237,6 +317,11 @@ const rdtConfig = defineRdtConfig({
237317
// TanStack Vite plugin configuration (advanced)
238318
tanstackViteConfig: {
239319
// Custom Vite plugin config if needed
320+
},
321+
322+
// Experimental codegen for automatic type annotations
323+
experimental_codegen: {
324+
enabled: true
240325
}
241326
})
242327

packages/react-router-devtools/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "react-router-devtools",
33
"description": "Devtools for React Router - debug, trace, find hydration errors, catch bugs and inspect server/client data with react-router-devtools",
44
"author": "Alem Tuzlak",
5-
"version": "6.0.2",
5+
"version": "6.1.0",
66
"license": "MIT",
77
"keywords": [
88
"react-router",

packages/react-router-devtools/src/vite/plugin.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from "node:fs"
12
import type { ClientEventBusConfig, TanStackDevtoolsConfig } from "@tanstack/devtools"
23
import { devtools } from "@tanstack/devtools-vite"
34
import type { TanStackDevtoolsViteConfig } from "@tanstack/devtools-vite"
@@ -7,6 +8,7 @@ import type { DevToolsServerConfig } from "../server/config.js"
78
import { eventClient } from "../shared/event-client.js"
89
import { runner } from "./node-server.js"
910
import { processPlugins } from "./utils.js"
11+
import { addRouteTypes } from "./utils/codegen.js"
1012
import { augmentDataFetchingFunctions } from "./utils/data-functions-augment.js"
1113
import { injectRdtClient } from "./utils/inject-client.js"
1214
import { injectContext } from "./utils/inject-context.js"
@@ -156,6 +158,10 @@ type ReactRouterViteConfig = {
156158
tanstackConfig?: Omit<Partial<TanStackDevtoolsConfig>, "customTrigger">
157159
tanstackClientBusConfig?: Partial<ClientEventBusConfig>
158160
tanstackViteConfig?: TanStackDevtoolsViteConfig
161+
/** Experimental codegen feature to automatically add type annotations to route exports */
162+
experimental_codegen?: {
163+
enabled: boolean
164+
}
159165
}
160166

161167
type Route = {
@@ -311,6 +317,67 @@ export const reactRouterDevTools: (args?: ReactRouterViteConfig) => Plugin[] = (
311317
return finalCode
312318
},
313319
},
320+
// Codegen plugin - watches for file changes and adds type annotations to route exports
321+
...(args?.experimental_codegen?.enabled
322+
? [
323+
(() => {
324+
// Set to track recently processed files to avoid infinite loops
325+
const recentlyProcessed = new Set<string>()
326+
const DEBOUNCE_MS = 100
327+
328+
return {
329+
name: "react-router-devtools:codegen",
330+
apply(config) {
331+
return config.mode === "development"
332+
},
333+
async watchChange(id, change) {
334+
// Only process on create or update
335+
if (change.event !== "create" && change.event !== "update") {
336+
return
337+
}
338+
339+
// Skip if recently processed (debounce)
340+
if (recentlyProcessed.has(id)) {
341+
return
342+
}
343+
344+
// Ensure routes are loaded
345+
await ensureRoutesLoaded()
346+
347+
// Check if it's a route file
348+
const routeId = isTransformable(id)
349+
if (!routeId) {
350+
return
351+
}
352+
353+
try {
354+
// Read the file
355+
const code = await fs.promises.readFile(id, "utf-8")
356+
357+
// Transform the code
358+
const result = addRouteTypes(code, id)
359+
360+
// Only write if modifications were made
361+
if (result.modified) {
362+
// Add to recently processed set
363+
recentlyProcessed.add(id)
364+
365+
// Write the file
366+
await fs.promises.writeFile(id, result.code, "utf-8")
367+
368+
// Remove from set after debounce period
369+
setTimeout(() => {
370+
recentlyProcessed.delete(id)
371+
}, DEBOUNCE_MS)
372+
}
373+
} catch (_e) {
374+
// Silently ignore errors (invalid syntax, etc.)
375+
}
376+
},
377+
} satisfies Plugin
378+
})(),
379+
]
380+
: []),
314381
{
315382
enforce: "pre",
316383
name: "react-router-devtools:grab-port",

0 commit comments

Comments
 (0)