wasm backend: ghc_wasm_jsffi.js uses top-level await, cannot be loaded from a module ServiceWorker
## Summary
`utils/jsffi/prelude.mjs` initializes `setImmediate` with a top-level
`await` on an async IIFE (lines 42–91), so every `ghc_wasm_jsffi.js`
emitted by `post-link.mjs` is an async ESM module. This blocks module
ServiceWorkers, which spec-reject registration of async modules — see
[w3c/ServiceWorker#1407](https://github.com/w3c/ServiceWorker/issues/1407)
(closed by [PR #1444](https://github.com/w3c/ServiceWorker/pull/1444),
which adds a `TypeError`-throwing branch for async module scripts in
the Update algorithm).
The `await` exists only for the Deno branch (`await import("node:timers")`,
line 51); the four other branches are sync. Two possible fixes that
keep behavior identical and remove the TLA:
- Replace the dynamic `import()` with
`createRequire(import.meta.url)("node:timers")`, which is sync; or
- Drop the Deno branch entirely — Deno ≥ 2.4 exposes
`globalThis.setImmediate` unconditionally (earlier 2.x needed
`--unstable-node-globals`), so the first sync branch already covers it.
Source:
<https://gitlab.haskell.org/ghc/ghc/-/blob/master/utils/jsffi/prelude.mjs>
## Steps to reproduce
1. Build a minimal reactor module and post-link it:
```sh
cat > Main.hs <<'EOF'
module Main where
import GHC.Wasm.Prim
main :: IO ()
main = pure ()
foreign export javascript "setup" setup :: IO ()
setup :: IO ()
setup = pure ()
EOF
wasm32-wasi-ghc -no-hs-main \
-optl-Wl,--export=setup \
-optl-mexec-model=reactor \
-o Main.wasm Main.hs
"$(wasm32-wasi-ghc --print-libdir)/post-link.mjs" \
-i Main.wasm -o ghc_wasm_jsffi.js
```
2. Inspect `ghc_wasm_jsffi.js` — `setImmediate` is initialized via
`await (async () => { ... })()` at module top level.
3. Try to load it (directly or transitively) from a module
ServiceWorker. Minimal `sw.js`:
```js
// sw.js
import "./ghc_wasm_jsffi.js";
// install/activate handlers omitted — registration rejects
// before they fire.
```
Page-side:
```js
await navigator.serviceWorker.register("./sw.js", { type: "module" });
```
Observed (Chrome v148):
```
installHook.js:1 SW registration error: TypeError: Failed to register a ServiceWorker for scope ('http://localhost:8000/') with script ('http://localhost:8000/sw.js'): ServiceWorker cannot be started
overrideMethod @ installHook.js:1
```
Registration rejects because the SW module graph contains TLA.
## Expected behavior
`ghc_wasm_jsffi.js` should be a synchronous ESM module so it can be
imported from a module ServiceWorker (and from any other consumer
that can't host TLA).
## Environment
* GHC version used: 9.14.1.20260330 (`ghc-wasm-meta` master nightly)
Optional:
* Operating System: macOS
* System Architecture: x86_64
---
_Drafted with help from Claude; please flag any factual errors._
issue