Skip to content

Commit

Permalink
feat: recreate router tree on file creation/deletion in dev
Browse files Browse the repository at this point in the history
  • Loading branch information
brc-dd committed May 28, 2024
1 parent a8bd7dc commit bb1f5af
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 10 deletions.
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"imports": {
"@cliffy/prompt": "jsr:@cliffy/prompt@^1.0.0-rc.4",
"@david/dax": "jsr:@david/dax@^0.41.0",
"@parcel/watcher": "npm:@parcel/watcher@^2.4.1",
"@std/assert": "jsr:@std/assert@^0.225.3",
"@std/fmt": "jsr:@std/fmt@^0.225.1",
"@std/fs": "jsr:@std/fs@^0.229.1",
Expand Down
128 changes: 126 additions & 2 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 24 additions & 8 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { walk } from '@std/fs'
import { toFileUrl } from '@std/path'
import watcher from '@parcel/watcher'

const methods = new Set(['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH'])

Expand Down Expand Up @@ -238,7 +239,7 @@ class UrlNode {
* ```ts
* const { handler } = await createRouter(
* fromFileUrl(new URL('./api', import.meta.url)),
* { baseUrl: '/api' }
* { baseUrl: '/api', dev: Deno.env.get('DENO_ENV') === 'development' },
* )
* Deno.serve({ port: 3000, handler })
* ```
Expand All @@ -249,7 +250,7 @@ export async function createRouter(
): Promise<{
handler: (req: Request) => Promise<Response>
}> {
const root = new UrlNode()
let root: UrlNode

/** req.url.pathname -> { match, params } */
const lookupCache = new LRUCache<string, { match: string; params: Params } | null>(100)
Expand All @@ -258,11 +259,26 @@ export async function createRouter(
/** file:METHOD -> handler */
const handlerCache = new LRUCache<string, Handler | null>(100)

for await (const file of walk(dir, { includeDirs: false, includeSymlinks: false, exts: ['.ts'] })) {
let path = baseUrl + file.path.replace(/\\/g, '/').slice(dir.length)
if (path.endsWith('.d.ts') || path.includes('/_') || path.includes('/.')) continue
path = path.replace(/\.ts$/, '').replace(/\/(index)?$/, '').replace(/^(?!\/)/, '/')
root.insert(path, toFileUrl(file.path).href)
async function createTree() {
root = new UrlNode()

for await (const file of walk(dir, { includeDirs: false, includeSymlinks: false, exts: ['.ts'] })) {
let path = baseUrl + file.path.replace(/\\/g, '/').slice(dir.length)
if (path.endsWith('.d.ts') || path.includes('/_') || path.includes('/.')) continue
path = path.replace(/\.ts$/, '').replace(/\/(index)?$/, '').replace(/^(?!\/)/, '/')
root.insert(path, toFileUrl(file.path).href)
}
}

await createTree()

if (dev) {
watcher.subscribe(dir, async (_, events) => {
if (events.some((event) => event.type === 'create' || event.type === 'delete')) {
console.log('Reloading router...')
await createTree()
}
})
}

function getHandler(file: string, method: string): Promise<Handler | null> {
Expand Down Expand Up @@ -318,6 +334,6 @@ export async function createRouter(
* - use URLPatternList once it's available (https://github.com/whatwg/urlpattern/pull/166)
* - use iterative pattern if there is significant memory/performance improvement
* - use more efficient LRU cache implementation
* - reload router in dev mode when files are created/deleted
* - use eager loading in production mode
* - don't destroy whole tree on single file change
*/

0 comments on commit bb1f5af

Please sign in to comment.