Files
rose-ash/hosts/ocaml/browser/bundle.sh
giles 0caa965de0
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 23m17s
OCaml CEK machine compiled to WebAssembly for browser execution
- 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>
2026-03-16 07:13:49 +00:00

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}'