Server streams HTML shell with ~suspense placeholders immediately, then sends resolution <script> chunks as async IO completes. Browser renders loading skeletons instantly, replacing them with real content as data arrives via __sxResolve(). - defpage :stream true opts pages into streaming response - ~suspense component renders fallback with data-suspense attr - resolve-suspense in boot.sx (spec) + bootstrapped to sx-browser.js - __sxPending queue handles resolution before sx-browser.js loads - execute_page_streaming() async generator with concurrent IO tasks - Streaming demo page at /isomorphism/streaming with 1.5s simulated delay Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
61 lines
3.5 KiB
Plaintext
61 lines
3.5 KiB
Plaintext
;; Streaming & Suspense demo — Phase 6
|
|
;;
|
|
;; This page uses :stream true to enable chunked transfer encoding.
|
|
;; The browser receives the HTML shell immediately with loading skeletons,
|
|
;; then the content fills in when the (deliberately slow) data resolves.
|
|
;;
|
|
;; The :data expression simulates 1.5s IO delay. Without streaming, the
|
|
;; browser would wait the full 1.5s before seeing anything. With streaming,
|
|
;; the page skeleton appears instantly.
|
|
|
|
(defcomp ~streaming-demo-content (&key streamed-at message items)
|
|
(div :class "space-y-8"
|
|
(div :class "border-b border-stone-200 pb-6"
|
|
(h1 :class "text-2xl font-bold text-stone-900" "Streaming & Suspense Demo")
|
|
(p :class "mt-2 text-stone-600"
|
|
"This page uses " (code :class "bg-stone-100 px-1 rounded text-violet-700" ":stream true")
|
|
" in its defpage declaration. The browser receives the page skeleton instantly, "
|
|
"then content fills in as IO resolves."))
|
|
|
|
;; Timestamp proves this was streamed
|
|
(div :class "rounded-lg border border-green-200 bg-green-50 p-5 space-y-3"
|
|
(h2 :class "text-lg font-semibold text-green-900" "Streamed Content")
|
|
(p :class "text-green-800" message)
|
|
(p :class "text-green-700 text-sm"
|
|
"Data resolved at: " (code :class "bg-green-100 px-1 rounded" streamed-at))
|
|
(p :class "text-green-700 text-sm"
|
|
"This content arrived via a " (code :class "bg-green-100 px-1 rounded" "<script>__sxResolve(...)</script>")
|
|
" chunk streamed after the initial HTML shell."))
|
|
|
|
;; Flow diagram
|
|
(div :class "space-y-4"
|
|
(h2 :class "text-lg font-semibold text-stone-800" "Streaming Flow")
|
|
(div :class "grid gap-3"
|
|
(map (fn (item)
|
|
(div :class "flex items-start gap-3 rounded-lg border border-stone-200 bg-white p-4"
|
|
(div :class "flex-shrink-0 w-8 h-8 rounded-full bg-violet-100 flex items-center justify-center text-violet-700 font-bold text-sm"
|
|
(get item "label"))
|
|
(p :class "text-stone-700 text-sm pt-1" (get item "detail"))))
|
|
items)))
|
|
|
|
;; How it works
|
|
(div :class "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3"
|
|
(h2 :class "text-lg font-semibold text-blue-900" "How Streaming Works")
|
|
(ol :class "list-decimal list-inside text-blue-800 space-y-2 text-sm"
|
|
(li "Server starts data fetch and header fetch " (em "concurrently"))
|
|
(li "HTML shell with " (code "~suspense") " placeholders is sent immediately")
|
|
(li "Browser loads sx-browser.js, renders the page with loading skeletons")
|
|
(li "Data IO completes — server sends " (code "<script>__sxResolve(\"stream-content\", ...)</script>"))
|
|
(li "sx.js calls " (code "Sx.resolveSuspense()") " — replaces skeleton with real content")
|
|
(li "Header IO completes — same process for header area")))
|
|
|
|
;; Technical details
|
|
(div :class "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2"
|
|
(p :class "font-semibold text-amber-800" "Implementation details")
|
|
(ul :class "list-disc list-inside text-amber-700 space-y-1"
|
|
(li (code "defpage :stream true") " — opts the page into streaming response")
|
|
(li (code "~suspense :id \"...\" :fallback (...)") " — renders loading skeleton until resolved")
|
|
(li "Quart async generator response — yields chunks as they become available")
|
|
(li "Resolution via " (code "__sxResolve(id, sx)") " inline scripts in the stream")
|
|
(li "Falls back to standard (non-streaming) response for SX/HTMX requests")))))
|