Created
May 31, 2025 16:18
-
-
Save roderik/50c4b07a452cc0546b76d66fc957757c to your computer and use it in GitHub Desktop.
Bun + ORPC + Tanstack Router setup, without Vite
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { serve } from "bun"; | |
import { serverConfig } from "./lib/config/server"; | |
import "./lib/plugins/tanstack-router-generator"; | |
import { routes } from "./lib/routes"; | |
const server = serve({ | |
routes, | |
...serverConfig, | |
}); | |
console.log(`🚀 Server running at ${server.url}`); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { auth } from "@/server/lib/auth"; | |
import { createApiHandler } from "@/server/lib/handlers/api-handler"; | |
import { | |
applyMiddleware, | |
composeMiddleware, | |
withApiCache, | |
withCompression, | |
withRequestId, | |
withResponseHeaders, | |
} from "@/server/lib/middleware"; | |
import index from "../../browser/index.html"; | |
// Create API handler with middleware | |
const apiHandler = applyMiddleware( | |
[withCompression, withApiCache], | |
withResponseHeaders(composeMiddleware([withRequestId], createApiHandler())), | |
); | |
// Wrap auth handler with compression | |
const authHandler = applyMiddleware([withCompression], auth.handler); | |
// For static files, use the HTMLBundle directly without middleware | |
// since Bun handles compression for static files automatically | |
const staticHandler = index; | |
export const routes = { | |
"/api/auth/*": authHandler, | |
"/api": apiHandler, | |
"/api/*": apiHandler, | |
// Serve index.html for all unmatched routes. | |
"/*": staticHandler, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { Config } from "@tanstack/router-generator"; | |
import { type BunPlugin } from "bun"; | |
import { watch, type FSWatcher } from "fs"; | |
import { resolve } from "path"; | |
// Load configuration using TanStack Router generator | |
async function loadConfig(): Promise<Config> { | |
const { getConfig } = await import("@tanstack/router-generator"); | |
const config = getConfig(); | |
return { | |
...config, | |
disableLogging: true, | |
}; | |
} | |
// Generate route tree using TanStack Router generator | |
async function generateRouteTree(config: Config) { | |
try { | |
const { generator } = await import("@tanstack/router-generator"); | |
await generator(config, process.cwd()); | |
} catch { | |
console.warn("TanStack Router generator not found or failed to generate"); | |
} | |
} | |
// Debounce timer storage | |
let debounceTimer: Timer | null = null; | |
// Watch for changes in development mode | |
function watchRoutes(config: Config): FSWatcher | undefined { | |
const routesDir = resolve(config.routesDirectory); | |
// Watch the routes directory for changes | |
const watcher = watch( | |
routesDir, | |
{ recursive: true }, | |
async (eventType, filename) => { | |
if (!filename) return; | |
// Only react to TypeScript/JavaScript files | |
if (!/\.(ts|tsx|js|jsx)$/.test(filename)) return; | |
// Ignore files with the ignore prefix | |
if (filename.startsWith(config.routeFileIgnorePrefix as string)) return; | |
// Debounce regeneration | |
if (debounceTimer) { | |
clearTimeout(debounceTimer); | |
} | |
debounceTimer = setTimeout(async () => { | |
await generateRouteTree(config); | |
}, 100); | |
}, | |
); | |
return watcher; | |
} | |
// Create the Bun plugin | |
export function createTanStackRouterPlugin( | |
options: Record<string, unknown> = {}, | |
): BunPlugin { | |
return { | |
name: "TanStack Router", | |
async setup(build) { | |
// Merge user options with loaded config | |
const loadedConfig = await loadConfig(); | |
const config = { ...loadedConfig, ...options }; | |
// Determine if we're in development mode | |
const isDev = | |
process.env.NODE_ENV !== "production" && | |
process.env.BUN_ENV !== "production"; | |
build.onStart(async () => { | |
// Always generate initially | |
await generateRouteTree(config); | |
// Set up watching in development mode | |
if (isDev) { | |
watchRoutes(config); | |
} | |
}); | |
// Handle route file imports - let TanStack Router handle them naturally | |
build.onResolve({ filter: /\.route\.(ts|tsx|js|jsx)$/ }, () => { | |
return undefined; | |
}); | |
}, | |
}; | |
} | |
// Export default plugin instance | |
export default createTanStackRouterPlugin(); | |
// Also run the plugin functionality immediately when imported | |
(async () => { | |
try { | |
const config = await loadConfig(); | |
await generateRouteTree(config); | |
// Set up watching in development mode | |
const isDev = | |
process.env.NODE_ENV !== "production" && | |
process.env.BUN_ENV !== "production"; | |
if (isDev) { | |
watchRoutes(config); | |
} | |
} catch (error) { | |
console.warn("Failed to run TanStack Router plugin:", error); | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"routesDirectory": "./browser/routes", | |
"generatedRouteTree": "./browser/routeTree.gen.ts", | |
"routeFileIgnorePrefix": "-", | |
"quoteStyle": "double" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment