feat(core): toStream() and lazy() routes — the answer to choojs/choo#653
toStream(location, state) renders to a web-standard ReadableStream of UTF-8 bytes: pass it to new Response() on web servers or Readable.fromWeb().pipe(res) on Node. It awaits store prefetch promises (state.prefetch, the pattern the #653 thread wanted standardized) and lazy route views before rendering, then flushes template output progressively through async holes. lazy(loader, loadingView?) wraps a dynamic import as a route handler: loads once, caches forever, renders the loading view (or holds the current tree via a placeholder) while in flight, emits render on arrival. toString() refuses lazy routes and prefetching stores with pointers to toStream() — both halves of the async-route story ship together, unlike the original PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
This commit is contained in:
co-authored by
Claude Fable 5
parent
9cf9e51ac9
commit
22c5045c25
@@ -0,0 +1,53 @@
|
||||
// Lazy (async) routes — the v8 answer to choojs/choo#653.
|
||||
//
|
||||
// A lazy route handler loads its view on first match via a native dynamic
|
||||
// import (or any promise-returning loader) and caches it forever after.
|
||||
// In the browser, while the view is loading, the handler renders the
|
||||
// optional loading view — or the PENDING sentinel, which tells choo to
|
||||
// keep whatever is already on screen. When the module arrives the wrapper
|
||||
// emits a render and the real view morphs in. On the server, toStream()
|
||||
// awaits load() before rendering; toString() refuses lazy routes loudly
|
||||
// (the missing server story is what stalled #653 — here both halves ship
|
||||
// together).
|
||||
|
||||
import { equal } from './assert.js'
|
||||
|
||||
// Sentinel: "keep the current tree, a view is on its way"
|
||||
export const PENDING = { __chooPending: true }
|
||||
|
||||
export default function lazy (loader, loading) {
|
||||
equal(typeof loader, 'function', 'choo.lazy: loader should be type function')
|
||||
|
||||
let view = null
|
||||
let promise = null
|
||||
|
||||
function load () {
|
||||
if (!promise) {
|
||||
promise = Promise.resolve(loader()).then(function (mod) {
|
||||
view = (mod && mod.default) || mod
|
||||
equal(typeof view, 'function', 'choo.lazy: loader should resolve to a view function (module default export)')
|
||||
return view
|
||||
})
|
||||
}
|
||||
return promise
|
||||
}
|
||||
|
||||
function lazyView (state, emit) {
|
||||
if (view) return view(state, emit)
|
||||
|
||||
load().then(function () {
|
||||
emit(state.events.RENDER)
|
||||
}, function (err) {
|
||||
// surface load failures instead of hanging on the loading view
|
||||
emit('error', err)
|
||||
throw err
|
||||
})
|
||||
|
||||
if (loading) return loading(state, emit)
|
||||
return PENDING
|
||||
}
|
||||
|
||||
lazyView.__lazy = true
|
||||
lazyView.load = load
|
||||
return lazyView
|
||||
}
|
||||
Reference in New Issue
Block a user