Phase 6: Streaming & Suspense — chunked HTML with suspense resolution
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>
This commit is contained in:
@@ -91,6 +91,32 @@
|
||||
(sx-hydrate-elements el))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Resolve Suspense — replace streaming placeholder with resolved content
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; Called by inline <script> tags that arrive during chunked transfer:
|
||||
;; __sxResolve("content", "(~article :title \"Hello\")")
|
||||
;;
|
||||
;; Finds the suspense wrapper by data-suspense attribute, renders the
|
||||
;; new SX content, and replaces the wrapper's children.
|
||||
|
||||
(define resolve-suspense
|
||||
(fn (id sx)
|
||||
(let ((el (dom-query (str "[data-suspense=\"" id "\"]"))))
|
||||
(if el
|
||||
(do
|
||||
(let ((ast (parse sx))
|
||||
(env (get-render-env nil))
|
||||
(node (render-to-dom ast env nil)))
|
||||
(dom-set-text-content el "")
|
||||
(dom-append el node)
|
||||
(process-elements el)
|
||||
(sx-hydrate-elements el)
|
||||
(dom-dispatch el "sx:resolved" {:id id})))
|
||||
(log-warn (str "resolveSuspense: no element for id=" id))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Hydrate — render all [data-sx] elements
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -480,6 +480,7 @@ class JSEmitter:
|
||||
"init-style-dict": "initStyleDict",
|
||||
"SX_VERSION": "SX_VERSION",
|
||||
"boot-init": "bootInit",
|
||||
"resolve-suspense": "resolveSuspense",
|
||||
"resolve-mount-target": "resolveMountTarget",
|
||||
"sx-render-with-env": "sxRenderWithEnv",
|
||||
"get-render-env": "getRenderEnv",
|
||||
@@ -4020,6 +4021,7 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has
|
||||
api_lines.append(' update: typeof sxUpdateElement === "function" ? sxUpdateElement : null,')
|
||||
api_lines.append(' renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null,')
|
||||
api_lines.append(' getEnv: function() { return componentEnv; },')
|
||||
api_lines.append(' resolveSuspense: typeof resolveSuspense === "function" ? resolveSuspense : null,')
|
||||
api_lines.append(' init: typeof bootInit === "function" ? bootInit : null,')
|
||||
elif has_orch:
|
||||
api_lines.append(' init: typeof engineInit === "function" ? engineInit : null,')
|
||||
@@ -4060,7 +4062,18 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has
|
||||
api_lines.append('''
|
||||
// --- Auto-init ---
|
||||
if (typeof document !== "undefined") {
|
||||
var _sxInit = function() { bootInit(); };
|
||||
var _sxInit = function() {
|
||||
bootInit();
|
||||
// Process any suspense resolutions that arrived before init
|
||||
if (global.__sxPending) {
|
||||
for (var pi = 0; pi < global.__sxPending.length; pi++) {
|
||||
resolveSuspense(global.__sxPending[pi].id, global.__sxPending[pi].sx);
|
||||
}
|
||||
global.__sxPending = null;
|
||||
}
|
||||
// Set up direct resolution for future chunks
|
||||
global.__sxResolve = function(id, sx) { resolveSuspense(id, sx); };
|
||||
};
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", _sxInit);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user