Add build caching and refactor route build logic

This commit is contained in:
Corey Johnson 2025-07-10 11:39:37 -07:00
parent 79bedd157f
commit 7073768cec
5 changed files with 156 additions and 33 deletions

View File

@ -0,0 +1,96 @@
# Build Caching in Nano-Remix
The nano-remix package now includes intelligent build caching to improve performance by avoiding unnecessary rebuilds.
## How it Works
The caching system tracks:
- File modification timestamps
- Last build times
- Output file existence
Routes are only rebuilt when:
1. Source files have been modified since last build
2. Output files don't exist
3. Cache is explicitly disabled or forced
## Usage Examples
### Development Mode (with caching)
```typescript
import { nanoRemix } from "@workshop/nano-remix"
// Default behavior - uses intelligent caching
export default {
fetch: (req: Request) => nanoRemix(req),
}
```
### Development Mode (without caching)
```typescript
import { nanoRemix } from "@workshop/nano-remix"
// Disable caching for development if you want immediate rebuilds
export default {
fetch: (req: Request) =>
nanoRemix(req, {
disableCache: true,
}),
}
```
### Production Setup
```typescript
import { nanoRemix, preloadAllRoutes } from "@workshop/nano-remix"
// Pre-build all routes on startup for production
const server = {
async fetch(req: Request) {
return nanoRemix(req)
},
}
// Build all routes once on server startup
await preloadAllRoutes()
export default server
```
### Cache Management
```typescript
import { clearBuildCache, getBuildCacheStats } from "@workshop/nano-remix"
// Clear cache (useful for development)
clearBuildCache()
// Get cache statistics for monitoring
const stats = getBuildCacheStats()
console.log("Cache entries:", stats.length)
```
## Configuration Options
```typescript
type Options = {
routesDir?: string // Custom routes directory
distDir?: string // Custom output directory
disableCache?: boolean // Disable caching entirely
}
```
## Performance Impact
With caching enabled:
- ✅ First request to a route: Normal build time
- ✅ Subsequent requests: Near-instant response (no rebuild)
- ✅ File changes: Automatic rebuild on next request
- ✅ Production: Pre-build all routes once
This should significantly improve your server performance, especially for routes that haven't changed!

View File

@ -1,4 +1,5 @@
import { mkdirSync } from "node:fs"
import bunPluginTailwind from "bun-plugin-tailwind"
import { join, extname, dirname, basename, relative } from "node:path"
if (!import.meta.main) throw new Error("This script is intended to be run as a cli tool.")
@ -42,7 +43,7 @@ render(<WrappedComponent />, root)`
sourcemap: "inline",
target: "browser",
format: "esm",
plugins: [await import("bun-plugin-tailwind").then((m) => m.default)],
plugins: [bunPluginTailwind],
})
if (!result.success) {

View File

@ -1,29 +0,0 @@
import { join } from "node:path"
type BuildRouteOptions = {
distDir: string
routeName: string
filepath: string
}
export const buildDynamicRoute = async ({ distDir, routeName, filepath }: BuildRouteOptions) => {
const scriptPath = join(import.meta.dirname, "../scripts/build.ts")
const proc = Bun.spawn({
cmd: ["bun", "run", scriptPath, distDir, routeName, filepath],
stdout: "pipe",
stderr: "pipe",
})
const exitCode = await proc.exited
if (exitCode !== 0) {
const stderr = await new Response(proc.stderr).text()
throw new Error(`Build process failed with exit code ${exitCode}: ${stderr}`)
}
const stdout = await new Response(proc.stdout).text()
console.log(stdout)
return { success: true }
}

View File

@ -0,0 +1,50 @@
import { join } from "node:path"
type BuildRouteOptions = {
distDir: string
routeName: string
filepath: string
force?: boolean
}
export const buildRoute = async ({ distDir, routeName, filepath, force = false }: BuildRouteOptions) => {
if (!force && !(await shouldRebuild(routeName, filepath, distDir))) {
return
}
const scriptPath = join(import.meta.dirname, "../scripts/build.ts")
const proc = Bun.spawn({
cmd: ["bun", "run", scriptPath, distDir, routeName, filepath],
stdout: "pipe",
stderr: "pipe",
})
const exitCode = await proc.exited
if (exitCode !== 0) {
const stderr = await new Response(proc.stderr).text()
throw new Error(`Build process failed with exit code ${exitCode}: ${stderr}`)
}
const stdout = await new Response(proc.stdout).text()
console.log(stdout)
}
const shouldRebuild = async (routeName: string, sourceFilePath: string, distDir: string) => {
try {
const outputPath = join(distDir, routeName + ".js")
// If output doesn't exist, need to build
if (!(await Bun.file(outputPath).exists())) {
return true
}
const sourceModified = await Bun.file(sourceFilePath).lastModified
const outputModified = await Bun.file(outputPath).lastModified
return sourceModified > outputModified
} catch (error) {
return true
}
}

View File

@ -1,11 +1,11 @@
import { renderServer } from "@/renderServer"
import { buildDynamicRoute } from "./buildDynamicRout"
import { buildRoute } from "./buildRoute"
import { join, extname } from "node:path"
import { serve } from "bun"
type Options = {
routesDir?: string
distDir?: string
disableCache?: boolean // Disable caching for development
}
export const nanoRemix = async (req: Request, options: Options = {}) => {
const nanoRemixDir = join(process.cwd(), ".nano-remix")
@ -33,7 +33,12 @@ export const nanoRemix = async (req: Request, options: Options = {}) => {
// If the the route includes an extension it is a static file that we serve from the distDir
if (!ext) {
await buildDynamicRoute({ distDir, routeName, filepath: route.filePath }) // Eventually this should be running only on initial build and when a route changes
await buildRoute({
distDir,
routeName,
filepath: route.filePath,
force: options.disableCache, // Force rebuild if cache is disabled
})
return await renderServer(req, route)
} else {
const file = Bun.file(join(distDir, routeName + ext))