diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e5c49..fb0830b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Start auxiliary WebAssembly workers through a generated module entry that + initializes their wasm-bindgen bindings. + ## 0.1.13 - 2026-08-27 - Build and publish configured auxiliary WebAssembly worker binaries in the diff --git a/lib/rekindle/web/builder.ex b/lib/rekindle/web/builder.ex index c6162f8..03d4362 100644 --- a/lib/rekindle/web/builder.ex +++ b/lib/rekindle/web/builder.ex @@ -128,7 +128,8 @@ defmodule Rekindle.Web.Builder do with {:ok, cargo} <- Rekindle.Cargo.build_worker(project, target, worker, profile, cargo_options(options)), :ok <- create_worker_directory(directory), - :ok <- bindgen(wasm_bindgen, cargo.artifact, directory, "worker", options), + :ok <- bindgen(wasm_bindgen, cargo.artifact, directory, "bindings", options), + :ok <- write_worker_entry(directory), :ok <- ensure_worker(directory) do {:ok, %{ @@ -147,12 +148,26 @@ defmodule Rekindle.Web.Builder do end end + defp write_worker_entry(directory) do + path = Path.join(directory, "worker.js") + source = "import init from \"./bindings.js\";\n\nawait init();\n" + + case File.write(path, source) do + :ok -> :ok + {:error, reason} -> file_error(:worker_entry, path, reason) + end + end + defp ensure_worker(directory) do if File.regular?(Path.join(directory, "worker.js")) and - File.regular?(Path.join(directory, "worker_bg.wasm")) do + File.regular?(Path.join(directory, "bindings.js")) and + File.regular?(Path.join(directory, "bindings_bg.wasm")) do :ok else - error(:missing_worker, "wasm-bindgen did not produce worker.js and worker_bg.wasm") + error( + :missing_worker, + "the worker bootstrap or wasm-bindgen bindings are missing" + ) end end diff --git a/test/integration/development_test.exs b/test/integration/development_test.exs index 777be16..4f98c2c 100644 --- a/test/integration/development_test.exs +++ b/test/integration/development_test.exs @@ -321,13 +321,14 @@ defmodule Rekindle.DevelopmentTest do worker = request("/@rekindle/web/#{generation}/workers/storage/worker.js", options) assert worker.status == 200 + assert worker.resp_body == "import init from \"./bindings.js\";\n\nawait init();\n" assert Plug.Conn.get_resp_header(worker, "content-type") == [ "text/javascript; charset=utf-8" ] worker_wasm = - request("/@rekindle/web/#{generation}/workers/storage/worker_bg.wasm", options) + request("/@rekindle/web/#{generation}/workers/storage/bindings_bg.wasm", options) assert worker_wasm.status == 200 assert Plug.Conn.get_resp_header(worker_wasm, "content-type") == ["application/wasm"] @@ -427,6 +428,38 @@ defmodule Rekindle.DevelopmentTest do refute Process.alive?(browser_process) end + test "starts a published auxiliary module worker in the browser", %{root: root} do + Application.put_env(:rekindle_development_test, Rekindle, + plugin: Rekindle.Plugin.Egui, + targets: [web: [], desktop: []] + ) + + publish_web(root, """ + export default async function init() { + const worker = new Worker( + new URL("./workers/storage/worker.js", import.meta.url), + {type: "module"} + ); + worker.addEventListener("message", async ({data}) => { + if (data === "worker-ready") { + await fetch("/__test/loaded/worker", {method: "POST"}); + } + }); + } + """) + + {browser, url, browser_directory} = start_browser_server(root, :reload) + on_exit(fn -> File.rm_rf(browser_directory) end) + + {browser_id, browser_process} = run_browser(browser, url, browser_directory) + + assert_receive :browser_document_requested, @browser_start_timeout + assert_receive {:browser_loaded, "worker"}, 10_000 + assert Process.alive?(browser_process) + assert :ok = stop_supervised(browser_id) + refute Process.alive?(browser_process) + end + test "backs off repeated WebSocket connections that close before state arrives", %{root: root} do Application.put_env(:rekindle_development_test, Rekindle, plugin: Rekindle.Plugin.Egui, @@ -642,11 +675,16 @@ defmodule Rekindle.DevelopmentTest do File.write!( Path.join(temporary, "workers/storage/worker.js"), - "export default async function init() {}" + "import init from \"./bindings.js\";\n\nawait init();\n" + ) + + File.write!( + Path.join(temporary, "workers/storage/bindings.js"), + "export default async function init() { postMessage(\"worker-ready\"); }" ) File.write!( - Path.join(temporary, "workers/storage/worker_bg.wasm"), + Path.join(temporary, "workers/storage/bindings_bg.wasm"), <<0, 97, 115, 109>> ) diff --git a/test/integration/web_build_test.exs b/test/integration/web_build_test.exs index 6aab9cc..66c5898 100644 --- a/test/integration/web_build_test.exs +++ b/test/integration/web_build_test.exs @@ -159,7 +159,11 @@ defmodule Rekindle.WebBuildTest do assert File.regular?(Path.join(generation_root, "main.js")) assert File.regular?(Path.join(generation_root, "main_bg.wasm")) assert File.regular?(Path.join(generation_root, "workers/storage/worker.js")) - assert File.regular?(Path.join(generation_root, "workers/storage/worker_bg.wasm")) + assert File.regular?(Path.join(generation_root, "workers/storage/bindings.js")) + assert File.regular?(Path.join(generation_root, "workers/storage/bindings_bg.wasm")) + + worker = File.read!(Path.join(generation_root, "workers/storage/worker.js")) + assert worker == "import init from \"./bindings.js\";\n\nawait init();\n" wrapper = File.read!(result.artifact) assert wrapper =~ ~s(import init from "./main.js";) @@ -180,7 +184,8 @@ defmodule Rekindle.WebBuildTest do assert File.regular?(Path.join(generation_root, "main.js")) assert File.regular?(Path.join(generation_root, "main_bg.wasm")) assert File.regular?(Path.join(generation_root, "workers/storage/worker.js")) - assert File.regular?(Path.join(generation_root, "workers/storage/worker_bg.wasm")) + assert File.regular?(Path.join(generation_root, "workers/storage/bindings.js")) + assert File.regular?(Path.join(generation_root, "workers/storage/bindings_bg.wasm")) entry = File.read!(Path.join(root, "priv/static/rekindle/entry.js")) assert entry =~ "// Rekindle generation: #{result.metadata.generation}" @@ -388,7 +393,7 @@ defmodule Rekindle.WebBuildTest do printf 'wasm' > "$output/${out_name}_bg.wasm" exit 0 fi - if [ "$mode" = "missing-worker" ] && [ "$out_name" = "worker" ]; then + if [ "$mode" = "missing-worker" ] && [ "$out_name" = "bindings" ]; then exit 0 fi printf "const wasm = new URL('${out_name}_bg.wasm', import.meta.url);\\n/* %s */\\nexport default async function init() { return wasm; }\\n" "$mode" > "$output/${out_name}.js"