From f0e1039848a37df0022206ede18bc8252deef090 Mon Sep 17 00:00:00 2001 From: gergelyvagujhelyi <13369913+gergelyvagujhelyi@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:52:23 +0200 Subject: [PATCH] fix(frb_rust): cfg-gate `wasm_bindgen::module()` call on emscripten target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `WorkerPool::spawn` calls `wasm_bindgen::module()` to forward the current Wasm module handle to a newly-spawned Web Worker. That intrinsic is only supported by wasm-bindgen-cli's OutputMode::{Web, NoModules, Node, Module} (see `wasm-bindgen-cli-support/src/js/mod.rs` `Intrinsic::Module`, which bails out for Bundler and Emscripten). An emscripten fork with `-sWASM_BINDGEN` integration (e.g. the `walkingeyerobot/emscripten` draft upstream of emscripten-core/emscripten#23493) inserts the `__wasm_bindgen_emscripten_marker` custom section, which forces wasm-bindgen-cli into `OutputMode::Emscripten` unconditionally. In that mode the `__wbindgen_module` intrinsic bails, so post-link fails with: failed to generates bindings for import of `__wbindgen_placeholder__::__wbg___wbindgen_module_` This happens even when the caller never spawns a worker — the mere reference in the dep graph is enough. Web Workers aren't usable from the emscripten target regardless (single-threaded model), so fall back to a null module handle when `target_os = "emscripten"`. Any runtime call to `spawn` would have failed on that target anyway; this just keeps the import out of the wasm so wasm-bindgen post-link can complete. Verified on `wasm32-unknown-emscripten` with wasm-bindgen-cli 0.2.118 + the walkingeyerobot emscripten fork: `cargo build --release --bin nobodywho_flutter_web --target wasm32-unknown-emscripten` now produces a ~15 MB .wasm + matching JS glue that loads and instantiates in Node. --- .../third_party/wasm_bindgen/worker_pool.rs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/frb_rust/src/third_party/wasm_bindgen/worker_pool.rs b/frb_rust/src/third_party/wasm_bindgen/worker_pool.rs index e013e2e9bc..e23d4b45c1 100644 --- a/frb_rust/src/third_party/wasm_bindgen/worker_pool.rs +++ b/frb_rust/src/third_party/wasm_bindgen/worker_pool.rs @@ -4,23 +4,36 @@ use crate::misc::web_utils::script_path; use crate::web_transfer::transfer_closure::TransferClosure; +// `Array`, `Object`, `Reflect`, `FromIterator`, `Blob`, `BlobPropertyBag`, `Url` +// are used only by the non-emscripten `spawn` body; the emscripten stub doesn't +// need them. Gate the imports so emscripten builds stay warning-free. +#[cfg(not(target_os = "emscripten"))] use js_sys::{Array, Object, Reflect}; use std::cell::RefCell; +#[cfg(not(target_os = "emscripten"))] use std::iter::FromIterator; use std::rc::Rc; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; +#[cfg(not(target_os = "emscripten"))] use web_sys::BlobPropertyBag; use web_sys::ErrorEvent; use web_sys::MessageEvent; +#[cfg(not(target_os = "emscripten"))] use web_sys::{Blob, Url}; use web_sys::{Event, Worker}; #[wasm_bindgen] pub struct WorkerPool { state: Rc, + // These fields are consumed only by the non-emscripten `spawn` body. + // `new_raw` still stores them on emscripten (the API signature is shared), + // so suppress the resulting dead-code warning rather than drop the fields. + #[cfg_attr(target_os = "emscripten", allow(dead_code))] script_src: String, + #[cfg_attr(target_os = "emscripten", allow(dead_code))] worker_js_preamble: String, + #[cfg_attr(target_os = "emscripten", allow(dead_code))] wasm_bindgen_name: String, } @@ -77,10 +90,19 @@ impl WorkerPool { worker_js_preamble, wasm_bindgen_name, }; + // On emscripten, `spawn` is a fail-fast stub (see its doc comment); + // eagerly priming the pool here would make `default()` — which uses + // `hardwareConcurrency` as `initial` — panic on startup for any + // program that just links the library, even if it never dispatches + // worker-bound work. Leave the pool empty on that target; explicit + // calls to `spawn` still return the same descriptive `Err`. + #[cfg(not(target_os = "emscripten"))] for _ in 0..initial { let worker = pool.spawn()?; pool.state.push(worker); } + #[cfg(target_os = "emscripten")] + let _ = initial; // silence unused-variable warning on emscripten Ok(pool) } @@ -94,6 +116,12 @@ impl WorkerPool { /// /// Returns any error that may happen while a JS web worker is created and a /// message is sent to it. + /// + /// On `target_os = "emscripten"` this always returns `Err`: the worker init + /// path calls [`wasm_bindgen::module`], which is unsupported in + /// wasm-bindgen-cli's `OutputMode::Emscripten`. See the emscripten stub of + /// this function for the full context. + #[cfg(not(target_os = "emscripten"))] fn spawn(&self) -> Result { let worker_js_preamble = &self.worker_js_preamble; let script_src = &self.script_src; @@ -148,6 +176,33 @@ impl WorkerPool { Ok(worker) } + /// Emscripten stub — see the non-emscripten variant for the normal + /// implementation. + /// + /// `wasm_bindgen::module()` is unsupported in wasm-bindgen-cli's + /// `OutputMode::Emscripten` (see `wasm-bindgen-cli-support/src/js/mod.rs`, + /// `Intrinsic::Module`, which bails for Bundler and Emscripten). That mode + /// is force-selected when an emscripten fork with `-sWASM_BINDGEN` + /// integration inserts a `__wasm_bindgen_emscripten_marker` custom + /// section, regardless of any explicit `--target` flag. + /// + /// The mere presence of a `wasm_bindgen::module()` call anywhere in the + /// dependency graph fails wasm-bindgen post-link on that target (bindings + /// cannot be generated for the `__wbindgen_module` import), even when + /// `spawn` is never actually called. The `cfg` gate keeps the import out + /// of the wasm entirely. + /// + /// At runtime, fail fast with a clear error rather than returning a + /// dead `Worker` initialised with a null module handle. + #[cfg(target_os = "emscripten")] + fn spawn(&self) -> Result { + Err(JsValue::from_str( + "flutter_rust_bridge::WorkerPool::spawn is not supported on \ + target_os = \"emscripten\": wasm_bindgen::module() is \ + unsupported in wasm-bindgen-cli's OutputMode::Emscripten", + )) + } + /// Fetches a worker from this pool, spawning one if necessary. /// /// This will attempt to pull an already-spawned web worker from our cache