Add build caching and refactor route build logic
This commit is contained in:
parent
79bedd157f
commit
7073768cec
96
packages/nano-remix/CACHING.md
Normal file
96
packages/nano-remix/CACHING.md
Normal 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!
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
50
packages/nano-remix/src/buildRoute.ts
Normal file
50
packages/nano-remix/src/buildRoute.ts
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user