From 451444c5532c5e88692a87d198d3c2936b31ada9 Mon Sep 17 00:00:00 2001 From: Jason Gross Date: Tue, 5 May 2026 11:58:52 -0700 Subject: [PATCH] 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. Fixes #11. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGES.md | 3 ++ runtime/wasm/runtime.js | 69 +++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2e8ad43b6..51dc6c3c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,9 @@ ## Features/Changes * Lib: fix the type of some DOM properties and methods (#1747) * Test: use dune test stanzas (#1631) +* Wasm: dispatch `wasmoocaml:loaded` and `wasmoocaml:error` `CustomEvent`s on + `globalThis` so that surrounding JavaScript can wait for the asynchronous + Wasm instantiation to complete (#11) # 5.9.1 (02-12-2024) - Lille diff --git a/runtime/wasm/runtime.js b/runtime/wasm/runtime.js index 03a88e629..733159cf7 100644 --- a/runtime/wasm/runtime.js +++ b/runtime/wasm/runtime.js @@ -531,35 +531,50 @@ } return { instance: { exports: Object.assign(imports.env, imports.OCaml) } }; } - const wasmModule = await instantiateFromDir(); + 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_tm, - caml_start_fiber, - caml_handle_uncaught_exception, - caml_buffer, - caml_extract_string, - string_get, - string_set, - _initialize, - } = wasmModule.instance.exports; + var { + caml_callback, + caml_alloc_tm, + caml_start_fiber, + caml_handle_uncaught_exception, + caml_buffer, + caml_extract_string, + string_get, + string_set, + _initialize, + } = wasmModule.instance.exports; - var buffer = caml_buffer?.buffer; - var out_buffer = buffer && new Uint8Array(buffer, 0, buffer.length); + 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); - var process = globalThis.process; - if (process && process.on) { - 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), - ); + start_fiber = make_promising(caml_start_fiber); + var _initialize = make_promising(_initialize); + var process = globalThis.process; + if (process && process.on) { + 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(); };