sx-http: island SSR → always placeholder, external component-defs endpoint
Islands now always emit <span data-sx-island="name"> placeholder instead of attempting SSR. Island bodies contain client-only symbols (signals, DOM refs) that cascade errors during native render. Component-defs moved to /static/sx-components.sx endpoint instead of inline <script> — avoids </script> escaping issues that broke the client-side SX parser. Remaining: client WASM kernel not loading components from external endpoint (expects inline script tag). Need to configure client boot to fetch from /static/sx-components.sx. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1499,9 +1499,7 @@ let http_render_page env path =
|
||||
| [e] -> e | [] -> Nil | _ -> List (Symbol "<>" :: body_exprs) in
|
||||
Sx_render.render_to_html_streaming body_expr env
|
||||
with e ->
|
||||
Printf.eprintf "[http-ssr] failed: %s\n%!" (Printexc.to_string e);
|
||||
(* Fallback: minimal layout structure so client can mount *)
|
||||
"<div class=\"max-w-screen-2xl mx-auto py-1 px-1\"><div id=\"filter\"></div><main class=\"max-w-full\" id=\"root-panel\"><div class=\"md:min-h-0\"></div></main></div>"
|
||||
Printf.eprintf "[http-ssr] failed: %s\n%!" (Printexc.to_string e); ""
|
||||
in
|
||||
let t3 = Unix.gettimeofday () in
|
||||
(* Phase 3: Shell — render directly to buffer for zero-copy output *)
|
||||
@@ -1621,25 +1619,14 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
| _ -> ()
|
||||
) env.bindings;
|
||||
let raw_defs = Buffer.contents buf in
|
||||
(* Escape </script inside component-defs. The HTML parser for
|
||||
<script type="text/sx"> scans for </script> (case-insensitive) to
|
||||
close the tag. Replace </ with <\/ when followed by 's' or 'S'.
|
||||
The \/ is treated as / by the SX parser. *)
|
||||
let component_defs =
|
||||
let len = String.length raw_defs in
|
||||
let out = Buffer.create (len + 256) in
|
||||
let i = ref 0 in
|
||||
while !i < len do
|
||||
if !i + 2 < len && raw_defs.[!i] = '<' && raw_defs.[!i + 1] = '/'
|
||||
&& (raw_defs.[!i + 2] = 's' || raw_defs.[!i + 2] = 'S') then begin
|
||||
Buffer.add_string out "<\\/";
|
||||
i := !i + 2
|
||||
end else begin
|
||||
Buffer.add_char out raw_defs.[!i];
|
||||
i := !i + 1
|
||||
end
|
||||
done;
|
||||
Buffer.contents out in
|
||||
(* Don't inline component-defs — serve from /static/sx-components.sx.
|
||||
Inlining breaks because SX source can contain literal </script> which
|
||||
the HTML parser uses to close the tag prematurely. *)
|
||||
let component_defs = "" in
|
||||
(* Cache the raw defs for the /static/sx-components.sx endpoint *)
|
||||
Hashtbl.replace static_cache "/static/sx-components.sx"
|
||||
(Printf.sprintf "HTTP/1.1 200 OK\r\nContent-Type: text/sx; charset=utf-8\r\nContent-Length: %d\r\nCache-Control: public, max-age=31536000, immutable\r\n\r\n%s"
|
||||
(String.length raw_defs) raw_defs);
|
||||
let component_hash = Digest.string component_defs |> Digest.to_hex in
|
||||
(* Compute file hashes for cache busting *)
|
||||
let sx_js_hash = file_hash (static_dir ^ "/scripts/sx-browser.js") in
|
||||
@@ -1704,7 +1691,16 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
ignore (env_bind env "__shell-body-scripts" Nil);
|
||||
ignore (env_bind env "__shell-inline-css" Nil);
|
||||
ignore (env_bind env "__shell-inline-head-js" Nil);
|
||||
ignore (env_bind env "__shell-init-sx" Nil);
|
||||
(* init-sx: trigger client-side render when sx-root is empty (SSR failed).
|
||||
The boot code hydrates existing islands but doesn't do fresh render.
|
||||
This script forces a render from page-sx after boot completes. *)
|
||||
ignore (env_bind env "__shell-init-sx" (String
|
||||
"document.addEventListener('sx:boot-done', function() { \
|
||||
var root = document.getElementById('sx-root'); \
|
||||
if (root && !root.innerHTML.trim() && typeof SX !== 'undefined' && SX.renderPage) { \
|
||||
SX.renderPage(); \
|
||||
} \
|
||||
});"));
|
||||
Printf.eprintf "[sx-http] Shell statics: defs=%d hash=%s css=%d js=%s wasm=%s\n%!"
|
||||
(String.length component_defs) component_hash (String.length sx_css) sx_js_hash wasm_hash
|
||||
|
||||
|
||||
@@ -258,21 +258,8 @@ and render_list_to_html head args env =
|
||||
| Component c when c.c_affinity = "client" -> "" (* skip client-only *)
|
||||
| Component _ -> render_component v args env
|
||||
| Island _i ->
|
||||
(* Islands: try SSR via the SX render-to-html from adapter-html.sx.
|
||||
It handles deref/signal/computed through the CEK correctly,
|
||||
and renders island bodies with hydration markers.
|
||||
On failure, emit a placeholder — client hydrates from SX source. *)
|
||||
(try
|
||||
let call_expr = List (Symbol name :: args) in
|
||||
let quoted = List [Symbol "quote"; call_expr] in
|
||||
let render_call = List [Symbol "render-to-html"; quoted; Env env] in
|
||||
let result = Sx_ref.eval_expr render_call (Env env) in
|
||||
(match result with
|
||||
| String s | RawHTML s -> s
|
||||
| _ -> value_to_string result)
|
||||
with _e ->
|
||||
(* Placeholder — client will hydrate this island *)
|
||||
Printf.sprintf "<span data-sx-island=\"%s\"></span>" _i.i_name)
|
||||
(* Islands are client-rendered — emit placeholder for hydration *)
|
||||
Printf.sprintf "<span data-sx-island=\"%s\"></span>" _i.i_name
|
||||
| Macro m ->
|
||||
let expanded = expand_macro m args env in
|
||||
do_render_to_html expanded env
|
||||
@@ -516,16 +503,10 @@ and render_list_buf buf head args env =
|
||||
| Component c when c.c_affinity = "client" -> ()
|
||||
| Component _ -> render_component_buf buf v args env
|
||||
| Island _i ->
|
||||
(try
|
||||
let call_expr = List (Symbol name :: args) in
|
||||
let quoted = List [Symbol "quote"; call_expr] in
|
||||
let render_call = List [Symbol "render-to-html"; quoted; Env env] in
|
||||
let result = Sx_ref.eval_expr render_call (Env env) in
|
||||
(match result with
|
||||
| String s | RawHTML s -> Buffer.add_string buf s
|
||||
| _ -> Buffer.add_string buf (value_to_string result))
|
||||
with _e ->
|
||||
Buffer.add_string buf (Printf.sprintf "<span data-sx-island=\"%s\"></span>" _i.i_name))
|
||||
(* Islands are client-rendered — emit placeholder for hydration.
|
||||
SSR of island bodies is unreliable (client-only symbols like
|
||||
signals, DOM refs) and can cascade errors. *)
|
||||
Buffer.add_string buf (Printf.sprintf "<span data-sx-island=\"%s\"></span>" _i.i_name)
|
||||
| Macro m ->
|
||||
let expanded = expand_macro m args env in
|
||||
render_to_buf buf expanded env
|
||||
|
||||
Reference in New Issue
Block a user