fix(js): build bashkit-js for wasm32-wasip1-threads#2141
Conversation
Lifts the publish-js.yml TODO ("WASM disabled — tokio 'full' features are
unsupported on wasm32") by gating the features that force incompatible
deps onto wasm builds:
- Cargo.toml: split the bashkit dependency by target. wasm32 drops
interop/sqlite (force tokio/rt-multi-thread -> mio -> socket2, which
does not compile on wasm), http_client (reqwest needs tokio::net or a
wasm-bindgen browser backend, neither exists on wasip1), and realfs
(host filesystem is meaningless in a browser sandbox).
- src/lib.rs: cfg-gate the interop fs-handle ABI, sqlite, and network
code paths. Methods inside #[napi] impl blocks branch in the body
rather than gating the item — napi's macro expands the whole block
before cfg stripping and leaves dangling trampoline callbacks
otherwise. callback_runtime() uses the current-thread tokio runtime
on wasm.
- format_support.rs: OsStrExt lives in std::os::wasi::ffi on WASI
targets, not std::os::unix::ffi — the old cfg(any(unix, wasi)) import
only worked on unix.
- package.json: add wasm32-wasip1-threads to napi targets (generates the
browser/worker glue), asyncInit (the ~11MB wasm binary exceeds
Chromium's 8MB sync-compile limit), and the @emnapi/core devDependency
the napi cli requires for wasm builds.
- publish-js.yml: enable the wasm32-wasip1-threads build matrix entry.
- js.yml: add a PR-time debug build of the wasm target so it cannot rot
between releases (publish-js.yml only exercises it at release time).
Verified locally: napi build --target wasm32-wasip1-threads succeeds
warning-free, and the built package runs a full interactive bash session
in headless Chromium (echo/uname/cat/loops) served with COOP/COEP
headers.
chaliy
left a comment
There was a problem hiding this comment.
Two inline findings from local verification against latest origin/main.
| } | ||
| }, | ||
| "devDependencies": { | ||
| "@emnapi/core": "1.10.0", |
There was a problem hiding this comment.
This wasm build path still fails in CI as submitted. Reproduced with the workflow's pinned pnpm@10.33.0: pnpm install --frozen-lockfile succeeds, then pnpm exec napi build --platform --target wasm32-wasip1-threads dies with Cannot find module '@emnapi/runtime'. The napi CLI resolves both @emnapi/core and @emnapi/runtime from the project root for WASI builds, so this needs a matching @emnapi/runtime: 1.10.0 devDependency and lockfile update.
| "x86_64-pc-windows-msvc" | ||
| ] | ||
| "x86_64-pc-windows-msvc", | ||
| "wasm32-wasip1-threads" |
There was a problem hiding this comment.
Adding the wasm target makes the package's existing browser export (./bashkit.wasi-browser.js) active for browser consumers, but the generated file exports the raw napi bindings only. It does not expose the wrapper API advertised by wrapper.d.ts/README, such as FileSystem, Bash.create, or the wrapper custom-builtin adaptation. Please either route the browser condition through a wrapper that re-exports the public API, or expose this as a separate raw wasm entrypoint with matching types/docs.
napi build --target wasm32-wasip1-threads requires both @emnapi/core and @emnapi/runtime from the project's scope (createRequire against the project package.json). runtime is an *optional* peer of @napi-rs/cli, so pnpm never auto-installs it, and @emnapi/core does not depend on it — only @emnapi/wasi-threads. With only core declared, a clean frozen install has no @emnapi/runtime at the root and the build dies in Builder.setWasiEnv with "Cannot find module '@emnapi/runtime'". Verified from the committed state: pnpm install --frozen-lockfile, then napi build --platform --target wasm32-wasip1-threads succeeds.
Problem
The
wasm32-wasip1-threadstarget has been commented out inpublish-js.ymlwith:This PR is that fix:
@everruns/bashkitnow builds forwasm32-wasip1-threads, which makes the npm package runnable in browsers (napi-rs WASI runtime + Web Workers).Changes
Cargo.toml: split thebashkitdependency by target. wasm32 drops four features, each for a concrete reason (documented inline):interop/sqliteforcetokio/rt-multi-thread→ mio → socket2, which does not compile on any wasm targethttp_client: reqwest needstokio::net(real sockets) or its wasm-bindgen browser backend; neither exists on wasip1realfs: mounting the host filesystem is meaningless in a browser sandboxsrc/lib.rs: cfg-gate the interop fs-handle ABI, sqlite, and network code paths. Methods inside#[napi] implblocks branch in the body instead of gating the item — napi's macro expands the whole block before cfg stripping and otherwise leaves dangling trampoline references.callback_runtime()uses tokio's current-thread runtime on wasm.format_support.rs(bashkit core):OsStrExtlives instd::os::wasi::ffion WASI targets, notstd::os::unix::ffi; the oldcfg(any(unix, target_os = "wasi"))import only compiled on unix.package.json: addwasm32-wasip1-threadsto napi targets (generates the browser/worker JS glue),asyncInit: true(the wasm binary exceeds Chromium's 8 MB sync-compile limit), and the@emnapi/coredevDependency the napi CLI needs for wasm builds.publish-js.yml: enable the wasm build matrix entry (the artifact upload paths were already wired forbashkit.*.wasm; with the target listed inpackage.json,napi prepublishexpects the artifact, so these must land together).js.yml: new PR-time debug build of the wasm target, so breakage surfaces in PR CI instead of at release time.The
test-js-wasipublish job stays commented: the build blocker is gone, but running the ava suite underNAPI_RS_FORCE_WASI=1is unverified — noted in the updated comment.Verification
napi build --target wasm32-wasip1-threadssucceeds warning-free for the bashkit-js crate (one remaining warning comes from bashkit core and is fixed by the companion wasm32-unknown-unknown time PR; the two are otherwise independent)