feat: add vite-plugin-route-builder for dynamic route generation

- Introduced a new plugin to generate React Router routes from a file system-based structure, enhancing routing capabilities similar to Next.js.
- Updated various configurations and components to integrate the new plugin, including automatic route generation during build time.
- Refactored existing router implementations to utilize generated routes, improving maintainability and performance.

Signed-off-by: Innei <i@innei.in>
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-06-20 01:41:53 +08:00
parent 710622a907
commit 1d83313872
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
19 changed files with 1088 additions and 75 deletions

4
.gitignore vendored
View File

@ -21,4 +21,6 @@ vite.config.*.mjs
apps/desktop/src/renderer/dev-dist
tsconfig.tsbuildinfo
buildServer.json
buildServer.json
generated-routes.ts

View File

@ -1,26 +1,55 @@
import { dirname, resolve } from "node:path"
import { fileURLToPath } from "node:url"
import { tsImport } from "tsx/esm/api"
import type { UserConfig } from "vite"
import { cleanupUnnecessaryFilesPlugin } from "../plugins/vite/cleanup"
import { createPlatformSpecificImportPlugin } from "../plugins/vite/specific-import"
import { viteRenderBaseConfig } from "./vite.render.config"
const routeBuilderPluginV2 = await tsImport(
"@follow/vite-plugin-route-builder",
import.meta.url,
).then((m) => m.default)
const root = resolve(fileURLToPath(dirname(import.meta.url)), "..")
const VITE_ROOT = resolve(root, "layer/renderer")
export default {
...viteRenderBaseConfig,
plugins: [...viteRenderBaseConfig.plugins, createPlatformSpecificImportPlugin("electron")],
plugins: [
...viteRenderBaseConfig.plugins,
createPlatformSpecificImportPlugin("electron"),
routeBuilderPluginV2({
pagePattern: `${resolve(VITE_ROOT, "./src/pages")}/**/*.tsx`,
outputPath: `${resolve(VITE_ROOT, "./src/generated-routes.ts")}`,
enableInDev: true,
}),
cleanupUnnecessaryFilesPlugin([
"og-image.png",
"icon-512x512.png",
"opengraph-image.png",
"favicon.ico",
"icon-192x192.png",
"favicon-dev.ico",
"apple-touch-icon-180x180.png",
"maskable-icon-512x512.png",
"pwa-64x64.png",
"pwa-192x192.png",
"pwa-512x512.png",
]),
],
root: resolve(root, "layer/renderer"),
root: VITE_ROOT,
build: {
outDir: resolve(root, "dist/renderer"),
sourcemap: !!process.env.CI,
target: "esnext",
rollupOptions: {
input: {
main: resolve(root, "layer/renderer/index.html"),
main: resolve(VITE_ROOT, "index.html"),
},
},
minify: true,

View File

@ -3,9 +3,7 @@ import { resolve } from "node:path"
import { defineConfig } from "electron-vite"
import { getGitHash } from "../../scripts/lib"
import { viteRenderBaseConfig } from "./configs/vite.render.config"
import { cleanupUnnecessaryFilesPlugin } from "./plugins/vite/cleanup"
import { createPlatformSpecificImportPlugin } from "./plugins/vite/specific-import"
import rendererConfig from "./configs/vite.electron-render.config"
export default defineConfig({
main: {
@ -42,43 +40,5 @@ export default defineConfig({
},
},
},
renderer: {
...viteRenderBaseConfig,
root: "layer/renderer",
build: {
outDir: "dist/renderer",
sourcemap: !!process.env.CI,
target: "esnext",
rollupOptions: {
input: {
main: resolve("./layer/renderer/index.html"),
},
},
minify: true,
},
plugins: [
...viteRenderBaseConfig.plugins,
createPlatformSpecificImportPlugin("electron"),
cleanupUnnecessaryFilesPlugin([
"og-image.png",
"icon-512x512.png",
"opengraph-image.png",
"favicon.ico",
"icon-192x192.png",
"favicon-dev.ico",
"apple-touch-icon-180x180.png",
"maskable-icon-512x512.png",
"pwa-64x64.png",
"pwa-192x192.png",
"pwa-512x512.png",
]),
],
define: {
...viteRenderBaseConfig.define,
ELECTRON: "true",
},
},
renderer: rendererConfig,
})

View File

@ -1,14 +1,11 @@
import { IN_ELECTRON } from "@follow/shared/constants"
import { buildGlobRoutes } from "@follow/utils/route-builder"
import { wrapCreateBrowserRouterV7 } from "@sentry/react"
import { createBrowserRouter, createHashRouter } from "react-router"
import { Component as App } from "./App"
import { ErrorElement } from "./components/common/ErrorElement"
import { NotFound } from "./components/common/NotFound"
const globTree = import.meta.glob("./pages/**/*.tsx")
const tree = buildGlobRoutes(globTree)
import { routes as tree } from "./generated-routes"
let routerCreator =
IN_ELECTRON || globalThis["__DEBUG_PROXY__"] ? createHashRouter : createBrowserRouter

View File

@ -1,13 +1,10 @@
import { IN_ELECTRON } from "@follow/shared/constants"
import { buildGlobRoutes } from "@follow/utils/route-builder"
import { wrapCreateBrowserRouterV7 } from "@sentry/react"
import { createBrowserRouter, createHashRouter } from "react-router"
import { ErrorElement } from "./components/common/ErrorElement"
import { NotFound } from "./components/common/NotFound"
const globTree = import.meta.glob("./pages/**/*.tsx")
const tree = buildGlobRoutes(globTree)
import { routes as tree } from "./generated-routes"
let routerCreator =
IN_ELECTRON || globalThis["__DEBUG_PROXY__"] ? createHashRouter : createBrowserRouter

View File

@ -56,6 +56,7 @@
"@follow/models": "workspace:*",
"@follow/shared": "workspace:*",
"@follow/utils": "workspace:*",
"@follow/vite-plugin-route-builder": "workspace:*",
"@pengx17/electron-forge-maker-appimage": "1.2.1",
"@sentry/vite-plugin": "3.5.0",
"@types/html-minifier-terser": "7.0.2",
@ -85,7 +86,6 @@
"tailwindcss-content-visibility": "1.0.2",
"tailwindcss-multi": "0.4.6",
"tar": "7.4.3",
"tsup": "8.5.0",
"unplugin-ast": "0.15.0",
"utf-8-validate": "6.0.5",
"vite-bundle-analyzer": "0.22.0",

View File

@ -2,23 +2,20 @@ import path from "node:path"
import { fileURLToPath } from "node:url"
import { set } from "es-toolkit/compat"
import type { Plugin } from "vite"
import type { Logger, Plugin } from "vite"
const __dirname = fileURLToPath(new URL(".", import.meta.url))
const localesDir = path.resolve(__dirname, "../../../../locales")
export function localesJsonPlugin(): Plugin {
let isBuild = false
let logger: Logger
return {
name: "locales-json-transform",
enforce: "pre",
configResolved(config) {
isBuild = config.command === "build"
logger = config.logger
},
async transform(code, id) {
if (isBuild) {
return null
}
if (!id.includes(localesDir) || !id.endsWith(".json")) {
return null
}
@ -31,7 +28,7 @@ export function localesJsonPlugin(): Plugin {
set(obj, accessorKey, (content as any)[accessorKey])
}
console.info("[locales-json-transform] Transformed:", id)
logger.info(`[locales-json-transform] Transformed: ${id}`)
return {
code: JSON.stringify(obj),
map: null,

View File

@ -7,6 +7,7 @@ import legacy from "@vitejs/plugin-legacy"
import { minify as htmlMinify } from "html-minifier-terser"
import { cyan, dim, green } from "kolorist"
import { parseHTML } from "linkedom"
import { tsImport } from "tsx/esm/api"
import type { PluginOption, ResolvedConfig, ViteDevServer } from "vite"
import { defineConfig, loadEnv } from "vite"
import { analyzer } from "vite-bundle-analyzer"
@ -20,9 +21,14 @@ import { localesPlugin } from "./plugins/vite/locales"
import manifestPlugin from "./plugins/vite/manifest"
import { createPlatformSpecificImportPlugin } from "./plugins/vite/specific-import"
const routeBuilderPluginV2 = await tsImport(
"@follow/vite-plugin-route-builder",
import.meta.url,
).then((m) => m.default)
const __dirname = fileURLToPath(new URL(".", import.meta.url))
const isCI = process.env.CI === "true" || process.env.CI === "1"
const ROOT = "./layer/renderer"
const ROOT = resolve(__dirname, "./layer/renderer")
const devPrint = (): PluginOption => ({
name: "dev-print",
@ -138,6 +144,11 @@ export default ({ mode }) => {
plugins: [
...((viteRenderBaseConfig.plugins ?? []) as any),
routeBuilderPluginV2({
pagePattern: `${resolve(ROOT, "./src/pages")}/**/*.tsx`,
outputPath: `${resolve(ROOT, "./src/generated-routes.ts")}`,
enableInDev: true,
}),
localesPlugin(),
isWebBuild &&
VitePWA({

View File

@ -1,11 +1,8 @@
import { buildGlobRoutes } from "@follow/utils/route-builder"
import { wrapCreateBrowserRouterV7 } from "@sentry/react"
import { createBrowserRouter, createHashRouter } from "react-router"
import { NotFound } from "./components/common/404"
const globTree = import.meta.glob("./pages/**/*.tsx")
const tree = buildGlobRoutes(globTree)
import tree from "./generated-routes"
declare global {
interface Window {

View File

@ -51,6 +51,7 @@
"@follow/shared": "workspace:*",
"@follow/types": "workspace:*",
"@follow/utils": "workspace:*",
"@follow/vite-plugin-route-builder": "workspace:*",
"@types/html-minifier-terser": "7.0.2",
"chokidar": "4.0.3",
"code-inspector-plugin": "0.20.11",

View File

@ -2,11 +2,17 @@ import { resolve } from "node:path"
import react from "@vitejs/plugin-react"
import { codeInspectorPlugin } from "code-inspector-plugin"
import { tsImport } from "tsx/esm/api"
import { defineConfig } from "vite"
import { viteRenderBaseConfig } from "../desktop/configs/vite.render.config"
import { astPlugin } from "../desktop/plugins/vite/ast"
const routeBuilderPluginV2 = await tsImport(
"@follow/vite-plugin-route-builder",
import.meta.url,
).then((m) => m.default)
export default defineConfig({
resolve: {
alias: {
@ -29,6 +35,11 @@ export default defineConfig({
},
},
plugins: [
routeBuilderPluginV2({
pagePattern: `${resolve(__dirname, "./client/pages")}/**/*.tsx`,
outputPath: `${resolve(__dirname, "./client/generated-routes.ts")}`,
enableInDev: true,
}),
react(),
astPlugin,
codeInspectorPlugin({

View File

@ -2,7 +2,7 @@
"name": "@follow/monorepo",
"type": "module",
"private": true,
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
"packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac",
"description": "Follow everything in one place",
"author": "Folo Team",
"license": "GPL-3.0-only",

View File

@ -0,0 +1,30 @@
{
"name": "@follow/vite-plugin-route-builder",
"type": "module",
"version": "0.0.1",
"exports": {
".": "./src/index.ts"
},
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"src"
],
"scripts": {
"build": "tsdown"
},
"devDependencies": {
"@types/node": "22.15.23",
"es-toolkit": "1.38.0",
"fast-glob": "3.3.3",
"react-router": "7.6.1",
"tsdown": "0.12.8",
"typescript": "catalog:",
"vite": "6.3.5"
},
"publishConfig": {
"access": "public",
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
}

View File

@ -0,0 +1,285 @@
# Vite Route Builder Plugin - Technical Documentation
## Overview
The Vite Route Builder plugin is a build-time code generation tool that creates React Router route configurations from a file system-based routing structure, similar to Next.js App Router. It transforms a `pages/` directory structure into optimized route objects with lazy loading and proper component imports.
## Core Architecture
### Plugin Flow
1. **Build-time Generation**: The plugin runs during Vite's build process to generate static route configurations
2. **File System Scanning**: Uses glob patterns to discover page and layout files
3. **Route Tree Building**: Leverages the existing `route-builder.ts` logic to create route hierarchy
4. **Code Generation**: Produces a TypeScript file with lazy-loaded route objects
### Key Components
- **`vite-plugin-route-builder.ts`**: Main Vite plugin implementation
- **`route-builder.ts`**: Core routing logic for transforming file paths to route objects
- **`generated-routes.ts`**: Auto-generated output file containing route configurations
## File System Routing Conventions
### Directory Structure
```
pages/
├── (main)/ # Route group (doesn't affect URL path)
│ ├── layout.tsx # Layout component for grouped routes
│ ├── index.tsx # / route
│ └── discover.tsx # /discover route
├── settings/
│ ├── layout.tsx # Layout for /settings/*
│ ├── index.tsx # /settings route
│ └── profile.tsx # /settings/profile route
├── (settings)/ # Another route group
│ ├── layout.tsx # Layout component
│ └── general.tsx # /general route
└── [...404].tsx # Catch-all route
```
### Route Mapping Rules
1. **Index Routes**: `index.tsx` files create routes for their parent directory path
2. **Named Routes**: File names become route paths (e.g., `profile.tsx``/profile`)
3. **Grouped Routes**: Directories with parentheses `(name)` group routes without affecting URL paths
4. **Dynamic Routes**: `[param].tsx` creates dynamic parameter routes
5. **Catch-all Routes**: `[...name].tsx` creates catch-all routes
6. **Layouts**: `layout.tsx` files provide wrapper components for child routes
## Implementation Details
### 1. Route Discovery Process
```typescript
// Plugin discovers files using glob patterns
const pageFiles = glob.sync("./src/pages/**/*.{ts,tsx}", {
ignore: ["**/*.d.ts", "**/*.test.*", "**/*.spec.*"],
})
```
### 2. Route Tree Generation
The plugin uses the existing `route-builder.ts` logic:
```typescript
import { buildRoute } from "../route-builder"
// Transform file paths to route objects
const routes = buildRoute(pageFiles)
```
### 3. Path Resolution Strategy
The plugin implements sophisticated path matching to connect route objects with their corresponding files:
```typescript
function findFileForRoute(route: RouteObject, pageFiles: string[]): string | null {
const routePath = route.handle?.fs as string
if (!routePath) return null
// Strategy 1: Direct file match (fs.tsx)
let targetFile = `./src/pages/${routePath}.tsx`
if (pageFiles.includes(targetFile)) return targetFile
// Strategy 2: Layout file for grouped routes (fs/layout.tsx)
targetFile = `./src/pages/${routePath}/layout.tsx`
if (pageFiles.includes(targetFile)) return targetFile
// Strategy 3: Index file (fs/index.tsx)
targetFile = `./src/pages/${routePath}/index.tsx`
if (pageFiles.includes(targetFile)) return targetFile
// Strategy 4: Handle paths ending with '/' (index pages)
if (routePath.endsWith("/")) {
const cleanPath = routePath.slice(0, -1)
targetFile = `./src/pages/${cleanPath}/index.tsx`
if (pageFiles.includes(targetFile)) return targetFile
}
return null
}
```
### 4. Lazy Loading Implementation
The plugin generates lazy-loaded components for optimal performance:
```typescript
// Generate lazy imports only for routes that have corresponding files
const lazyComponents = new Map<string, string>()
let lazyCounter = 1
function collectLazyFunctions(route: RouteObject, pageFiles: string[]) {
const file = findFileForRoute(route, pageFiles)
if (file && !lazyComponents.has(file)) {
const relativePath = path.relative(
path.dirname("./src/generated-routes.ts"),
file.replace("./src/", "./src/"),
)
const varName = `LazyComponent${lazyCounter++}`
lazyComponents.set(file, varName)
return `const ${varName} = lazy(() => import("${relativePath}"))`
}
}
```
### 5. Code Generation
The final step generates a complete TypeScript file:
```typescript
const output = `
// Do not edit manually
/* eslint-disable */
// @ts-nocheck
import type { RouteObject } from "react-router"
import { lazy } from "react"
${lazyImports.join("\n")}
export const routes: RouteObject[] = ${serializedRoutes}
`
```
## Route Object Structure
### Generated Route Format
```typescript
interface RouteObject {
path?: string
index?: boolean
children?: RouteObject[]
element?: React.ComponentType
// Internal properties removed in final output
handle?: { fs: string } // Removed during serialization
}
```
### Example Generated Output
```typescript
const LazyComponent1 = lazy(() => import("./pages/(main)/layout"))
const LazyComponent2 = lazy(() => import("./pages/(main)/index"))
const LazyComponent3 = lazy(() => import("./pages/settings/layout"))
export const routes: RouteObject[] = [
{
path: "/",
element: LazyComponent1,
children: [
{
index: true,
element: LazyComponent2,
},
],
},
{
path: "/settings",
element: LazyComponent3,
children: [
// ... nested routes
],
},
]
```
## Problem Solving Approach
### Common Issues and Solutions
1. **Path Mismatch**: Routes generated without corresponding lazy functions
- **Solution**: Enhanced file matching with multiple strategies and path normalization
2. **Incorrect Import Paths**: Absolute paths causing import failures
- **Solution**: Proper relative path calculation using `path.relative()`
3. **Unused Lazy Variables**: Too many lazy imports for non-existent files
- **Solution**: Only generate lazy imports for routes with actual file matches
4. **Index Route Handling**: Paths ending with `/` causing mapping issues
- **Solution**: Special handling for index pages and path cleaning
### Debugging Strategy
The plugin includes comprehensive logging for troubleshooting:
```typescript
console.log("📁 Page files found:", pageFiles.length)
console.log("🎯 Routes with lazy functions:", routesWithLazy)
console.log("📝 Generated lazy imports:", lazyComponents.size)
```
## Performance Considerations
### Build-time Optimization
- **Static Generation**: All routing logic runs at build time, not runtime
- **Lazy Loading**: Components are loaded on-demand, reducing initial bundle size
- **Tree Shaking**: Unused routes and components are eliminated during bundling
### Memory Efficiency
- **File Caching**: Plugin processes files once and caches results
- **Selective Import**: Only imports files that are actually used in routes
## Integration Points
### Vite Integration
```typescript
// vite.config.ts
import { defineConfig } from "vite"
import routeBuilder from "./plugins/vite/vite-plugin-route-builder"
export default defineConfig({
plugins: [
routeBuilder(), // Add the route builder plugin
// ... other plugins
],
})
```
### React Router Integration
```typescript
// App.tsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { routes } from './generated-routes'
const router = createBrowserRouter(routes)
export default function App() {
return <RouterProvider router={router} />
}
```
## Future Enhancements
### Potential Improvements
1. **Watch Mode**: Real-time route regeneration during development
2. **TypeScript Validation**: Compile-time route validation
3. **Route Metadata**: Support for route-level metadata and guards
4. **Custom Conventions**: Configurable file naming conventions
5. **Nested Layouts**: Support for multiple layout levels
### Extensibility
The plugin architecture allows for easy extension:
- Custom file processors for different route types
- Pluggable path resolution strategies
- Configurable code generation templates
- Integration with other meta-frameworks
## Conclusion
The Vite Route Builder plugin successfully transforms file system-based routing into optimized React Router configurations. By leveraging build-time generation, it provides excellent performance while maintaining developer ergonomics similar to Next.js App Router. The robust path matching and lazy loading implementation ensures reliable route generation for complex application structures.

View File

@ -0,0 +1 @@
export { default } from "./vite-plugin-route-builder"

View File

@ -0,0 +1,356 @@
import { writeFileSync } from "node:fs"
import { dirname, relative, resolve } from "node:path"
import { inspect } from "node:util"
import glob from "fast-glob"
import type { RouteObject } from "react-router"
import type { Logger, Plugin } from "vite"
import { buildGlobRoutes } from "./utils/route-builder"
export interface RouteBuilderPluginOptions {
/** Page files glob pattern */
pagePattern?: string
/** Output path for generated routes */
outputPath?: string
/** Whether to enable in dev mode */
enableInDev?: boolean
/** Custom file to route path transformation logic */
transformPath?: (path: string) => string
}
export function routeBuilderPluginV2(options: RouteBuilderPluginOptions = {}): Plugin {
const {
pagePattern = "./pages/**/*.tsx",
outputPath = "./src/generated-routes.ts",
enableInDev = true,
transformPath,
} = options
let isProduction = false
let root = ""
let logger: Logger
function generateRouteFileContent(
routes: RouteObject[],
fileToImportMap: Record<string, string>,
): string {
// Collect all used lazy functions
const usedLazyFunctions = new Set<string>()
const lazyFunctionMap = new Map<string, string>()
let lazyCounter = 0
// Recursively traverse route tree, collect all used lazy functions
function collectUsedLazyFunctions(routes: RouteObject[]) {
routes.forEach((route) => {
if (route.lazy && route.handle?.fs) {
const fsPath = route.handle.fs
// Try to find the corresponding file
let matchedKey: string | undefined
// Direct match strategy: according to route-builder.ts logic
// 1. For grouped routes, fs is segmentPathKey, need to find ${fs}/layout.tsx
// 2. For layout files, fs is segmentPathKey, need to find ${fs}.tsx
// 3. For normal pages, fs is ${segmentPathKey}/${normalizeKey}, but the actual file is ${segmentPathKey}.tsx
// Strategy 1: Direct match fs.tsx
if (fileToImportMap[`${fsPath}.tsx`]) {
matchedKey = `${fsPath}.tsx`
}
// Strategy 2: layout file (for grouped routes)
else if (fileToImportMap[`${fsPath}/layout.tsx`]) {
matchedKey = `${fsPath}/layout.tsx`
}
// Strategy 3: index file
else if (fileToImportMap[`${fsPath}/index.tsx`]) {
matchedKey = `${fsPath}/index.tsx`
}
// Strategy 4: For special path correction
else {
// If fsPath ends with /, it might be an index page
if (fsPath.endsWith("/")) {
const correctedPath = fsPath.slice(0, -1) // Remove trailing /
if (fileToImportMap[`${correctedPath}/index.tsx`]) {
matchedKey = `${correctedPath}/index.tsx`
} else if (fileToImportMap[`${correctedPath}.tsx`]) {
matchedKey = `${correctedPath}.tsx`
}
}
// For dynamic routes, remove /:param part, keep file path
else if (fsPath.includes("/:")) {
// Handle cases like "./pages/category/[category]/:category"
// Remove /:param part, keep the previous path
const correctedPath = fsPath.replace(/\/:[^/]+(?:\/.*)?$/, "")
if (fileToImportMap[`${correctedPath}.tsx`]) {
matchedKey = `${correctedPath}.tsx`
}
}
// If fsPath ends with a repeated path segment, remove the last segment
else {
const pathParts = fsPath.split("/")
if (pathParts.length >= 2) {
const lastPart = pathParts.at(-1)
const secondLastPart = pathParts.at(-2)
// If the last two path segments are the same, remove the last one
if (lastPart === secondLastPart) {
const correctedPath = pathParts.slice(0, -1).join("/")
if (fileToImportMap[`${correctedPath}.tsx`]) {
matchedKey = `${correctedPath}.tsx`
}
}
}
}
}
if (matchedKey && fileToImportMap[matchedKey]) {
const lazyFuncName = `lazy${lazyCounter++}`
usedLazyFunctions.add(matchedKey)
lazyFunctionMap.set(matchedKey, lazyFuncName)
logger.info(
`[route-builder-v2] Mapped lazy function: ${fsPath} -> ${matchedKey} -> ${lazyFuncName}`,
)
} else {
logger.warn(`[route-builder-v2] Could not find file for fs path: ${fsPath}`)
logger.warn(
`[route-builder-v2] Available file keys: ${inspect(Object.keys(fileToImportMap), {
depth: null,
})}`,
)
}
}
if (route.children) {
collectUsedLazyFunctions(route.children)
}
})
}
collectUsedLazyFunctions(routes)
// Generate import statements
const imports: string[] = []
usedLazyFunctions.forEach((key) => {
const importPath = fileToImportMap[key]
const lazyFuncName = lazyFunctionMap.get(key)
if (importPath && lazyFuncName) {
imports.push(`const ${lazyFuncName} = () => import("${importPath}")`)
}
})
// Recursively process routes, replace lazy functions and remove handle
function processRoutes(routes: RouteObject[]): any {
return routes.map((route) => {
const newRoute: any = { ...route }
// Process lazy functions
if (route.lazy && route.handle?.fs) {
const fsPath = route.handle.fs
// Find matching file - use the same matching logic
let matchedKey: string | undefined
if (fileToImportMap[`${fsPath}.tsx`]) {
matchedKey = `${fsPath}.tsx`
} else if (fileToImportMap[`${fsPath}/layout.tsx`]) {
matchedKey = `${fsPath}/layout.tsx`
} else if (fileToImportMap[`${fsPath}/index.tsx`]) {
matchedKey = `${fsPath}/index.tsx`
} else {
// For special path correction
if (fsPath.endsWith("/")) {
const correctedPath = fsPath.slice(0, -1) // Remove trailing /
if (fileToImportMap[`${correctedPath}/index.tsx`]) {
matchedKey = `${correctedPath}/index.tsx`
} else if (fileToImportMap[`${correctedPath}.tsx`]) {
matchedKey = `${correctedPath}.tsx`
}
}
// For dynamic routes, remove /:param part, keep file path
else if (fsPath.includes("/:")) {
// Handle cases like "./pages/category/[category]/:category"
// Remove /:param part, keep the previous path
const correctedPath = fsPath.replace(/\/:[^/]+(?:\/.*)?$/, "")
if (fileToImportMap[`${correctedPath}.tsx`]) {
matchedKey = `${correctedPath}.tsx`
}
}
// For repeated path segments, try removing the last segment
else {
const pathParts = fsPath.split("/")
if (pathParts.length >= 2) {
const lastPart = pathParts.at(-1)
const secondLastPart = pathParts.at(-2)
// If the last two path segments are the same, remove the last one
if (lastPart === secondLastPart) {
const correctedPath = pathParts.slice(0, -1).join("/")
if (fileToImportMap[`${correctedPath}.tsx`]) {
matchedKey = `${correctedPath}.tsx`
}
}
}
}
}
if (matchedKey && lazyFunctionMap.has(matchedKey)) {
newRoute.lazy = `__LAZY_${lazyFunctionMap.get(matchedKey)}__`
} else {
// If no matching file is found, delete lazy property
delete newRoute.lazy
logger.warn(`[route-builder-v2] No lazy function for route: ${fsPath}`)
}
}
// Remove handle property
delete newRoute.handle
// Recursively process children
if (route.children) {
newRoute.children = processRoutes(route.children)
}
return newRoute
})
}
const processedRoutes = processRoutes(routes)
// Convert routes object to string and replace lazy function placeholders
const routesString = JSON.stringify(processedRoutes, null, 2).replaceAll(
/"__LAZY_(\w+)__"/g,
"$1",
)
return `// This file is auto-generated by vite-plugin-route-builder
// Do not edit manually
/* eslint-disable */
// @ts-nocheck
import type { RouteObject } from "react-router"
// Lazy imports for page components
${imports.join("\n")}
// Generated route configuration
export const routes: RouteObject[] = ${routesString}
export default routes
`
}
function generateRoutes() {
try {
const pageFiles = glob.sync(pagePattern, {
cwd: root,
absolute: true,
})
logger.info(`[route-builder-v2] Found ${pageFiles.length} page files`)
// Build glob object, key is the relative path to pages directory
const globObject: Record<string, () => Promise<any>> = {}
const fileToImportMap: Record<string, string> = {}
pageFiles.forEach((absolutePath) => {
// Get relative path to root
const relativePath = relative(root, absolutePath)
// Convert to ./pages/ format for route-builder
let routeKey: string
if (relativePath.includes("/pages/")) {
routeKey = `./pages/${relativePath.split("/pages/")[1]}`
} else if (relativePath.includes("\\pages\\")) {
routeKey = `./pages/${relativePath.split("\\pages\\")[1]?.replaceAll("\\", "/")}`
} else {
// Assume file is in pages directory
routeKey = `./${relativePath.replaceAll("\\", "/")}`
}
// Apply custom path transformation
if (transformPath) {
routeKey = transformPath(routeKey)
}
// Generate relative path for import (relative to output file)
const outputDir = dirname(resolve(root, outputPath))
let importPath = relative(outputDir, absolutePath)
// Ensure correct path separator
importPath = importPath.replaceAll("\\", "/")
// Ensure relative path starts with ./ or ../
if (!importPath.startsWith(".")) {
importPath = `./${importPath}`
}
// Remove .tsx extension
importPath = importPath.replace(/\.tsx$/, "")
globObject[routeKey] = () => Promise.resolve({ default: () => null })
fileToImportMap[routeKey] = importPath
logger.info(`[route-builder-v2] Mapped: ${routeKey} -> ${importPath}`)
})
// Use existing route building logic
const routes = buildGlobRoutes(globObject)
// Generate route file content
const routeFileContent = generateRouteFileContent(routes, fileToImportMap)
const outputFilePath = resolve(root, outputPath)
writeFileSync(outputFilePath, routeFileContent, "utf-8")
logger.info(`[route-builder-v2] Generated routes: ${outputFilePath}`)
} catch (error: any) {
logger.error(`[route-builder-v2] Error generating routes:${error.message}`)
console.error(error)
throw error
}
}
return {
name: "vite-plugin-route-builder-v2",
configResolved(config) {
isProduction = config.command === "build"
root = config.root
logger = config.logger
},
buildStart() {
if (isProduction || enableInDev) {
generateRoutes()
}
},
configureServer(server) {
if (!enableInDev) return
const watchPattern = resolve(root, pagePattern.replace("./", ""))
server.watcher.add(watchPattern)
server.watcher.on("add", handleFileChange)
server.watcher.on("unlink", handleFileChange)
function handleFileChange(path: string) {
const relativePath = relative(root, path)
if (relativePath.includes("/pages/") && relativePath.endsWith(".tsx")) {
logger.info(`[route-builder-v2] Page file changed: ${relativePath}`)
generateRoutes()
// Send custom HMR event
server.ws.send({
type: "custom",
event: "routes-updated",
data: { timestamp: Date.now() },
})
}
}
},
}
}
export default routeBuilderPluginV2

View File

@ -222,6 +222,9 @@ importers:
'@follow/utils':
specifier: workspace:*
version: link:../../packages/internal/utils
'@follow/vite-plugin-route-builder':
specifier: workspace:*
version: link:../../packages/vite-plugin-route-builder
'@pengx17/electron-forge-maker-appimage':
specifier: 1.2.1
version: 1.2.1(patch_hash=5b5ab1ba36e8c0d7ffee912ebf29c1a18bc101c9c661ceb1bb0bda3deaf4c667)(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3)
@ -309,9 +312,6 @@ importers:
tar:
specifier: 7.4.3
version: 7.4.3
tsup:
specifier: 8.5.0
version: 8.5.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0)
unplugin-ast:
specifier: 0.15.0
version: 0.15.0
@ -1236,6 +1236,9 @@ importers:
'@follow/utils':
specifier: workspace:*
version: link:../../packages/internal/utils
'@follow/vite-plugin-route-builder':
specifier: workspace:*
version: link:../../packages/vite-plugin-route-builder
'@types/html-minifier-terser':
specifier: 7.0.2
version: 7.0.2
@ -1815,6 +1818,30 @@ importers:
specifier: 8.5.0
version: 8.5.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0)
packages/vite-plugin-route-builder:
devDependencies:
'@types/node':
specifier: 22.15.23
version: 22.15.23
es-toolkit:
specifier: 1.38.0
version: 1.38.0
fast-glob:
specifier: 3.3.3
version: 3.3.3
react-router:
specifier: 7.6.1
version: 7.6.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
tsdown:
specifier: 0.12.8
version: 0.12.8(typescript@5.8.3)
typescript:
specifier: 'catalog:'
version: 5.8.3
vite:
specifier: 6.3.5
version: 6.3.5(@types/node@22.15.23)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.2)(tsx@4.19.4)(yaml@2.8.0)
packages:
7zip-bin@5.2.0:
@ -1892,6 +1919,10 @@ packages:
resolution: {integrity: sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==}
engines: {node: '>=6.9.0'}
'@babel/generator@7.27.5':
resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
engines: {node: '>=6.9.0'}
'@babel/helper-annotate-as-pure@7.27.1':
resolution: {integrity: sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==}
engines: {node: '>=6.9.0'}
@ -1999,6 +2030,11 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/parser@7.27.5':
resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1':
resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==}
engines: {node: '>=6.9.0'}
@ -2585,6 +2621,10 @@ packages:
resolution: {integrity: sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==}
engines: {node: '>=6.9.0'}
'@babel/types@7.27.6':
resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
engines: {node: '>=6.9.0'}
'@better-auth/expo@1.2.7':
resolution: {integrity: sha512-g8qBx7lBFfOWvAzWwNUVZr6IWYwBcZ7D/HcTzBG1zoZmD7PbdseQugWlIeS711vZ/Qqez7ZV0mDwpk2h+uT4nQ==}
peerDependencies:
@ -4716,6 +4756,13 @@ packages:
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@oxc-project/runtime@0.72.3':
resolution: {integrity: sha512-FtOS+0v7rZcnjXzYTTqv1vu/KDptD1UztFgoZkYBGe/6TcNFm+SP/jQoLvzau1SPir95WgDOBOUm2Gmsm+bQag==}
engines: {node: '>=6.9.0'}
'@oxc-project/types@0.72.3':
resolution: {integrity: sha512-CfAC4wrmMkUoISpQkFAIfMVvlPfQV3xg7ZlcqPXPOIMQhdKIId44G8W0mCPgtpWdFFAyJ+SFtiM+9vbyCkoVng==}
'@peculiar/asn1-android@2.3.16':
resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==}
@ -5631,6 +5678,69 @@ packages:
resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==}
engines: {node: '>= 10'}
'@rolldown/binding-darwin-arm64@1.0.0-beta.15':
resolution: {integrity: sha512-YInZppDBLp5DadbJZGc7xBfDrMCSj3P6i2rPlvOCMlvjBQxJi2kX8Jquh+LufsWUiHD3JsvvH5EuUUc/tF5fkA==}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.0.0-beta.15':
resolution: {integrity: sha512-Zwv8KHU/XdVwLseHG6slJ0FAFklPpiO0sjNvhrcMp1X3F2ajPzUdIO8Cnu3KLmX1GWVSvu6q1kyARLUqPvlh7Q==}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.0.0-beta.15':
resolution: {integrity: sha512-FwhNC23Fz9ldHW1/rX4QaoQe4kyOybCgxO9eglue3cbb3ol28KWpQl3xJfvXc9+O6PDefAs4oFBCbtTh8seiUw==}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.15':
resolution: {integrity: sha512-E60pNliWl4j7EFEVX2oeJZ5VzR+NG6fvDJoqfqRfCl8wtKIf9E1WPWVQIrT+zkz+Fhc5op8g7h25z6rtxsDy9g==}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.15':
resolution: {integrity: sha512-d+qo1LZ/a3EcQW08byIIZy0PBthmG/7dr69pifmNIet/azWR8jbceQaRFFczVc/NwVV3fsZDCmjG8mgJzsNEAg==}
cpu: [arm64]
os: [linux]
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.15':
resolution: {integrity: sha512-P1hbtYF+5ftJI2Ergs4iARbAk6Xd6WnTQb3CF9kjN3KfJTsRYdo5/fvU8Lz/gzhZVvkCXXH3NxDd9308UBO8cw==}
cpu: [arm64]
os: [linux]
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.15':
resolution: {integrity: sha512-Q9NM9uMFN9cjcrW7gd9U087B5WzkEj9dQQHOgoENZSy+vYJYS2fINCIG40ljEVC6jXmVrJgUhJKv7elRZM1nng==}
cpu: [x64]
os: [linux]
'@rolldown/binding-linux-x64-musl@1.0.0-beta.15':
resolution: {integrity: sha512-1tuCWuR8gx9PyW2pxAx2ZqnOnwhoY6NWBVP6ZmrjCKQ16NclYc61BzegFXSdugCy8w1QpBPT8/c5oh2W4E5aeA==}
cpu: [x64]
os: [linux]
'@rolldown/binding-wasm32-wasi@1.0.0-beta.15':
resolution: {integrity: sha512-zrSeYrpTf27hRxMLh0qpkCoWgzRKG8EyR6o09Zt9xkqCOeE5tEK/S3jV1Nii9WSqVCWFRA+OYxKzMNoykV590g==}
engines: {node: '>=14.21.3'}
cpu: [wasm32]
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.15':
resolution: {integrity: sha512-diR41DsMUnkvb9hvW8vuIrA0WaacAN1fu6lPseXhYifAOZN6kvxEwKn7Xib8i0zjdrYErLv7GNSQ48W+xiNOnA==}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.15':
resolution: {integrity: sha512-oCbbcDC3Lk8YgdxCkG23UqVrvXVvllIBgmmwq89bhq5okPP899OI/P+oTTDsUTbhljzNq1pH8a+mR6YBxAFfvw==}
cpu: [ia32]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.15':
resolution: {integrity: sha512-w5hVsOv3dzKo10wAXizmnDvUo1yasn/ps+mcn9H9TiJ/GeRE5/15Y6hG6vUQYRQNLVbYRHUt2qG0MyOoasPcHg==}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.0-beta.15':
resolution: {integrity: sha512-lvFtIbidq5EqyAAeiVk41ZNjGRgUoGRBIuqpe1VRJ7R8Av7TLAgGWAwGlHNhO7MFkl7MNRX350CsTtIWIYkNIQ==}
'@rolldown/pluginutils@1.0.0-beta.9':
resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==}
@ -6742,6 +6852,10 @@ packages:
ansicolors@0.3.2:
resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==}
ansis@4.1.0:
resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==}
engines: {node: '>=14'}
any-base@1.1.0:
resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==}
@ -6851,6 +6965,10 @@ packages:
resolution: {integrity: sha512-P63jzlYNz96MF9mCcprU+a7I5/ZQ5QAn3y+mZcPWEcGV3CHF/GWnkFPj3oCrWLUjL47+PD9PNiCUdXxw0cWdsg==}
engines: {node: '>=20.18.0'}
ast-kit@2.1.0:
resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==}
engines: {node: '>=20.18.0'}
astral-regex@2.0.0:
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
engines: {node: '>=8'}
@ -7041,6 +7159,9 @@ packages:
birecord@0.1.1:
resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==}
birpc@2.4.0:
resolution: {integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==}
bl@1.2.3:
resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==}
@ -8094,6 +8215,10 @@ packages:
resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
engines: {node: '>=0.3.1'}
diff@8.0.2:
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
engines: {node: '>=0.3.1'}
diffie-hellman@5.0.3:
resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
@ -8289,6 +8414,15 @@ packages:
resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==}
engines: {node: '>=0.10'}
dts-resolver@2.1.1:
resolution: {integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==}
engines: {node: '>=20.18.0'}
peerDependencies:
oxc-resolver: '>=11.0.0'
peerDependenciesMeta:
oxc-resolver:
optional: true
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@ -8420,6 +8554,10 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
empathic@1.1.0:
resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==}
engines: {node: '>=14'}
encode-utf8@1.0.3:
resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
@ -9981,6 +10119,9 @@ packages:
resolution: {integrity: sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==}
engines: {node: '>=16.9.0'}
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@ -13416,6 +13557,26 @@ packages:
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
engines: {node: '>=8.0'}
rolldown-plugin-dts@0.13.11:
resolution: {integrity: sha512-1TScN31JImk8xcq9kdm52z2W8/QX3zeDpEjFkyZmK+GcD0u8QqSWWARBsCEdfS99NyI6D9NIbUpsABXlcpZhig==}
engines: {node: '>=20.18.0'}
peerDependencies:
'@typescript/native-preview': '>=7.0.0-dev.20250601.1'
rolldown: ^1.0.0-beta.9
typescript: ^5.0.0
vue-tsc: ~2.2.0
peerDependenciesMeta:
'@typescript/native-preview':
optional: true
typescript:
optional: true
vue-tsc:
optional: true
rolldown@1.0.0-beta.15:
resolution: {integrity: sha512-ep788NsIGl0W5gT+99hBrSGe4Hdhcwc55PqM3O0mR5H0C4ZpGpDGgu9YzTJ8a6mFDLnFnc/LYC+Dszb7oWK/dg==}
hasBin: true
rollup@2.79.2:
resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==}
engines: {node: '>=10.0.0'}
@ -14334,6 +14495,28 @@ packages:
typescript:
optional: true
tsdown@0.12.8:
resolution: {integrity: sha512-niHeVcFCNjvVZYVGTeoM4BF+/DWxP8pFH2tUs71sEKYdcKtJIbkSdEmtxByaRZeMgwVbVgPb8nv9i9okVwFLAA==}
engines: {node: '>=18.0.0'}
hasBin: true
peerDependencies:
'@arethetypeswrong/core': ^0.18.1
publint: ^0.3.0
typescript: ^5.0.0
unplugin-lightningcss: ^0.4.0
unplugin-unused: ^0.5.0
peerDependenciesMeta:
'@arethetypeswrong/core':
optional: true
publint:
optional: true
typescript:
optional: true
unplugin-lightningcss:
optional: true
unplugin-unused:
optional: true
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@ -15407,6 +15590,14 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
'@babel/generator@7.27.5':
dependencies:
'@babel/parser': 7.27.5
'@babel/types': 7.27.6
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
'@babel/helper-annotate-as-pure@7.27.1':
dependencies:
'@babel/types': 7.27.3
@ -15610,6 +15801,10 @@ snapshots:
dependencies:
'@babel/types': 7.27.3
'@babel/parser@7.27.5':
dependencies:
'@babel/types': 7.27.6
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.26.10)':
dependencies:
'@babel/core': 7.26.10
@ -17017,6 +17212,11 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@babel/types@7.27.6':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@better-auth/expo@1.2.7(better-auth@1.2.8)':
dependencies:
'@better-fetch/fetch': 1.1.18
@ -20085,6 +20285,10 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@oxc-project/runtime@0.72.3': {}
'@oxc-project/types@0.72.3': {}
'@peculiar/asn1-android@2.3.16':
dependencies:
'@peculiar/asn1-schema': 2.3.15
@ -21064,7 +21268,7 @@ snapshots:
debug: 2.6.9
invariant: 2.2.4
metro: 0.82.4(bufferutil@4.0.9)
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-config: 0.82.4(bufferutil@4.0.9)
metro-core: 0.82.4
semver: 7.7.2
transitivePeerDependencies:
@ -21203,6 +21407,46 @@ snapshots:
'@resvg/resvg-js-win32-ia32-msvc': 2.6.2
'@resvg/resvg-js-win32-x64-msvc': 2.6.2
'@rolldown/binding-darwin-arm64@1.0.0-beta.15':
optional: true
'@rolldown/binding-darwin-x64@1.0.0-beta.15':
optional: true
'@rolldown/binding-freebsd-x64@1.0.0-beta.15':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.15':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.15':
optional: true
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.15':
optional: true
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.15':
optional: true
'@rolldown/binding-linux-x64-musl@1.0.0-beta.15':
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-beta.15':
dependencies:
'@napi-rs/wasm-runtime': 0.2.10
optional: true
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.15':
optional: true
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.15':
optional: true
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.15':
optional: true
'@rolldown/pluginutils@1.0.0-beta.15': {}
'@rolldown/pluginutils@1.0.0-beta.9': {}
'@rollup/plugin-babel@5.3.1(@babel/core@7.27.3)(@types/babel__core@7.20.5)(rollup@2.79.2)':
@ -21969,7 +22213,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.23
optional: true
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)':
@ -22479,6 +22723,8 @@ snapshots:
ansicolors@0.3.2: {}
ansis@4.1.0: {}
any-base@1.1.0: {}
any-promise@1.3.0: {}
@ -22656,6 +22902,11 @@ snapshots:
'@babel/parser': 7.27.3
pathe: 2.0.3
ast-kit@2.1.0:
dependencies:
'@babel/parser': 7.27.5
pathe: 2.0.3
astral-regex@2.0.0: {}
async-es@3.2.6: {}
@ -23006,6 +23257,8 @@ snapshots:
birecord@0.1.1: {}
birpc@2.4.0: {}
bl@1.2.3:
dependencies:
readable-stream: 2.3.8
@ -24222,6 +24475,8 @@ snapshots:
diff@7.0.0: {}
diff@8.0.2: {}
diffie-hellman@5.0.3:
dependencies:
bn.js: 4.12.2
@ -24370,6 +24625,8 @@ snapshots:
nan: 2.22.2
optional: true
dts-resolver@2.1.1: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -24658,6 +24915,8 @@ snapshots:
emoji-regex@9.2.2: {}
empathic@1.1.0: {}
encode-utf8@1.0.3:
optional: true
@ -26544,7 +26803,7 @@ snapshots:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.2
fast-glob: 3.3.3
ignore: 5.3.0
merge2: 1.4.1
slash: 3.0.0
@ -26826,6 +27085,8 @@ snapshots:
hono@4.7.10(patch_hash=5c74c2d2afaa5880c13d75458dd26c84da568851691b1fb6de6d877c29936d05): {}
hookable@5.5.3: {}
hosted-git-info@2.8.9: {}
hosted-git-info@4.1.0:
@ -28221,6 +28482,21 @@ snapshots:
transitivePeerDependencies:
- supports-color
metro-config@0.82.4(bufferutil@4.0.9):
dependencies:
connect: 3.7.0
cosmiconfig: 5.2.1
flow-enums-runtime: 0.0.6
jest-validate: 29.7.0
metro: 0.82.4(bufferutil@4.0.9)
metro-cache: 0.82.4
metro-core: 0.82.4
metro-runtime: 0.82.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
metro-config@0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5):
dependencies:
connect: 3.7.0
@ -28235,6 +28511,7 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
metro-core@0.82.4:
dependencies:
@ -28346,6 +28623,7 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
metro@0.82.4(bufferutil@4.0.9):
dependencies:
@ -28373,7 +28651,7 @@ snapshots:
metro-babel-transformer: 0.82.4
metro-cache: 0.82.4
metro-cache-key: 0.82.4
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
metro-config: 0.82.4(bufferutil@4.0.9)
metro-core: 0.82.4
metro-file-map: 0.82.4
metro-resolver: 0.82.4
@ -28440,6 +28718,7 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
micromark-core-commonmark@2.0.3:
dependencies:
@ -30765,6 +31044,43 @@ snapshots:
sprintf-js: 1.1.3
optional: true
rolldown-plugin-dts@0.13.11(rolldown@1.0.0-beta.15)(typescript@5.8.3):
dependencies:
'@babel/generator': 7.27.5
'@babel/parser': 7.27.5
'@babel/types': 7.27.6
ast-kit: 2.1.0
birpc: 2.4.0
debug: 4.4.1(supports-color@8.1.1)
dts-resolver: 2.1.1
get-tsconfig: 4.10.1
rolldown: 1.0.0-beta.15
optionalDependencies:
typescript: 5.8.3
transitivePeerDependencies:
- oxc-resolver
- supports-color
rolldown@1.0.0-beta.15:
dependencies:
'@oxc-project/runtime': 0.72.3
'@oxc-project/types': 0.72.3
'@rolldown/pluginutils': 1.0.0-beta.15
ansis: 4.1.0
optionalDependencies:
'@rolldown/binding-darwin-arm64': 1.0.0-beta.15
'@rolldown/binding-darwin-x64': 1.0.0-beta.15
'@rolldown/binding-freebsd-x64': 1.0.0-beta.15
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.15
'@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.15
'@rolldown/binding-linux-arm64-musl': 1.0.0-beta.15
'@rolldown/binding-linux-x64-gnu': 1.0.0-beta.15
'@rolldown/binding-linux-x64-musl': 1.0.0-beta.15
'@rolldown/binding-wasm32-wasi': 1.0.0-beta.15
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.15
'@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.15
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.15
rollup@2.79.2:
optionalDependencies:
fsevents: 2.3.3
@ -31833,6 +32149,29 @@ snapshots:
optionalDependencies:
typescript: 5.8.3
tsdown@0.12.8(typescript@5.8.3):
dependencies:
ansis: 4.1.0
cac: 6.7.14
chokidar: 4.0.3
debug: 4.4.1(supports-color@8.1.1)
diff: 8.0.2
empathic: 1.1.0
hookable: 5.5.3
rolldown: 1.0.0-beta.15
rolldown-plugin-dts: 0.13.11(rolldown@1.0.0-beta.15)(typescript@5.8.3)
semver: 7.7.2
tinyexec: 1.0.1
tinyglobby: 0.2.14
unconfig: 7.3.2
optionalDependencies:
typescript: 5.8.3
transitivePeerDependencies:
- '@typescript/native-preview'
- oxc-resolver
- supports-color
- vue-tsc
tslib@1.14.1:
optional: true