diff --git a/shared/static/wasm/sx/orchestration.sx b/shared/static/wasm/sx/orchestration.sx
index d12e9d57..46267717 100644
--- a/shared/static/wasm/sx/orchestration.sx
+++ b/shared/static/wasm/sx/orchestration.sx
@@ -300,7 +300,8 @@
(with-transition use-transition
(fn ()
(let ((swap-result (swap-dom-nodes target content swap-style)))
- (post-swap (or swap-result target))))))))))))
+ (post-swap (or swap-result target)))))))))))))
+
(define handle-html-response :effects [mutation io]
@@ -1083,7 +1084,7 @@
(with-transition use-transition
(fn ()
(swap-html-string target trimmed swap-style)
- (post-swap target))))))))
+ (post-swap target)))))))))
;; --------------------------------------------------------------------------
diff --git a/shared/sx/helpers.py b/shared/sx/helpers.py
index 10211149..95367c7f 100644
--- a/shared/sx/helpers.py
+++ b/shared/sx/helpers.py
@@ -7,6 +7,7 @@ page elements (headers, search, etc.) from template context.
from __future__ import annotations
import hashlib
+import os
from pathlib import Path
from typing import Any
@@ -884,12 +885,14 @@ def _get_shell_static() -> dict[str, Any]:
sx_css_classes=sx_css_classes,
sx_js_hash=_script_hash("sx-browser.js"),
body_js_hash=_script_hash("body.js"),
+ wasm_hash=_wasm_hash("sx_browser.bc.js"),
asset_url=_ca.config.get("ASSET_URL", "/static"),
head_scripts=_shell_cfg.get("head_scripts"),
inline_css=_shell_cfg.get("inline_css"),
inline_head_js=_shell_cfg.get("inline_head_js"),
init_sx=_shell_cfg.get("init_sx"),
body_scripts=_shell_cfg.get("body_scripts"),
+ use_wasm=os.environ.get("SX_USE_WASM") == "1",
)
t1 = time.monotonic()
@@ -1081,8 +1084,11 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *,
# Tail: bootstrap suspense resolver + scripts + close
tail = (
_SX_STREAMING_BOOTSTRAP + '\n'
- f'\n'
- f'\n'
+ + (f'\n'
+ f'\n'
+ if os.environ.get("SX_USE_WASM") == "1" else
+ f'\n')
+ + f'\n'
)
return shell, tail
@@ -1120,6 +1126,18 @@ def _script_hash(filename: str) -> str:
return _SCRIPT_HASH_CACHE[filename]
+def _wasm_hash(filename: str) -> str:
+ """Compute MD5 hash of a static wasm file, cached for process lifetime."""
+ key = "wasm/" + filename
+ if key not in _SCRIPT_HASH_CACHE:
+ try:
+ data = (Path(__file__).resolve().parent.parent / "static" / "wasm" / filename).read_bytes()
+ _SCRIPT_HASH_CACHE[key] = hashlib.md5(data).hexdigest()[:8]
+ except OSError:
+ _SCRIPT_HASH_CACHE[key] = "dev"
+ return _SCRIPT_HASH_CACHE[key]
+
+
def _get_csrf_token() -> str:
"""Get the CSRF token from the current request context."""
try:
diff --git a/shared/sx/ocaml_bridge.py b/shared/sx/ocaml_bridge.py
index 182672d3..2e22281a 100644
--- a/shared/sx/ocaml_bridge.py
+++ b/shared/sx/ocaml_bridge.py
@@ -190,7 +190,7 @@ class OcamlBridge:
# Only inject small, safe values as kernel variables.
# Large/complex blobs use placeholder tokens at render time.
for key in ("component_hash", "sx_css_classes", "asset_url",
- "sx_js_hash", "body_js_hash"):
+ "sx_js_hash", "body_js_hash", "wasm_hash"):
val = static.get(key) or ""
var = f"__shell-{key.replace('_', '-')}"
defn = f'(define {var} "{_escape(str(val))}")'
@@ -268,7 +268,7 @@ class OcamlBridge:
"sx_css", "inline_css", "inline_head_js"}
placeholders = {}
static_keys = {"component_hash", "sx_css_classes", "asset_url",
- "sx_js_hash", "body_js_hash",
+ "sx_js_hash", "body_js_hash", "wasm_hash",
"head_scripts", "body_scripts"}
# page_source is SX wire format that may contain \" escapes.
# Send via binary blob protocol to avoid double-escaping
diff --git a/shared/sx/templates/shell.sx b/shared/sx/templates/shell.sx
index eb83fb46..1c2ef892 100644
--- a/shared/sx/templates/shell.sx
+++ b/shared/sx/templates/shell.sx
@@ -17,8 +17,10 @@
(pages-sx :as string?) (page-sx :as string?)
(body-html :as string?)
(asset-url :as string) (sx-js-hash :as string) (body-js-hash :as string?)
+ (wasm-hash :as string?)
(head-scripts :as list?) (inline-css :as string?) (inline-head-js :as string?)
- (init-sx :as string?) (body-scripts :as list?))
+ (init-sx :as string?) (body-scripts :as list?)
+ (use-wasm :as boolean?))
(<>
(raw! "")
(html :lang "en"
@@ -83,7 +85,12 @@ details.group{overflow:hidden}details.group>summary{list-style:none}details.grou
(raw! (or pages-sx "")))
(script :type "text/sx" :data-mount "#sx-root"
(raw! (or page-sx "")))
- (script :src (str asset-url "/scripts/sx-browser.js?v=" sx-js-hash))
+ (if use-wasm
+ (let ((wv (or wasm-hash "dev")))
+ (<>
+ (script :src (str asset-url "/wasm/sx_browser.bc.js?v=" wv))
+ (script :src (str asset-url "/wasm/sx-platform.js?v=" wv))))
+ (script :src (str asset-url "/scripts/sx-browser.js?v=" sx-js-hash)))
;; Body scripts — configurable per app
;; Pass a list (even empty) to override defaults; nil = use defaults
(if (not (nil? body-scripts))
diff --git a/web/orchestration.sx b/web/orchestration.sx
index d12e9d57..46267717 100644
--- a/web/orchestration.sx
+++ b/web/orchestration.sx
@@ -300,7 +300,8 @@
(with-transition use-transition
(fn ()
(let ((swap-result (swap-dom-nodes target content swap-style)))
- (post-swap (or swap-result target))))))))))))
+ (post-swap (or swap-result target)))))))))))))
+
(define handle-html-response :effects [mutation io]
@@ -1083,7 +1084,7 @@
(with-transition use-transition
(fn ()
(swap-html-string target trimmed swap-style)
- (post-swap target))))))))
+ (post-swap target)))))))))
;; --------------------------------------------------------------------------