Skip to content

fix: exclude emscripten from the browser-wasm cfg branches - #857

Merged
crowlKats merged 1 commit into
denoland:mainfrom
crowlKats:emscripten-cfg
Oct 1, 2026
Merged

crowlKats merged 1 commit into
denoland:mainfrom
crowlKats:emscripten-cfg

Conversation

@crowlKats

Copy link
Copy Markdown
Member

wasm32-unknown-emscripten is target_arch = "wasm32", but unlike wasm32-unknown-unknown it is also unix with a full libc — it wants the native code paths. std::process::Command, percent_encoding and a Rust closure for the markdown highlighter all work there; js_sys/wasm-bindgen do not, since nothing is reaching into the module from a JS host that way.

deno_doc currently keys those branches on target_arch = "wasm32" alone, so an emscripten build takes the browser path and fails to compile. For example src/html/usage.rs passes a js_sys::Function where the native signature wants Fn(&str, Option<&str>):

error[E0277]: expected a `Fn(&str, Option<&str>)` closure, found `js_sys::Function`
  --> src/html/usage.rs:397:57

This PR gates each of those cfgs on the OS as well:

  • cfg(target_arch = "wasm32") → cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))
  • cfg(not(target_arch = "wasm32")) → cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))

including the [target.'cfg(target_arch = "wasm32")'.dependencies] table, so emscripten builds don't pull js-sys, serde-wasm-bindgen or wasm-bindgen at all.

No behaviour change for any existing target — emscripten is the only cfg that moves. Verified:

  • cargo check --all-features (native): passes
  • cargo check --target wasm32-unknown-unknown --no-default-features, for both deno_doc and deno_doc_wasm: passes, before and after

Context: this came out of running the whole jsr registry API as a wasm32-unknown-emscripten Cloudflare Worker, where deno_doc is currently the one dependency that has to be vendored with a local patch.

`wasm32-unknown-emscripten` is `target_arch = "wasm32"`, but it is also unix
with a full libc, so it wants the native code paths: `std::process::Command`,
`percent_encoding` and a Rust closure for the markdown highlighter all work
there, while `js_sys`/`wasm-bindgen` do not (there is no JS host reaching into
the module that way).

Today deno_doc keys those branches on `target_arch = "wasm32"` alone, so an
emscripten build takes the browser path and fails to compile — e.g.
`src/html/usage.rs` hands the highlighter a `js_sys::Function` where the native
signature wants `Fn(&str, Option<&str>)`.

Gate them on the os as well: every `cfg(target_arch = "wasm32")` browser branch
becomes `all(target_arch = "wasm32", not(target_os = "emscripten"))`, and its
`not(target_arch = "wasm32")` counterpart becomes
`any(not(target_arch = "wasm32"), target_os = "emscripten")`. The same gate goes
on the wasm-only dependency table, so emscripten builds do not pull js-sys,
serde-wasm-bindgen or wasm-bindgen at all.

No behaviour change for any existing target. Verified: `cargo check --all-features`
(native) and `cargo check --target wasm32-unknown-unknown --no-default-features`
for both deno_doc and deno_doc_wasm pass before and after.
@crowlKats
crowlKats enabled auto-merge (squash) October 1, 2026 02:18
@crowlKats
crowlKats merged commit e09e2e3 into denoland:main Oct 1, 2026
5 checks passed
crowlKats added a commit to jsr-io/jsr that referenced this pull request Oct 10, 2026
Moves the branch off every local checkout and onto published toolchains:

* [patch.crates-io] now carries the workers-rs emscripten tokio patchset
  verbatim from cloudflare/workers-rs examples/emscripten-tokio (guybedford
  tokio/mio tags + rust-lang/libc), and the whole wasm-bindgen family comes
  from crates.io 0.2.129 — the sibling ../workers-rs forks are gone, as are
  the 3 tokio methods that fork needed.
* `worker-build --emscripten --release` (workers-rs 0.8.7) is the build
  driver, via api/build-worker.sh and wrangler's [build] command. It
  provisions emsdk 6.0.10 and applies the emscripten frontend patches itself,
  so the hand-patched emscripten checkout and the import-source workaround
  are no longer needed. It also requires the `worker` SDK crate in the graph,
  which is what makes it pass the tokio/wasm-bindgen unstable cfgs.
* The fetch export is `#[wasm_bindgen(experimental_tokio = "isolated")]`
  (upstream's name) and drops `js_namespace = ["default"]`: worker-build's
  generated shim provides the default export as a WorkerEntrypoint subclass.
* tokio can no longer use `features = ["full"]` on wasm — the patched tokio
  hard-errors on process/signal/rt-multi-thread for emscripten — so the
  shared dep lists a wasm-buildable set and native re-adds `full`.
* Release profile for the worker: opt-level = "s" + fat LTO, 36.6MB -> 24.2MB
  wasm. opt-level = "z" reaches 22.0MB but triples latency and starves the
  per-request pool under load; opt-level = 3 + LTO does not link (wasm-bindgen
  cannot parse the exnref output). See EMSCRIPTEN_EXPERIMENT.md#size.
* Four patches retired by upgrading instead of patching (libc from the git
  patchset, and socket2 0.6 / deno_doc / tar from the vendored set), leaving the
  tokio+mio git patchset and six vendored cfg fixes:
  - libc: the emscripten declarations shipped in 0.2.190, so it comes from
    crates.io now. The release alone was not enough - the mio fork hardcodes
    `libc = { git = "rust-lang/libc", branch = "libc-0.2" }`, so a
    [patch."https://github.com/rust-lang/libc"] entry redirects that source at the
    published crate, otherwise the graph carries two libc copies.
  - socket2 0.6: 0.6.5 has the emscripten cfg upstream (tokio uses it unpatched);
    socket2 0.5 refreshed 0.5.7 -> 0.5.10.
  - deno_doc: 0.208.0 ships denoland/deno_doc#857. Needs deno_graph =0.109.0 ->
    =0.111.0 (two deno_graph versions in one graph are different types), and with
    it: BuildOptions::unstable_text_imports is gone (0.111 allows
    `with { type: "text" }` unconditionally - jsr had it false, and there is no
    flag left to keep it off), BuildOptions gained unstable_config_imports and
    prefer_cached_jsr_versions (both false = previous behaviour), GenerateOptions
    gained symbol_listing_limit (None = render everything, as before).
  - tar: 0.4.46 builds for emscripten unpatched. The blocker was jsr's own
    fixture - big_file/big.txt is 21MB of zeros stored sparsely, and tar >= 0.4.46
    writes a sparse file as a GNUSparse entry, which the reader rejects as
    invalidEntryType before the size check the test is about. tar.sparse(false) in
    the test helpers fixes it and stops the fixture depending on the checkout's
    filesystem.
  The six fixes that remain are deliberately NOT in this commit. jsr does not
  carry local patches of third-party crates, so the committed [patch] table is
  just the git tags above, and as a consequence the wasm32-unknown-emscripten
  link does not succeed from a clean checkout until those six land upstream.
  Native builds are unaffected: `cargo test` is 173/173 with no patch directory
  present at all, and Cargo.lock resolves those crates from crates.io at main's
  versions (reqwest 0.12.12 and astral-tokio-tar 0.6.3 pinned back so re-resolving
  does not drag in unrelated drift).
  EMSCRIPTEN_EXPERIMENT.md records what each fix is - 2 lines for ring, 3 for
  slug, 4 for socket2, ~10 for astral-tokio-tar, 110 mechanical ones for reqwest -
  and the blocker for each: ring
  needs the tree-sitter grammar bump (tree-sitter-javascript 0.21.4 is the last
  crate pinning cc ~1.0.90) before briansmith/ring#2877 can be used; socket2 0.5
  is pulled only by routerify's hyper "tcp" feature; reqwest is stuck in two
  different ways (below); slug is a mandatory comrak 0.29 dependency that
  deno_doc pins; astral-tokio-tar has no upstream fix in 0.7.0.
* .gitignore picks up .wrangler/, api/build/ and .emscripten-patches/, and
  .licenserc.json / deno.json exclude .emscripten-patches/ as well, so a locally
  reconstructed copy of those crates cannot be committed by accident and does not
  trip the license-header check (it globs **/*.rs and flagged 418 files).

Also fixes a regression from the first experiment commit: the native-only
rust-s3 dep had lost `default-features = false`, which re-enabled rust-s3's
`fail-on-err` and turned every missing-object 404 into a 500 (3 failing tests).

Upstream re-checked 2026-10-08: mio#1969 is merged but unreleased (mio 1.2.4
predates it), tokio#8484 and ring#2877 are still open, rust-s3 0.38.0 is still on
reqwest 0.12, and comrak 0.56 drops slug to a dev-dependency but deno_doc still
pins comrak 0.29.

reqwest specifically (2026-10-10): the emscripten gate only ever landed in 0.13 -
0.12.28, the latest 0.12, still has the bare cfg(target_arch = "wasm32") - and
neither copy can reach it from jsr's side. The 0.12 copy belongs to rust-s3 and
opentelemetry (opentelemetry-http is still ^0.12 even at 0.31), and 0.13 cannot
satisfy ^0.12. The 0.11 copy is capped at 0.12 by oauth2 5.0.0, so the only
reachable step is consolidating 0.11 -> 0.12: it retires one of the two patches
and costs oauth2 4->5 plus postmark 0.10->2.0 in the auth/email path.

Verified: 173/173 + 8 native tests pass; the worker serves on stock
`wrangler dev` (no forked workerd) with 64/64 concurrent requests returning
200 and no cross-request-I/O errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants