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
54 lines
1.7 KiB
JavaScript
54 lines
1.7 KiB
JavaScript
// 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
|
|
}
|