Phase 5: async IO rendering — components call IO primitives client-side
Wire async rendering into client-side routing: pages whose component trees reference IO primitives (highlight, current-user, etc.) now render client-side via Promise-aware asyncRenderToDom. IO calls proxy through /sx/io/<name> endpoint, which falls back to page helpers. - Add has-io flag to page registry entries (helpers.py) - Remove IO purity filter — include IO-dependent components in bundles - Extend try-client-route with 4 paths: pure, data, IO, data+IO - Convert tryAsyncEvalContent to callback style, add platform mapping - IO proxy falls back to page helpers (highlight works via proxy) - Demo page: /isomorphism/async-io with inline highlight calls Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
62
sx/sx/async-io-demo.sx
Normal file
62
sx/sx/async-io-demo.sx
Normal file
@@ -0,0 +1,62 @@
|
||||
;; Async IO demo — Phase 5 client-side rendering with IO primitives.
|
||||
;;
|
||||
;; This component calls `highlight` inline — an IO primitive that runs
|
||||
;; server-side Python (pygments). When rendered on the server, it
|
||||
;; executes synchronously. When rendered client-side, the async
|
||||
;; renderer proxies the call via /sx/io/highlight and awaits the result.
|
||||
;;
|
||||
;; Open browser console and look for:
|
||||
;; "sx:route client+async" — async render with IO proxy
|
||||
;; "sx:io registered N proxied primitives" — IO proxy initialization
|
||||
|
||||
(defcomp ~async-io-demo-content ()
|
||||
(div :class "space-y-8"
|
||||
(div :class "border-b border-stone-200 pb-6"
|
||||
(h1 :class "text-2xl font-bold text-stone-900" "Async IO Demo")
|
||||
(p :class "mt-2 text-stone-600"
|
||||
"This page calls " (code :class "bg-stone-100 px-1 rounded text-violet-700" "highlight")
|
||||
" inline — an IO primitive. On the server it runs Python (pygments). "
|
||||
"On the client it proxies via " (code :class "bg-stone-100 px-1 rounded text-violet-700" "/sx/io/highlight")
|
||||
" and the async renderer awaits the result."))
|
||||
|
||||
;; Live syntax-highlighted code blocks — each is an IO call
|
||||
(div :class "space-y-6"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Live IO: syntax highlighting")
|
||||
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
|
||||
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "SX component definition")
|
||||
(div :class "rounded bg-stone-900 p-4 text-sm overflow-x-auto"
|
||||
(raw! (highlight "(defcomp ~card (&key title subtitle &rest children)\n (div :class \"border rounded-lg p-4 shadow-sm\"\n (h2 :class \"text-lg font-bold\" title)\n (when subtitle\n (p :class \"text-stone-500 text-sm\" subtitle))\n (div :class \"mt-3\" children)))" "lisp"))))
|
||||
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
|
||||
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Python server code")
|
||||
(div :class "rounded bg-stone-900 p-4 text-sm overflow-x-auto"
|
||||
(raw! (highlight "from shared.sx.pages import mount_io_endpoint\n\n# The IO proxy serves any allowed primitive:\n# GET /sx/io/highlight?_arg0=code&_arg1=lisp\nasync def io_proxy(name):\n result = await execute_io(name, args, kwargs, ctx)\n return serialize(result)" "python"))))
|
||||
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
|
||||
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "JavaScript async renderer")
|
||||
(div :class "rounded bg-stone-900 p-4 text-sm overflow-x-auto"
|
||||
(raw! (highlight "// The async renderer intercepts IO primitive calls\nfunction asyncEval(expr, env) {\n if (IO_PRIMITIVES[head.name]) {\n return asyncEvalIoCall(name, args, env);\n }\n return asyncTrampoline(evalExpr(expr, env));\n}" "javascript")))))
|
||||
|
||||
;; Architecture explanation
|
||||
(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 it works")
|
||||
(ol :class "list-decimal list-inside text-blue-800 space-y-2 text-sm"
|
||||
(li "Server renders the page — " (code "highlight") " runs Python pygments directly")
|
||||
(li "Client receives page with component definitions including " (code "~async-io-demo-content"))
|
||||
(li "On client navigation, " (code "has-io") " flag routes to async renderer")
|
||||
(li "Async renderer encounters " (code "(highlight ...)") " — checks " (code "IO_PRIMITIVES"))
|
||||
(li "Proxied call: " (code "fetch(\"/sx/io/highlight?_arg0=...&_arg1=lisp\")"))
|
||||
(li "Server executes, returns SX wire format (quoted HTML string)")
|
||||
(li "Async renderer inserts result via " (code "(raw! ...)") " — renders identically")))
|
||||
|
||||
;; Verification instructions
|
||||
(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" "How to verify async IO rendering")
|
||||
(ol :class "list-decimal list-inside text-amber-700 space-y-1"
|
||||
(li "Open the browser console (F12)")
|
||||
(li "Navigate to another page (e.g. Data Test)")
|
||||
(li "Click back to this page")
|
||||
(li "Look for: " (code :class "bg-amber-100 px-1 rounded" "sx:route client+async /isomorphism/async-io"))
|
||||
(li "The code blocks should render identically — same syntax highlighting")
|
||||
(li "Check Network tab: you'll see 3 requests to " (code :class "bg-amber-100 px-1 rounded" "/sx/io/highlight"))))))
|
||||
@@ -107,7 +107,8 @@
|
||||
(dict :label "Roadmap" :href "/isomorphism/")
|
||||
(dict :label "Bundle Analyzer" :href "/isomorphism/bundle-analyzer")
|
||||
(dict :label "Routing Analyzer" :href "/isomorphism/routing-analyzer")
|
||||
(dict :label "Data Test" :href "/isomorphism/data-test")))
|
||||
(dict :label "Data Test" :href "/isomorphism/data-test")
|
||||
(dict :label "Async IO" :href "/isomorphism/async-io")))
|
||||
|
||||
(define plans-nav-items (list
|
||||
(dict :label "Reader Macros" :href "/plans/reader-macros"
|
||||
|
||||
@@ -444,6 +444,17 @@
|
||||
:server-time server-time :items items
|
||||
:phase phase :transport transport))
|
||||
|
||||
(defpage async-io-demo
|
||||
:path "/isomorphism/async-io"
|
||||
:auth :public
|
||||
:layout (:sx-section
|
||||
:section "Isomorphism"
|
||||
:sub-label "Isomorphism"
|
||||
:sub-href "/isomorphism/"
|
||||
:sub-nav (~section-nav :items isomorphism-nav-items :current "Async IO")
|
||||
:selected "Async IO")
|
||||
:content (~async-io-demo-content))
|
||||
|
||||
;; Wildcard must come AFTER specific routes (first-match routing)
|
||||
(defpage isomorphism-page
|
||||
:path "/isomorphism/<slug>"
|
||||
|
||||
Reference in New Issue
Block a user