Use `Bytes::from_owner(napi::Buffer)` to eliminate the `Buffer →
Vec<u8>` memcpy on the JS→Rust path in the worker pool.
`bytes::Bytes::from_owner` (>=1.9.0) wraps any `T: AsRef<[u8]> + Send +
'static` without copying — the data pointer comes from `T::as_ref()` and
`T` is dropped when the `Bytes` is dropped. Since napi@2's `Buffer`
implements `AsRef<[u8]>`, this gives true zero-copy.
Also unifies the `Operation` trait to use `Bytes` for both `send` and
`recv`.
This also helps reduce memory consumption, without zero-copy, the memory
consumption of the data returned by the loader would be doubled until GC
on the JS side or dropped on the Rust side.
# What
Make a significant chunk of the remaining turbotask registration code
(Values, Traits and Value/Trait impls) `const`. The vtable lookup for
trait methods is constructed at compile time, allowing us to look up a
trait's implementation in near-constant time.
Introduces a new, common RegistryType we use for Values, Traits and
Functions to reduce duplication. We still have a Lazy guard on each
registry, but that will probably be removed if we migrate to distributed
slices.
NOTE: This removes a number of Lazy guards, but not all of them to avoid
making the PR too large. The next pass of PR will remove the remaining
Lazy guards, where possible.
# Why
By making this code `const`, we reduce the overall init time of the
executable, make LTO a little more powerful (when we end up using that),
reduce startup allocations, and reduce the amount of time we spend
checking Lazy atomics.
In hyperfine tests of the benchmark-apps folder, we see a 1% to 3%
speedup for compilation.
## Summary
Remove the unused LMDB database backend and the `ReadTransaction` abstraction layer from `turbo-tasks-backend`. This is a pure cleanup that deletes ~1,500 lines of code and eliminates all `unsafe` blocks related to transaction lifetime management.
**Motivation:**
- This was much more useful in the early days of the persistence layer where comparing against a 'known good' db was useful, but this has rotted and turbo-persistence has gained lots of tests and usage so that this is not needed.
- Carrying the code added a lot of complexity to APIs e.g.
- The `ReadTransaction` abstraction existed solely to support LMDB's transaction-based read model
- The only remaining backend (`TurboKeyValueDatabase` via `turbo-persistence`) already used `()` as its `ReadTransaction` type, making the entire abstraction dead weight
- The transaction plumbing required `unsafe` transmute-based lifetime extension in `ExecuteContextImpl`, adding complexity and risk for no benefit
- The WriteBatch::Serial branches complicated already subtle code paths
### What?
Adds a new `experimental.lightningCssFeatures` config option that lets
users control which CSS features lightningcss should always transpile
(`include`) or never transpile (`exclude`), regardless of browserslist
targets.
```js
// next.config.js
module.exports = {
experimental: {
useLightningcss: true,
lightningCssFeatures: {
include: ['light-dark', 'oklab-colors'],
exclude: ['nesting'],
},
},
}
```
### Why?
Currently, lightningcss feature transpilation is determined solely by
browserslist targets. There's no way to force transpilation of a
specific feature (e.g., `light-dark()`) when targeting modern browsers
that already support it, or to skip transpilation of a feature that the
user wants to preserve as-is.
This is useful for:
- Forcing polyfill-style transpilation for features with incomplete
browser support
- Testing transpiled output for specific features
- Opting out of specific transforms that may interfere with CSS tooling
downstream
### How?
**TypeScript config & validation:**
- `config-shared.ts`: `LIGHTNINGCSS_FEATURE_NAMES` const array (single
source of truth) with `LightningCssFeature` type derived from it, and
`LightningCssFeatures` interface
- `config-schema.ts`: Zod validation using
`z.enum(LIGHTNINGCSS_FEATURE_NAMES)` for both include/exclude arrays
**Feature name → bitmask mapping (Rust, shared via NAPI):**
- `crates/next-core/src/next_config.rs`:
`lightningcss_feature_names_to_mask()` maps feature name strings to
`lightningcss::targets::Features` bitflag constants and returns a
`Result<u32>` bitmask. Unknown feature names produce an error via
`bail!`.
- Exposed to JS via NAPI as `lightningcssFeatureNamesToMaskNapi`,
callable through `bindings.css.lightning.featureNamesToMask(names)`
- Single source of truth for feature name resolution — both webpack and
Turbopack paths use this Rust function
**Webpack path:**
- `global.ts` / `modules.ts` pass config through to loader options
- `loader.ts` calls `featureNamesToMask()` via the native bindings to
compute include/exclude masks, then passes them to the SWC `transform()`
call
- The `lightningCssFeatures` without `useLightningcss` warning only
shows when using webpack (Turbopack always uses lightningcss)
**Turbopack path (Rust):**
- `next_config.rs`: `LightningCssFeatures` struct for deserialization,
accessor methods converting names → u32 bitmask via
`lightningcss_feature_names_to_mask()`
- Config flows through `CssOptionsContext` → `ModuleType::Css` →
`CssModuleAsset` (using `LightningCssFeatureFlags { include, exclude }`
struct) → `process.rs` where bitmasks are merged into
`lightningcss::targets::Targets { include, exclude }`
- Uses `u32` bitmask representation throughout to avoid adding
lightningcss dependency to non-CSS crates
**Defaults preserved:** `Nesting | MediaRangeSyntax` for Turbopack,
`Nesting` for Webpack. User `include` is OR-ed on top, user `exclude`
masks bits off.
**Feature names:** 21 individual features (bit 0–20) + 3 composite
groups (`selectors`, `media-queries`, `colors`), all in dash-case.
### Test
- **Include test**
(`test/e2e/app-dir/experimental-lightningcss-features/`) — targets
Chrome 123 (which natively supports `light-dark()`) with `include:
['light-dark']`, then asserts the CSS output contains lightningcss
transpilation markers (`--lightningcss-light`, `--lightningcss-dark`)
instead of raw `light-dark()`.
- **Exclude test**
(`test/e2e/app-dir/experimental-lightningcss-features-exclude/`) —
targets Chrome 100 (which does NOT support `light-dark()` natively) with
`exclude: ['light-dark']`, then asserts the CSS output preserves raw
`light-dark()` calls and does not contain transpilation markers.
Not that error rendering performance is critical, but it is silly to pass frame information from turbopack to js just to have js pass the same information back to native code to draw a frame.
## Summary
Reduces CPU overhead in turbo-persistence SST reads through three optimizations:
1. **Switch from `twox-hash` to `xxhash-rust`** — `twox_hash::XxHash3_64::with_seed(0)` heap-allocates a 192-byte secret buffer on every call. `xxhash_rust::xxh3::Xxh3Default::new()` is a `const fn` with zero allocation. Applied to both `turbo-persistence` and `turbo-tasks-hash`.
2. **Bulk `write()` in `KeyBase::hash` impls** — The hash implementations were feeding bytes one at a time via `write_u8()`, preventing xxh3 from using its optimized bulk ingestion path. Changed all slice-backed types (`&[u8]`, `[u8; N]`, `Vec<u8>`, `Box<[u8]>`) to use a single `state.write(slice)` call.
3. **Binary search improvements in SST lookups:**
- **Index block lookup**: Replaced hand-rolled binary search with `as_chunks::<10>()` + stdlib `binary_search_by`, making the code simpler and leveraging the stdlib's optimized search.
- **Duplicate key scans**: When collecting all entries with the same key (FIND_ALL mode), the backward/forward scans now use the binary search bounds (`l..m` and `m+1..r`) instead of scanning from `0` to `entry_count`. The binary search already proved entries outside these bounds are different, so this avoids unnecessary comparisons.
- **`entry_matches_key` helper**: Equality checks during duplicate scans now use a dedicated function that avoids recomputing the full hash when no hash is stored in the block (block type 2/4). Previously `compare_hash_key` would call `hash_key()` to compute the full hash even when only an equality check was needed.
## Benchmark results
Compared against the `fixed_size_blocks` branch using `cargo bench -p turbo-persistence -- 'read/get/key_8'` (8-byte keys, 4-byte values):
| Benchmark | Change |
|-----------|--------|
| 10.67Mi/compacted/miss/cached | **-45%** |
| 10.67Mi/compacted/hit/cached | **-35%** |
| 10.67Mi/uncompacted/hit/cached | **-35%** |
| 10.67Mi/uncompacted/hit/uncached | **-22%** |
| 10.67Mi/uncompacted/miss/uncached | **-22%** |
| 10.67Mi/uncompacted/miss/cached | **-21%** |
| 10.67Mi/20commits/hit/cached | **-26%** |
| 10.67Mi/20commits/miss/* | -13% |
| 85.33Mi/compacted/hit/uncached | **-56%** |
| 85.33Mi/compacted/hit/cached | **-22%** |
| 85.33Mi/compacted/miss/* | **-22%** |
| 85.33Mi/uncompacted/hit/cached | -10% |
| 85.33Mi/20commits/hit/uncached | -12% |
| 85.33Mi/20commits/hit/cached | -6% |
| 85.33Mi/20commits/miss/* | -1 to -3% |
Largest wins on cached paths with smaller datasets where CPU cost (hashing, search) dominates over I/O.
We only store hashes for TaskTypes in the TaskCache keyspace, so this introduces an allocation free path to compute it.
For writing out the TaskCache this wont make much of a difference since we were reusing a scratch buffer, but this should eliminate a source of allocations from the read path.
This does of course cause a small binary size regression `157M (160,952K) -> 158M (161,800K) = +1M (+848K)`
## What
Replaces the `@babel/code-frame` dependency with a new Rust-based implementation (`next-code-[frame](https://github.com/arthurprs/qfilter/pull/20#issuecomment-3986882055)` crate) for rendering code frames in error messages.
### Why
- **Crash fix**: `@babel/code-frame` uses the `js-tokens` library for syntax highlighting, which has [known issues](https://github.com/lydell/js-tokens?tab=readme-ov-file#known-failures) with large string literals and long lines. This can cause Next.js to throw RangeErrors when rendering errors, hiding the original issue!
- **Long line support**: The old implementation had no concept of terminal width, dumping entire lines into the output. The new implementation uses "horizontal scrolling" — truncating lines and centering the error location in the visible window.
- **Performance**: The Rust implementation only processes the visible line range (typically ~6 lines), not the entire file. Syntax highlighting uses a skip-scan heuristic to start tokenizing near the visible window rather than from byte 0.
- **Dependency reduction**: Drops the semi-unmaintained `@babel/code-frame` bundled dependency in favor of code we control.
### Benchmarks
In-process benchmarks comparing `render_code_frame()` (Rust, via criterion) against `codeFrameColumns()` (Babel, via hrtime with DCE prevention). Both have syntax highlighting and color output enabled. No process startup or file I/O is included in the measurement.
| Scenario | `next-code-frame` (Rust) | `@babel/code-frame` | Speedup |
|---|---|---|---|
| Small file (~490 lines TSX) | **5.4 µs** | 507 µs | **~94x** |
| Large file (~39k lines JS) | **143 µs** | 82.9 ms | **~580x** |
| Large file minified | **51 µs** | - | **-** |
The gap widens with file size because Babel's `highlight()` runs a regex tokenizer over the **entire source** before slicing to the visible window, while the Rust implementation uses a windowed line index and skip-scan heuristic — only processing the visible window regardless of file size. However, for minified files we do end up tokenizing the whole thing so we end up being only 8x faster
### How
- New `crates/next-code-frame/` Rust crate with:
- Frame rendering with terminal-width-aware horizontal scrolling
- Regex-based syntax highlighting (matching Babel's color scheme)
- Skip-scan heuristic for O(1)-ish highlighting regardless of file size
- Windowed line index that only scans/stores offsets for the visible region
- Comprehensive test suite (800+ lines)
- Exposed via both NAPI (native) and WASM bindings
- JS wrappers in `packages/next/src/shared/lib/errors/`:
- `code-frame.ts` — primary wrapper using native bindings
- `optional-code-frame.ts` — graceful fallback returning `undefined` if bindings are unavailable
- All existing callsites (`diagnosticFormatter`, `parseScss`, dev overlay, turbopack utils) updated
- for `patch-error-inspect.ts` i adopted an `injection` style approach to avoid coupling to the native dependency
### Concerns / review focus areas
- **Reliability**: The regex-based tokenizer is best-effort and language-agnostic — it should never crash on invalid syntax, but highlighting accuracy may differ from Babel's `js-tokens` in edge cases.
- **Native dependency**: This moves code frame rendering into the native binary. Performance should be better, but worth verifying there are no regressions in environments where native bindings behave differently (e.g. WASM fallback path).
- **Regressions**: The output format and color scheme closely match Babel's, but there may be subtle differences. The horizontal scrolling behavior is new.
Fixes#85357
Closes PACK-5754
### What?
Add a 4-byte CRC32 checksum to every block in turbo-persistence SST
files.
### Why?
Detect on-disk cache corruption early with a clear error message, rather
than silently returning wrong data or hitting confusing LZ4
decompression failures.
### How?
**New on-disk block format:**
```
[4B uncompressed_length] [4B CRC32 checksum] [block data]
```
(Previously the checksum field did not exist — header was just the 4B
uncompressed_length.)
- The checksum is computed on the **uncompressed** block data using
`crc32fast` (hardware-accelerated CRC32C on modern CPUs).
- On write, the checksum is computed before compression and stored in
the block header.
- On read, the checksum is verified after decompression (or directly for
uncompressed blocks). A mismatch returns an `anyhow` error with a
message like: `Cache corruption detected: checksum mismatch in block N
of SST file seq:M`.
- During compaction, medium values are copied as raw compressed blocks
without decompression — the checksum is carried through the
`MediumRaw`/`LazyLookupValue::Medium` path unchanged.
- A `BLOCK_HEADER_SIZE` constant (= 8) replaces magic numbers across the
write, read, and inspect code.
**Files changed:**
- `compression.rs` — `checksum_block()` helper wrapping
`crc32fast::hash()`
- `static_sorted_file_builder.rs` — write path
(`write_raw_block_to_file`, `write_block_to_file`, `close()` index
block), `EntryValue::MediumRaw` carries checksum
- `static_sorted_file.rs` — read path (`get_raw_block_slice`,
`read_block` with `verify_checksum()` helper)
- `lookup_entry.rs` — `LazyLookupValue::Medium` carries checksum for
compaction
- `bin/sst_inspect.rs` — updated block parsing offsets
- `README.md` — documented new format
**Tests:**
- All 48 existing tests pass (checksums are transparent to correct data)
- 2 new corruption tests: one corrupts a compressed block's checksum,
the other corrupts an uncompressed block's data — both verify the error
is returned
### What?
Updates outdated Rust dependencies used by `turbo-persistence` and
`turbo-tasks-backend` crates.
### Why?
Keep dependencies current to benefit from bug fixes, performance
improvements, and security patches.
### How?
Each dependency was updated in a separate commit. All crates were
verified to compile and tests pass.
**Direct dependency updates:**
- `lzzzz` 1.1.0 → 2.0.0 (moved to workspace dependency, used by both
crates)
- `arc-swap` 1.7.1 → 1.8.2 (turbo-tasks-backend)
**Workspace dependency updates:**
- `bitfield` 0.18 → 0.19
- `either` 1.9 → 1.15
- `indexmap` 2.7 → 2.13
- `once_cell` 1.17 → 1.21
- `rand` 0.9 → 0.10 (required API migration: `Rng`/`RngCore` → `RngExt`)
- `ringmap` 0.1 → 0.2
**Skipped:**
- `hashbrown` 0.14 → 0.16: The `raw` module was removed in 0.15 and
replaced with `HashTable` API. The codebase uses `RawTable`, `Bucket`,
`InsertSlot` extensively, requiring a non-trivial migration.
1. refactor `turbopack-node` to run webpack loaders via nodejs
`worker_threads`
2. the `child_proccess` loader runner is keep in the crate feature to
run unit tests in `turbopack-tests` or run `turbopack-cli`
3. if wanting to switch back to `child_proccess` loader runner, change
features of `turbopack_node/worker_pool` to
`turbopack_node/process_pool` in crate `next-swc-napi`
4. after switched to `worker_threads` loader runner, debugging with
`CDP` (spawn child_process via `--inspect-brk`) doesn't work anymore,
but you can use breakpoint debugging in IDE (like vscode) directly
---------
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
We've had some really bad bugs in the past, which can be hard to root-cause due to eventually-consistent reads a the top-level, e.g. https://github.com/vercel/next.js/pull/77511
Outside of tests, there's no good justification for doing an eventually-consistent read at the top level. It may leak incomplete computations, which can contain errors that would otherwise be resolved by a strongly consistent read.
The basic idea here is simple:
- Store a flag in `CURRENT_TASK_STATE` indicating that we're in a top-level task.
- When reading a task output or cell, check for that flag.
But, there are a few complications:
- `RawVc`'s read and resolution logic is wrong, and internally uses multiple eventually consistent operations after a strongly consistent read.
- For this to be correct, it needs to happen as one atomic operation inside of the task backend, but it would change the backend API contract, so it's not straightforward to do that.
- I added a `SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK` flag that I'm using for now.
- There are a ton of places that are doing eventually consistent reads for no good reason. These aren't causing problems today, but we should fix them instead of suppressing them. Claude helped with this, but it's still a lot.
- Some tests actually do need eventually-consistent reads because they're testing some internal behavior of turbo-tasks. I added an intentionally long-named `unmark_top_level_task_may_leak_eventually_consistent_state` helper for this.
I manually tested `turbopack-cli` against our bundler-benchmark repository that uses it.
When cold-booting `next dev` on a fresh create-next-app, on the first server hit we need to fetch fonts and compile CSS, which takes up nearly half of the request time.
It shouldn't be necessary to wait until the first server request to launch the first NodeJS process (eventually used for PostCSS processing in Tailwind). Once the server has started, we can anticipate that first request and pre-warm a single Node process.
Because we grab the boot semaphore, if a request comes in as soon as the server is ready, it waits on the semaphore to resolve, and at most it takes as long as it would before this change. In all likelihood, the Node process is ready before any server request comes in.
It also slightly saves time on `next build`
I'd appreciate scrutiny of all the ways this can go wrong.
## Summary
- Remove the zstd key compression dictionary from SST files, simplifying
the on-disk format
- Key blocks now use plain LZ4 compression (same as value blocks),
eliminating the zstd dependency
- Update the turbo-persistence README to reflect the format changes
## Why
The key compression dictionary added complexity (zstd dependency,
dictionary computation, dual decompression paths) with diminishing
returns. Plain LZ4 is already used for value blocks and provides
sufficient compression for key blocks. This simplifies both the SST file
format and the compaction/inspection tooling.
This main goal is to unblock streaming SST writes.
## What Changed
**On-disk format (breaking):**
- SST files no longer start with a serialized key compression dictionary
- Meta file entries no longer include the
`key_compression_dictionary_length` field
**Code:**
- Removed `zstd` dependency from `turbo-persistence`
- Removed dictionary computation in `StaticSortedFileBuilder`
- Simplified `sst_inspect` tool (no more dictionary-aware decompression)
- Removed `key_compression_dictionary_length` from
`StaticSortedFileMetaData`, `MetaEntry`, and `MetaFileBuilder`
**Docs:**
- Updated README meta file format (removed dictionary length field)
- Updated README SST file format (removed dictionary region)
- Updated compression description to reference LZ4 directly
## Test Plan
- Existing turbo-persistence tests and benchmarks pass with the format
changes
- `sst_inspect` tool updated to work without dictionary logic
---------
Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com>
We want `FileContent.content_hash()` to only hash the content bytes. This is also needed to produce the correct hashes [for SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity)
However, the default `DeterministicHash` and `Hash` impls are ill-suited for that because they're a generic hashing infrastructure.
Hashing only the content bytes isn't what you want actually, otherwise `hash(("a", "b", "c"))` would be the same as `hash("abc")`
https://doc.rust-lang.org/std/hash/trait.Hasher.html#method.write_str describes this problem as well
This is exactly what we want for Rope though, so this adds a `Rope.content_hash() -> impl DeterministicHash`.
Additionally, this changes `content_hash` to accept a HashAlgorithm to prepare for sha SRI hashes. This required changing the return type to `RcStr`, because of the variable hash lengths. But the return value was always `encode_hex`d everywhere anyway.
# Refactor JSON module handling in Turbopack
## What?
Moves the JSON module handling from a separate crate (`turbopack-json`) into the `turbopack-ecmascript` crate as a source transform, similar to how text and binary files are handled.
## Why?
This simplifies the codebase by:
1. Removing a separate crate dependency
2. Using a consistent pattern for handling non-JavaScript assets
* notably by sending them through the normal asset pipeline we unlock other behaviors in turbopack like scope-hoisting
3. Improving error handling for invalid JSON files
## How?
- Removed the `turbopack-json` crate
- Added a new `json_source_transform.rs` module to `turbopack-ecmascript`
- Implemented two modes for JSON handling:
- Spec-compliant ESM mode (for `import ... with { type: 'json' }`)
- Webpack-compatible CommonJS mode (for regular imports)
- Added "no side effects" directive to JSON, text, and binary modules to preserve tree shaking
- Updated error handling to provide better feedback for invalid JSON
- Opt'd out traced modules from json/bytes/text processing, this is more correct and should be a bit faster. On the other hand this exposed a subtle issue with traced dependencies
## What
Add a `#[derive(ValueToString)]` proc macro and convert ~45 manual `ValueToString` implementations across turbopack crates to use it (-660 lines of boilerplate, 49 files touched).
## Why
Nearly every `ValueToString` impl follows the same pattern: format some fields into a string, possibly awaiting `Vc` values first. These hand-written impls are noisy boilerplate that obscure the actual format string.
## How
The derive macro generates the `#[turbo_tasks::value_impl] impl ValueToString` block automatically. A helper trait `ValueToStringify` with a blanket impl for `Display` and specializations for `Vc<T>`/`ResolvedVc<T>` handles async resolution of fields.
### Supported forms
- **No attribute** — delegates to `Display::to_string(&self)`
- `#[value_to_string("constant")]` — emits `rcstr!` directly
- `#[value_to_string("{field} text {other_field}")]` — auto-resolves named/positional fields via `ValueToStringify`
- `#[value_to_string("fmt {}", expr1, expr2)]` — explicit expressions as format args
- `#[value_to_string(expr)]` — single expression delegation
Enums support per-variant attributes (defaulting to variant name) or a single top-level attribute.
This implements the remainder of functionality needed for Server HMR
(Fast Refresh).
How It Works
- Turbopack detects file changes in RSC modules
- Rust sends HMR update payloads to the Node.js runtime via the existing
chunk update system
- A Node-specific HMR client evaluates changed modules using shared HMR
runtime logic (extracted earlier in this stack)
- Only modified modules are re-evaluated. Unmodified modules retain
their state
- Next.js hot-reloader forwards updates to RSC workers, triggering
automatic re-renders in clients
Test Plan: Added an e2e test confirming that unaffected modules don't
get re-evaluated when others change.
## What?
Adds the `experimental.turbopackIgnoreIssue` configuration option, which allows developers to suppress specific Turbopack warnings and errors based on file path, issue title, and/or description patterns. Filtered issues are hidden from both CLI output and the error overlay.
## Why?
Developers need a way to suppress noisy or expected warnings from Turbopack without disabling all warnings. This is useful for:
- Suppressing warnings from third-party code, generated files, or optional dependencies
- Reducing noise in development server output
- Hiding expected issues from the error overlay
## How?
### Configuration → Filtering Pipeline
1. **Config types** (`packages/next/src/server/config-shared.ts`, `config-schema.ts`):
Each rule has a required `path` (glob string or RegExp) and optional `title`/`description` (exact string or RegExp). All specified fields must match (AND logic).
2. **RegExp serialization** (`packages/next/src/build/swc/index.ts`):
Converts RegExp objects to `{ source, flags }` and glob strings to `{ type: 'glob', value }` for transfer to Rust.
3. **Rust deserialization** (`crates/next-core/src/next_config.rs`):
`TurbopackIgnoreIssueRule`, `TurbopackIgnoreIssuePathPattern`, and `TurbopackIgnoreIssueTextPattern` types deserialize the config. A `turbopack_ignore_issue_rules()` turbo-tasks function converts them to `IgnoreIssue` rules with proper cache invalidation.
4. **Issue filtering** (`turbopack/crates/turbopack-core/src/issue/mod.rs`):
`IssueFilter` is a turbo-tasks value that holds both severity filters and ignore rules. `IgnoreIssuePattern` supports exact string, glob, and regex matching (using `turbo-esregex` for ES-style regex). Matching issues are excluded before severity filtering.
5. **Integration** (`crates/next-api/src/project.rs`, `route.rs`, `empty.rs`):
`Endpoint::project()` trait method provides access to the project config. `issue_filter()` builds the `IssueFilter` from project config. All issue collection points use the project's dynamic filter instead of the previous hardcoded `NEXT_ISSUE_FILTER`.
### Documentation
- New docs page: `docs/01-app/03-api-reference/05-config/01-next-config-js/turbopackIgnoreIssue.mdx`
- Updated Turbopack reference page with experimental options table: `docs/01-app/03-api-reference/08-turbopack.mdx`
### Testing
E2E test suite (`test/development/app-dir/turbopack-ignore-issue/`):
- **With config**: verifies matching warnings are suppressed from CLI output, matching errors are suppressed from the error overlay, and unrelated pages still compile normally
- **Without config**: verifies warnings and errors appear when no rules are defined
### Dependencies
- Added `turbo-esregex` for ES-style regex support in Rust
## Example
```js
// next.config.js
module.exports = {
experimental: {
turbopackIgnoreIssue: [
{ path: '**/vendor/**' },
{ path: 'app/**', title: 'Module not found' },
{ path: /generated\/.*\.ts/, description: /expected error/i },
],
},
}
```
These `try_resolve_*` methods predate the introduction of the `ResolvedVc` type.
`ResolvedVc` has efficient synchronous `try_downcast`/`try_sidecast` functions, and there are only a few callsites that need to do this, so it makes more sense to split the resolution and the casting into to separate calls.
Disclaimer: This PR is entirely claude+opencode generated.
- Crate name said `next-swc-napi`, which was confusing, because this has a lot more than swc bindings nowdays.
- The crate name didn't match the directory name, which often confused agents.
This does not change the npm package name, as that would likely have a much larger blast radius, and could break more things, to a point where it may not be worth it.
This should fix the user-reported issue here:
https://github.com/vercel/next.js/pull/88290#issuecomment-3762940063
That issue occurs when building with bare-bones docker images that
contain no root CA store.
It's inconvenient to test inside of a bare-bones docker image, but I did
at least test this works on a normal (Debian) Linux install when
fetching fonts for shadcn-ui.
Though I've been unable to reproduce the issue (prior PRs in this stack are my attempts at that), I believe this *should* fix the OS Error 80 ([`ERROR_FILE_EXISTS`](https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-)) that we've seen users report on Windows: https://github.com/vercel/next.js/discussions/88382
Basically instead of deleting the existing link in a retry loop, and then trying to create new new link using a retry loop, we should instead:
- Merge these two retry loops into one, in case another process creates the link after we delete it. This shouldn't typically happen, but could theoretically happen.
- Extend the retry logic to also retry on [`ErrorKind::AlreadyExists`](https://github.com/rust-lang/rust/blob/503745e9170b40841611aaaa634641edffd00b29/library/std/src/sys/io/error/windows.rs#L19). This might be possible if the filesystem is eventually consistent (which can happen on Windows), but I wasn't able to reproduce this.
There's a similar -- but different -- issue that users have been reporting where they get OS Error 1 (`ERROR_INVALID_FUNCTION`) on Windows. I believe this happens if we try to create a junction point on a non-NTFS filesystem. In that case, we need to report a clearer issue to the user instead of bubbling this up as an internal Turbopack error.
I tested this on Windows with the symlink fuzzer from #88667, as well as `cargo test -p turbo-tasks-fs`:

In an attempt to reproduce https://github.com/vercel/next.js/discussions/88382, this tries creating and updating a bunch of symlinks/junction points in a tight loop with some parallelism. However, I've been unable to reproduce the error reported in that discussion.
I don't care about the code quality here, so this was LLM-generated with very lightweight review.
A simplified version of the fuzzer is also included as a unit test in `turbo-tasks-fs`. This version only does a few iterations and runs very quickly.
### What?
Previously cells could either be unchanged or changed. This falls short when updating Maps or Sets.
We can't have a dependency on a single key of a map or set.
The workaround for this was to create an "selector" turbo-tasks which picks the value and copies it into a new cell.
This allows more granular invalidation.
But it also creates a lot of extra tasks. And changing the map/set does invalidate all these selector tasks, which causes a lot of invalidation work.
This change adds support for dependencies on certain keys of cells.
On write side a turbo-tasks value need to opt-in into that behavior via `cell = "keyed"`.
This changes the `cell` method to compare the new value key-by-key and report the changed keys to the backend.
On read side the Vc need to be read via `.get(key).await?` or `.contains_key(key).await?`, which returns only the value resp. existance of the key and adds a dependency only to that key.
With this approach the "selector" tasks can be avoided, while maintaining the same level of invalidation.
### How?
On implementation side we only track the hash of the key for size reasons.
A dependency has an additional field `key: Option<u64>` which represents the key depending on.
When updating a cell we have a new optional argument `updated_key_hashes: Option<SmallVec<[u64; 2]>>` where the updates keys can be provided. The new `CellMode` (cell = "keyed") fills that.
I added this as an option in https://github.com/vercel/next.js/pull/81818 as a solution for users with corporate firewalls that MITM TLS traffic.
Reqwest 0.13.x now uses `rustls-platform-verifier` by default, which doesn't have the tradeoffs that `rustls-native-certs` had. We should now pick up and work with system certs by default, and we no longer depend on shipping our own blob of trusted PKI roots! (though wasmer still pulls this in...)
This PR was generated with Opus + OpenCode, but there was a ton of manual iteration to get it working on CI.
### TLS Provider
`reqwest` now defaults to `aws-lc-rs` instead of `ring`. This causes a few problems:
- On Windows, this requires cmake and NASM. There's a prebuilt NASM blob we can use, but it still appears to need cmake. We could install this in CI, but I don't want to make Windows development any more complicated than it already is.
- On Linux, there are issues with it picking up the wrong glibc version in our CI: https://github.com/aws/aws-lc-rs/issues/673
We just use this for fetching Google Fonts, so it's not worth it: Fall back to using `ring` on these platforms.
### CI Testing
Manually triggered a build-and-release job so that it tries to build for the whole platform matrix: https://github.com/vercel/next.js/actions/runs/20936098900
### Manual Testing
Followed the test plan in https://github.com/vercel/next.js/pull/81818

DirList is how require.context internally represents a list of files
matching a requested pattern. I'd like to be able to re-use it for
import.meta.glob as well, with a few tweaks. Most of this diff is just
breaking it out into its own module.
The filter used to be an EsRegex. It's now an enum, with a possible Glob
variant. In either path, it takes a pattern and finds file paths that
match it.
I also added some sanity tests for both filters, but there's definitely
room to expand to more test cases.
Before:
```
Module not found: Can't resolve 'next/dist/esm/build/templates/helpers'
failed to analyze ecmascript module '[project]/node_modules/.pnpm/next@file+..+next-repo-2bf56e024dbccc85257a8c308492f2375d690b206153b02d3bea2029707d4c38+packa_tacnzof7rtmb5bwqa7bdnfcdbq/node_modules/next/dist/esm/build/templates/helpers.js [ssr] (ecmascript)'
Caused by:
- failed to parse [project]/node_modules/.pnpm/next@file+..+next-repo-2bf56e024dbccc85257a8c308492f2375d690b206153b02d3bea2029707d4c38+packa_tacnzof7rtmb5bwqa7bdnfcdbq/node_modules/next/dist/esm/build/templates/helpers.js
- Transforming and/or parsing of [project]/node_modules/.pnpm/next@file+..+next-repo-2bf56e024dbccc85257a8c308492f2375d690b206153b02d3bea2029707d4c38+packa_tacnzof7rtmb5bwqa7bdnfcdbq/node_modules/next/dist/esm/build/templates/helpers.js failed
- failed to deserialize `swc_common::plugin::diagnostics::PluginCorePkgDiagnostics`
- Mismatch { name: "array", found: 48 }
Debug info:
- Execution of <ModuleAssetContext as AssetContext>::process_resolve_result failed
- Execution of <ModuleAssetContext as AssetContext>::process failed
- Execution of <EcmascriptModuleAsset as Module>::side_effects failed
- Execution of analyze_ecmascript_module failed
- failed to analyze ecmascript module '[project]/node_modules/.pnpm/next@file+..+next-repo-2bf56e024dbccc85257a8c308492f2375d690b206153b02d3bea2029707d4c38+packa_tacnzof7rtmb5bwqa7bdnfcdbq/node_modules/next/dist/esm/build/templates/helpers.js [ssr] (ecmascript)'
- Execution of <EcmascriptModuleAsset as EcmascriptParsable>::failsafe_parse failed
- Execution of parse failed
- failed to parse [project]/node_modules/.pnpm/next@file+..+next-repo-2bf56e024dbccc85257a8c308492f2375d690b206153b02d3bea2029707d4c38+packa_tacnzof7rtmb5bwqa7bdnfcdbq/node_modules/next/dist/esm/build/templates/helpers.js
- Transforming and/or parsing of [project]/node_modules/.pnpm/next@file+..+next-repo-2bf56e024dbccc85257a8c308492f2375d690b206153b02d3bea2029707d4c38+packa_tacnzof7rtmb5bwqa7bdnfcdbq/node_modules/next/dist/esm/build/templates/helpers.js failed
- failed to deserialize `swc_common::plugin::diagnostics::PluginCorePkgDiagnostics`
- Mismatch { name: "array", found: 48 }
Import map: aliased to module 'next' with subpath '/dist/esm/build/templates/helpers' inside of [project]/
https://nextjs.org/docs/messages/module-not-found
```
After:
```
./packages/next/document.js
Failed to execute SWC plugin
An unexpected error occurred when executing an SWC EcmaScript transform plugin.
This might be due to a version mismatch between the plugin and Next.js.
Failed to execute @swc/plugin-react-remove-properties
Caused by:
0: failed to deserialize `swc_common::plugin::diagnostics::PluginCorePkgDiagnostics`
1: Mismatch { name: "array", found: 48 }
```
Replace the `mimalloc_rspack` to the orign `mimalloc`, since
`mimalloc_rspack` is formerly aimed to support the `mimalloc` `v3`, but
`mimalloc` already support `v3` feature now.
I grepped for `Serialize` and `Deserialize`, and (because it's hard to automate this) went through everything by hand to try to remove as many unused trait implementations as possible.