All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 23m17s
- wasm_of_ocaml compiles OCaml SX engine to WASM (722/722 spec tests) - js_of_ocaml fallback also working (722/722 spec tests) - Thin JS platform layer (sx-platform.js) with ~80 DOM/browser natives - Lambda callback bridge: SX lambdas callable from JS via handle table - Side-channel pattern bypasses js_of_ocaml return-value property stripping - Web adapters (signals, deps, router, adapter-html) load as SX source - Render mode dispatch: HTML tags + fragments route to OCaml renderer - Island/component accessors handle both Component and Island types - Dict-based signal support (signals.sx creates dicts, not native Signal) - Scope stack implementation (collect!/collected/emit!/emitted/context) - Bundle script embeds web adapters + WASM loader + platform layer - SX_USE_WASM env var toggles WASM engine in dev/production - Bootstrap extended: --web flag transpiles web adapters, :effects stripping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
140 lines
4.6 KiB
Bash
Executable File
140 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Bundle the WASM engine + platform + web adapters into shared/static/scripts/
|
|
#
|
|
# Usage: hosts/ocaml/browser/bundle.sh
|
|
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/../../.."
|
|
|
|
WASM_LOADER="hosts/ocaml/_build/default/browser/sx_browser.bc.wasm.js"
|
|
WASM_ASSETS="hosts/ocaml/_build/default/browser/sx_browser.bc.wasm.assets"
|
|
PLATFORM="hosts/ocaml/browser/sx-platform.js"
|
|
OUT="shared/static/scripts/sx-wasm.js"
|
|
ASSET_DIR="shared/static/scripts/sx-wasm-assets"
|
|
|
|
if [ ! -f "$WASM_LOADER" ]; then
|
|
echo "Build first: cd hosts/ocaml && eval \$(opam env) && dune build browser/sx_browser.bc.wasm.js"
|
|
exit 1
|
|
fi
|
|
|
|
# 1. WASM loader (patched asset path)
|
|
sed 's|"src":"sx_browser.bc.wasm.assets"|"src":"sx-wasm-assets"|' \
|
|
"$WASM_LOADER" > "$OUT"
|
|
|
|
# 2. Platform layer
|
|
echo "" >> "$OUT"
|
|
cat "$PLATFORM" >> "$OUT"
|
|
|
|
# 3. Embedded web adapters — SX source as JS string constants
|
|
echo "" >> "$OUT"
|
|
echo "// =========================================================================" >> "$OUT"
|
|
echo "// Embedded web adapters (loaded into WASM engine at boot)" >> "$OUT"
|
|
echo "// =========================================================================" >> "$OUT"
|
|
echo "globalThis.__sxAdapters = {};" >> "$OUT"
|
|
|
|
# Adapters to embed (order matters for dependencies)
|
|
ADAPTERS="signals deps page-helpers router adapter-html"
|
|
|
|
for name in $ADAPTERS; do
|
|
file="web/${name}.sx"
|
|
if [ -f "$file" ]; then
|
|
echo -n "globalThis.__sxAdapters[\"${name}\"] = " >> "$OUT"
|
|
# Escape the SX source for embedding in a JS string
|
|
python3 -c "
|
|
import json, sys
|
|
with open('$file') as f:
|
|
print(json.dumps(f.read()) + ';')
|
|
" >> "$OUT"
|
|
fi
|
|
done
|
|
|
|
# 4. Boot shim
|
|
cat >> "$OUT" << 'BOOT'
|
|
|
|
// =========================================================================
|
|
// WASM Boot: load adapters, then process inline <script type="text/sx">
|
|
// =========================================================================
|
|
(function() {
|
|
"use strict";
|
|
if (typeof document === "undefined") return;
|
|
|
|
function sxWasmBoot() {
|
|
var K = globalThis.SxKernel;
|
|
if (!K || !globalThis.Sx) { setTimeout(sxWasmBoot, 50); return; }
|
|
|
|
console.log("[sx-wasm] booting, engine:", K.engine());
|
|
|
|
// Load embedded web adapters
|
|
var adapters = globalThis.__sxAdapters || {};
|
|
var adapterOrder = ["signals", "deps", "page-helpers", "router", "adapter-html"];
|
|
for (var j = 0; j < adapterOrder.length; j++) {
|
|
var name = adapterOrder[j];
|
|
if (adapters[name]) {
|
|
var r = K.loadSource(adapters[name]);
|
|
if (typeof r === "string" && r.startsWith("Error:")) {
|
|
console.error("[sx-wasm] adapter " + name + " error:", r);
|
|
} else {
|
|
console.log("[sx-wasm] loaded " + name + " (" + r + " defs)");
|
|
}
|
|
}
|
|
}
|
|
delete globalThis.__sxAdapters; // Free memory
|
|
|
|
// Process <script type="text/sx" data-components>
|
|
var scripts = document.querySelectorAll('script[type="text/sx"]');
|
|
for (var i = 0; i < scripts.length; i++) {
|
|
var s = scripts[i], src = s.textContent.trim();
|
|
if (!src) continue;
|
|
if (s.hasAttribute("data-components")) {
|
|
var result = K.loadSource(src);
|
|
if (typeof result === "string" && result.startsWith("Error:"))
|
|
console.error("[sx-wasm] component load error:", result);
|
|
}
|
|
}
|
|
|
|
// Process <script type="text/sx" data-init>
|
|
for (var i = 0; i < scripts.length; i++) {
|
|
var s = scripts[i];
|
|
if (s.hasAttribute("data-init")) {
|
|
var src = s.textContent.trim();
|
|
if (src) K.loadSource(src);
|
|
}
|
|
}
|
|
|
|
// Process <script type="text/sx" data-mount="...">
|
|
for (var i = 0; i < scripts.length; i++) {
|
|
var s = scripts[i];
|
|
if (s.hasAttribute("data-mount")) {
|
|
var mount = s.getAttribute("data-mount"), src = s.textContent.trim();
|
|
if (!src) continue;
|
|
var target = mount === "body" ? document.body : document.querySelector(mount);
|
|
if (!target) continue;
|
|
try {
|
|
var parsed = K.parse(src);
|
|
if (parsed && parsed.length > 0) {
|
|
var html = K.renderToHtml(parsed[0]);
|
|
if (html && typeof html === "string") {
|
|
target.innerHTML = html;
|
|
console.log("[sx-wasm] mounted to", mount);
|
|
}
|
|
}
|
|
} catch(e) { console.error("[sx-wasm] mount error:", e); }
|
|
}
|
|
}
|
|
|
|
console.log("[sx-wasm] boot complete");
|
|
}
|
|
|
|
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", sxWasmBoot);
|
|
else sxWasmBoot();
|
|
})();
|
|
BOOT
|
|
|
|
# 5. Copy WASM assets
|
|
mkdir -p "$ASSET_DIR"
|
|
cp "$WASM_ASSETS"/*.wasm "$ASSET_DIR/"
|
|
|
|
echo "=== Bundle complete ==="
|
|
ls -lh "$OUT"
|
|
echo -n "WASM assets: "; du -sh "$ASSET_DIR" | awk '{print $1}'
|