@troyio/vite-file-router 0.3.2 → 0.4.1

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/README.md CHANGED
@@ -249,6 +249,10 @@ export default function DashboardPage() {
249
249
  }
250
250
  ```
251
251
 
252
+ To get the type from the loader module instead of writing it by hand, use
253
+ `useRouteData` from `virtual:route-helpers`. See
254
+ [Typed params and loader data](#typed-params-and-loader-data).
255
+
252
256
  The authentication and mutation functions above are application code. A
253
257
  client-side route guard controls navigation and presentation; it is not a
254
258
  security boundary. Protect data and mutations in the server/API layer as well,
@@ -322,10 +326,53 @@ export default defineConfig({
322
326
  });
323
327
  ```
324
328
 
325
- Open the **Routes** dock to browse the nested route tree, filter by path or
326
- source file, and inspect parameters and special files. The panel refreshes its
327
- manifest automatically while the dev server is running. Vite DevTools must be
328
- enabled; route-tree inspection is development-only.
329
+ Open the **Routes** dock to use the panel. It's development-only and needs
330
+ Vite DevTools enabled. In the panel you can:
331
+
332
+ - **Browse the route tree.** Each route shows its URL segment, full path, and
333
+ badges for its files: page, layout, loading, error, not-found (`404`),
334
+ loader, and action. You can filter by path, param, or file, collapse
335
+ branches, and move between routes with the arrow keys.
336
+ - **See route details.** Select a route to see its params and files.
337
+ - **Open files in your editor.** Click any file path. This uses Vite's built-in
338
+ `/__open-in-editor` endpoint, so it opens the editor Vite detects or the one
339
+ named in `LAUNCH_EDITOR`.
340
+ - **Test a URL.** Enter a path such as `/users/42?tab=posts`. The panel shows
341
+ the matched routes, the params taken from the URL, and the page or
342
+ not-found page that renders. It also lists the layouts, loaders, loading
343
+ fallback, and error boundary that apply. Matching uses React Router's
344
+ `matchRoutes`, so the result agrees with the app. The matched routes are
345
+ highlighted in the tree.
346
+ - **Spot route conflicts.** Pages that resolve to the same URL pattern are
347
+ listed at the top, with links to their files.
348
+ - **Follow the current page.** The **Current page** card shows the URL your
349
+ app is on, its params, and the page that renders it. The matched routes get
350
+ a green dot in the tree, and the card updates as the app navigates. With
351
+ several app tabs open, pick which one to follow. When a tab closes, it drops
352
+ out of the list.
353
+ - **See loader and action calls.** The **Timeline** tab lists each loader and
354
+ action call your app makes, newest first. Each entry shows the route, URL,
355
+ start time, duration, and outcome: returned data, a redirect with its status
356
+ and location, a returned response, or a thrown error. Calls of 500 ms or
357
+ more are highlighted. It also shows each `shouldRevalidate` decision, so
358
+ you can see when a loader was skipped. You can filter by call type or text,
359
+ and expand an entry to see a short preview of returned data and a link to
360
+ the loader or action file. The dev server keeps the last 200 calls, so a
361
+ panel opened later still sees recent history.
362
+
363
+ The panel updates on its own when route files are added, removed, or renamed.
364
+ The dev server pushes changes over a Server-Sent Events stream instead of the
365
+ panel polling. If the dev server restarts, the panel reconnects by itself. The
366
+ refresh button reloads the data by hand.
367
+
368
+ The current page and the timeline come from a small client that
369
+ `virtual:routes` includes on the dev server only. It reports the app's URL and
370
+ wraps the generated loader, action, and `shouldRevalidate` handlers. It sends
371
+ these over Vite's HMR connection. Handler results pass through unchanged, and
372
+ only a short text preview of returned data is sent. It tracks navigation by
373
+ wrapping `history.pushState` and `history.replaceState` and listening for
374
+ `popstate`, and it also supports hash URLs (`#/path`). Production builds
375
+ include none of this code.
329
376
 
330
377
  ### Typed route helpers
331
378
 
@@ -360,6 +407,106 @@ parameters are rejected at runtime. Routes whose optional segment overlaps
360
407
  another page's URL pattern are reported as conflicts. The generated
361
408
  `routePatterns` export contains the discovered page paths.
362
409
 
410
+ ### Generated route types
411
+
412
+ In TypeScript projects, the plugin writes `src/route-types.d.ts` when the dev
413
+ server starts and at the start of each build. It counts a project as
414
+ TypeScript when the Vite project root has a `tsconfig.json` or
415
+ `jsconfig.json`. JavaScript projects without either file get no extra file.
416
+
417
+ The plugin rewrites the file whenever route files are added, removed, or
418
+ renamed, and leaves it alone when nothing changed. The file lists every
419
+ discovered page pattern and loader module. With it in your TypeScript project,
420
+ `routePath`, `routePatterns`, and the hooks below only accept routes that
421
+ exist, and your editor autocompletes them:
422
+
423
+ ```ts
424
+ routePath("/users/:userId", { userId }); // OK
425
+ routePath("/usres/:userId", { userId }); // Type error: unknown route pattern
426
+ ```
427
+
428
+ The `dts` option controls this. It defaults to `"auto"`, which is the behavior
429
+ above. A path, relative to the Vite project root and ending in `.d.ts`, always
430
+ writes the file there, even without a TypeScript config. `false` never writes
431
+ it:
432
+
433
+ ```ts
434
+ plugins: [routes({ dts: "types/route-types.d.ts" })];
435
+ plugins: [routes({ dts: false })];
436
+ ```
437
+
438
+ If your `tsconfig.json` isn't in the Vite project root, for example when Vite's
439
+ `root` points at a subfolder, set an explicit path.
440
+
441
+ Without the file, route patterns are typed as `string` and loader data as
442
+ `unknown`, the same as before. You can commit the file so types work before
443
+ the first dev run, or add it to `.gitignore`.
444
+
445
+ ### Typed params and loader data
446
+
447
+ `virtual:route-helpers` also exports two hooks that wrap React Router's hooks
448
+ with types taken from the route tree:
449
+
450
+ - `useRouteParams(pattern)` returns `useParams()`, typed for that pattern.
451
+ Required params are `string`, optional params are `string | undefined`, and
452
+ a splat is a `string` under the `"*"` key.
453
+ - `useRouteData(key)` returns `useRouteLoaderData()`, typed from the loader
454
+ module. The key is either a page pattern, which reads the loader in that
455
+ page's directory, or a loader route ID such as
456
+ `"file-route:src/pages/users/[userId]"`, which can read any matched loader,
457
+ including a parent's. It returns `undefined` when that route isn't matched,
458
+ just like React Router's hook.
459
+
460
+ ```tsx
461
+ // src/pages/users/[userId]/loader.ts
462
+ import type { LoaderFunctionArgs } from "react-router";
463
+
464
+ export async function loader({ params }: LoaderFunctionArgs) {
465
+ return { name: await getUserName(params.userId!) };
466
+ }
467
+
468
+ // src/pages/users/[userId]/page.tsx
469
+ import { useRouteData, useRouteParams } from "virtual:route-helpers";
470
+
471
+ export default function UserPage() {
472
+ const { userId } = useRouteParams("/users/:userId"); // { userId: string }
473
+ const data = useRouteData("/users/:userId"); // { name: string } | undefined
474
+ return <h1>{data?.name ?? userId}</h1>;
475
+ }
476
+ ```
477
+
478
+ The `RouteParams<"/users/:userId">`, `RouteLoaderData<key>`, `RoutePattern`,
479
+ and `RouteLoaderKey` types are exported from `@troyio/vite-file-router` for
480
+ your own helpers. Loader data is typed the same way as React Router's
481
+ `useRouteLoaderData<typeof loader>()`. JavaScript loader modules are typed as
482
+ `any`. Both hooks throw if they're passed a pattern or key the plugin didn't
483
+ discover.
484
+
485
+ ### Route preloading
486
+
487
+ `virtual:routes` exports `preloadRoute(to, basename?)`. It matches a URL
488
+ against the route tree and imports the page, layout, and loader modules of
489
+ every matched route, so the next navigation there doesn't wait for those
490
+ downloads. Error and action modules are left out, since they're only needed
491
+ after an error or a form submission. Importing modules doesn't run loaders.
492
+ Each module downloads once, however often you call it. The promise resolves to
493
+ `false` when no route matches the URL.
494
+
495
+ Call it when a user is likely to navigate, such as on link hover or focus:
496
+
497
+ ```tsx
498
+ import { Link, type LinkProps } from "react-router";
499
+ import { preloadRoute } from "virtual:routes";
500
+
501
+ export function PreloadLink(props: LinkProps & { to: string }) {
502
+ const preload = () => void preloadRoute(props.to);
503
+ return <Link {...props} onMouseEnter={preload} onFocus={preload} />;
504
+ }
505
+ ```
506
+
507
+ If the router uses a `basename`, pass it as the second argument. `to` also
508
+ accepts a partial location such as `{ pathname: "/users/42" }`.
509
+
363
510
  ### TypeScript setup
364
511
 
365
512
  Add Vite's client types and the package's virtual-module declaration to your
@@ -372,8 +519,9 @@ application TypeScript project. For example, create `src/vite-env.d.ts`:
372
519
 
373
520
  If `tsconfig.json` sets `compilerOptions.types`, include both `"vite/client"`
374
521
  and `"@troyio/vite-file-router"` in that list. This lets TypeScript resolve
375
- `virtual:routes`. Restart your editor's TypeScript server if the declaration
376
- isn't picked up immediately.
522
+ `virtual:routes`. Make sure the generated `src/route-types.d.ts`, or the path
523
+ you set with `dts`, is included by your `tsconfig.json`. Restart your editor's
524
+ TypeScript server if the declarations aren't picked up immediately.
377
525
 
378
526
  ## Build
379
527