Skip to content

fix(turbopack): handle unresolvable browserslist via Targets::Query#92244

Open
mhdsdt wants to merge 2 commits into
vercel:canaryfrom
mhdsdt:fix/turbopack-browserslist-unknown-versions
Open

fix(turbopack): handle unresolvable browserslist via Targets::Query#92244
mhdsdt wants to merge 2 commits into
vercel:canaryfrom
mhdsdt:fix/turbopack-browserslist-unknown-versions

Conversation

@mhdsdt
Copy link
Copy Markdown

@mhdsdt mhdsdt commented Apr 2, 2026

Fixes #92091

What?

When a project has a browserslist like >0.2%, not dead (the CRA default), Turbopack panics with not yet implemented: right-associative binary expression on any code that uses async + ** together.

Why?

Next.js resolves the browserslist on the JS side using an up-to-date caniuse-lite (which knows about Chrome 146, Firefox 149, etc.), then sends the resolved browser strings to Turbopack. Turbopack re-resolves them using browserslist-rs v0.19.0, which only knows up to about Chrome 141. Unknown versions are silently dropped because of ignore_unknown_versions: true in environment.rs.

With all versions dropped, Versions::parse_versions returns all-None fields. SWC's preset_env then sees is_any_target() == true and short-circuits its caniuse to "transform every feature", which includes async-to-generator and the ES2015 generator transform — and that transform has a todo!() for the ** operator at generator.rs:507.

SWC already handles this case for the raw browserslist path via the unknown_version flag added in swc-project/swc#11457. But Turbopack bypasses that flag because it passes pre-resolved Targets::Versions(versions) directly, and targets_to_versions always sets unknown_version: false on that variant.

How?

When versions.is_any_target() is true and a browserslist query string is available on the Environment, re-route through Targets::Query(Query::Single(query)) instead of Targets::Versions(empty). SWC's own resolver runs the query, observes the empty result, and sets unknown_version: true — the documented "modern browser, supports everything" semantics from swc#11457.

The user's experimental.swcEnvOptions (mode, coreJs, include, exclude, shippedProposals, forceAllTransforms, debug, loose) continue to flow through to SWC's Config unchanged, so SWC remains the source of truth for how those options interact with unknown_version.

Test plan

  • New snapshot test swc_transforms/preset_env_unknown_browserslist covers an explicit chrome 99999 query. Without the fix it panics on the same todo!(); with the fix the async function ... return 10n ** 18n is preserved verbatim in the chunk output.
  • Verified against the reproduction project: rebuilt the SWC binary against this branch, ran next dev --turbopack, page renders with status 200, and the emitted chunk contains the original async function compute() { return 10n ** 18n; } with no transforms applied.
  • cargo check, cargo fmt --check, cargo clippy --all-targets -- -D warnings, and the full turbopack-tests snapshot suite all clean.

@nextjs-bot nextjs-bot added the Turbopack Related to Turbopack with Next.js. label Apr 2, 2026
@nextjs-bot
Copy link
Copy Markdown
Contributor

Allow CI Workflow Run

  • approve CI run for commit: 06e8a3683135eb8f67e80e5c63ea60f20127a671

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@nextjs-bot
Copy link
Copy Markdown
Contributor

nextjs-bot commented Apr 2, 2026

Allow CI Workflow Run

  • approve CI run for commit: 051eed3

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@afurm

This comment was marked as spam.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 2, 2026

Merging this PR will not alter performance

✅ 8 untouched benchmarks
⏩ 12 skipped benchmarks1


Comparing mhdsdt:fix/turbopack-browserslist-unknown-versions (06e8a36) with canary (e9597e6)

Open in CodSpeed

Footnotes

  1. 12 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@mhdsdt
Copy link
Copy Markdown
Author

mhdsdt commented Apr 2, 2026

@afurm Good call, added that in the second commit. There's now a snapshot test under preset_env_unknown_browserslist that uses chrome 99999 as the target and has async + ** in the input. It verifies both are preserved in the output without downleveling. All 3 preset_env snapshot tests pass.

@mischnic
Copy link
Copy Markdown
Member

mischnic commented Apr 8, 2026

This looks good, but you'll need to rebase this onto canary, #92272 was merged in the meantime

@mhdsdt mhdsdt force-pushed the fix/turbopack-browserslist-unknown-versions branch 2 times, most recently from 9de4d06 to 051eed3 Compare April 8, 2026 16:03
@mhdsdt
Copy link
Copy Markdown
Author

mhdsdt commented Apr 8, 2026

This looks good, but you'll need to rebase this onto canary, #92272 was merged in the meantime

Rebased onto canary. The changes in #92272 touched the same match arm, so I wrapped the new preset-env config parsing inside the else branch of our is_any_target() guard so the guard runs first.

Verified locally: cargo check, fmt, clippy all clean. All 3 preset_env snapshot tests pass. Also rebuilt the SWC binary against the rebased branch and confirmed the reproduction project still renders without the panic.

@mischnic
Copy link
Copy Markdown
Member

Now, experimental.swcEnvOptions would get ignored though if you specify too new versions here. It should still respect options like coreJs, mode and include.

mhdsdt added 2 commits April 25, 2026 12:04
When `browserslist-rs` returns an empty distrib list (e.g. the user wrote
`chrome 99999`, or the bundled caniuse-lite is older than the JS-side data),
`Versions::parse_versions` produces an all-`None` result. SWC's preset-env
treats all-`None` as `is_any_target == true` and short-circuits its caniuse
logic to "transform every feature", which then panics in
`swc_ecma_compat_es2015::generator` on unsupported nodes such as `**`.

Detect that case and re-route the resolution through `Targets::Query` using
the original browserslist string from `Environment::browserslist_query`.
SWC's own `targets_to_versions` then runs the query, observes the empty
result, and sets `unknown_version: true` — the documented "modern browser,
supports everything" semantics introduced in swc-project/swc#11457. All
user-supplied `swcEnvOptions` (`mode`, `coreJs`, `include`, `exclude`)
continue to flow through to SWC, which decides how to honor them.
Cover the case where `browserslist-rs` returns an empty result for an
unrecognized version like `chrome 99999`. Without the accompanying fix,
preset-env panicked here with the generator transform's `todo!()` for
right-associative binary expressions (`**`).
@mhdsdt mhdsdt force-pushed the fix/turbopack-browserslist-unknown-versions branch from 051eed3 to e7c2e28 Compare April 25, 2026 08:36
@mhdsdt mhdsdt changed the title fix(turbopack): skip preset-env when browserslist-rs returns no versions fix(turbopack): handle unresolvable browserslist via Targets::Query Apr 25, 2026
@mhdsdt
Copy link
Copy Markdown
Author

mhdsdt commented Apr 25, 2026

Now, experimental.swcEnvOptions would get ignored though if you specify too new versions here. It should still respect options like coreJs, mode and include.

Good point, thanks. Reworked the fix so we don't skip preset-env anymore. When is_any_target() is true we now route through Targets::Query with the original browserslist string, which lets SWC's own resolver set unknown_version: true for the empty result (the same path from swc-project/swc#11457).

All of experimental.swcEnvOptions (mode, coreJs, include, exclude, etc.) flows through to SWC's Config unchanged, so SWC stays in charge of how those options interact with unknown_version.

New version is pushed and the title/description are updated to match. Verified locally with cargo check, fmt, clippy, the full snapshot suite, and against the reproduction project (page renders with the async function ... 10n ** 18n preserved verbatim in the chunk).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Turbopack: stale browserslist-rs data causes all SWC compat transforms to be enabled, panicking on ** operator

4 participants