From 019a617d05cc32efaa322f3b26315bd4628548ac Mon Sep 17 00:00:00 2001 From: Jason Gross Date: Tue, 5 May 2026 17:18:25 -0700 Subject: [PATCH 1/2] Wasm: dispatch lifecycle events when async instantiation completes Compiling and instantiating the generated WebAssembly is asynchronous, but the launcher only returns a Promise that callers typically don't await. Surrounding JavaScript that runs on DOMContentLoaded therefore has no way to tell when the OCaml exports are actually ready. Dispatch 'wasmoocaml:loaded' (and 'wasmoocaml:error' on failure) CustomEvents on globalThis so external code can wait for the runtime to be ready. Ported from ocaml-wasm/wasm_of_ocaml#143. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGES.md | 3 ++ runtime/wasm/runtime.js | 73 +++++++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 58c8e63b5e..32fb05bd1d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,9 @@ * Compiler: improved shape computation (#2198) * Add the --build-config and --apply-build-config flags (#2177) * Runtime/wasm: optimized some bigstring primitives (#2144) +* Runtime/wasm: dispatch `wasmoocaml:loaded` and `wasmoocaml:error` + `CustomEvent`s on `globalThis` so that surrounding JavaScript can wait + for the asynchronous Wasm instantiation to complete ## Bug fixes * Compiler: fix reference unboxing (#2210) diff --git a/runtime/wasm/runtime.js b/runtime/wasm/runtime.js index 84c5a0ad08..568fbe3942 100644 --- a/runtime/wasm/runtime.js +++ b/runtime/wasm/runtime.js @@ -774,34 +774,49 @@ } return { instance: { exports: Object.assign(imports.env, imports.OCaml) } }; } - const wasmModule = await instantiateFromDir(); - - var { - caml_callback, - caml_alloc_times, - caml_alloc_tm, - caml_alloc_stat, - caml_start_fiber, - caml_handle_uncaught_exception, - caml_buffer, - caml_extract_bytes, - _initialize, - } = wasmModule.instance.exports; - - var buffer = caml_buffer?.buffer; - var out_buffer = buffer && new Uint8Array(buffer, 0, buffer.length); - - start_fiber = make_promising(caml_start_fiber); - var _initialize = make_promising(_initialize); - if (globalThis.process?.on) { - globalThis.process.on("uncaughtException", (err, _origin) => - caml_handle_uncaught_exception(err), - ); - } else if (globalThis.addEventListener) { - globalThis.addEventListener( - "error", - (event) => event.error && caml_handle_uncaught_exception(event.error), - ); + function dispatchLifecycleEvent(name, detail) { + if ( + typeof globalThis.dispatchEvent === "function" && + typeof CustomEvent === "function" + ) { + globalThis.dispatchEvent(new CustomEvent(name, { detail })); + } + } + + try { + const wasmModule = await instantiateFromDir(); + + var { + caml_callback, + caml_alloc_times, + caml_alloc_tm, + caml_alloc_stat, + caml_start_fiber, + caml_handle_uncaught_exception, + caml_buffer, + caml_extract_bytes, + _initialize, + } = wasmModule.instance.exports; + + var buffer = caml_buffer?.buffer; + var out_buffer = buffer && new Uint8Array(buffer, 0, buffer.length); + + start_fiber = make_promising(caml_start_fiber); + var _initialize = make_promising(_initialize); + if (globalThis.process?.on) { + globalThis.process.on("uncaughtException", (err, _origin) => + caml_handle_uncaught_exception(err), + ); + } else if (globalThis.addEventListener) { + globalThis.addEventListener( + "error", + (event) => event.error && caml_handle_uncaught_exception(event.error), + ); + } + await _initialize(); + dispatchLifecycleEvent("wasmoocaml:loaded", { src }); + } catch (error) { + dispatchLifecycleEvent("wasmoocaml:error", { src, error }); + throw error; } - await _initialize(); }; From e5fc28c28b7db510ea817f4381d73ef03f5bbc59 Mon Sep 17 00:00:00 2001 From: Jason Gross Date: Tue, 5 May 2026 17:22:32 -0700 Subject: [PATCH 2/2] Reserved: register CustomEvent as a known global The free-variable check in compiler/bin-wasm_of_ocaml/gen/gen.ml flagged the new CustomEvent reference in runtime.js. CustomEvent is a standard DOM/Node global comparable to XMLHttpRequest and DOMException already in this list. Co-Authored-By: Claude Opus 4.7 (1M context) --- compiler/lib/reserved.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/lib/reserved.ml b/compiler/lib/reserved.ml index 1f5c87c834..baedb3f7cf 100644 --- a/compiler/lib/reserved.ml +++ b/compiler/lib/reserved.ml @@ -143,6 +143,7 @@ let provided = ; "btoa" ; "clearInterval" ; "console" + ; "CustomEvent" ; "global" (* only available in node *) ; "importScripts" (* only available in WebWorker *) ; "performance" (* Not available in node until v16+ *)