## 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
This:
- Fetches `data/_app/analyze.data` alongside the current route's data
- Merges `_app` modules into the current route's `AnalyzeData` before
visualization using a `mergeAnalyzeData()`, which merges the two
datasets, reindexing the additional ones and recreating the binary data
format expected by the treemap.
## Test plan
- [x] Add a large dependency (e.g. `moment`) in `pages/_app.tsx`
- [x] Run `next experimental-analyze`
- [x] Open the bundle analyzer and select a pages page
- [x] Verify moment appears in the treemap
- [x] Verify App Router projects (no `_app`) work without errors
Initial version of build-time instant validation.
During prerendering, if we have `unstable_instant` configs, we perform a
complete staged render of the page (including dynamic data). This render
mocks request data (`params`/`searchParams`/`cookies`/`headers`) using
the `samples` provided in `unstable_instant`. Then, we use the staged
chunks to run instant validation in mostly the same way that we would in
dev. Most changes in the validation flow are related to samples, which
also need to be passed into the client render to mock APIs like
`useParams`.
### Providing Mock data
We run everything in a fresh workStore+workUnitStore to (hopefully)
avoid any data from the prerender from sneaking in. This is quite
involved.
A big part of the trickiness is that if you do e.g. `(await
cookies()).get('myCookie')` but the `samples` don't contain `myCookie`,
we want the validation to error and require explicitly saying what state
`myCookie` is in.
If it's available, use
```ts
export const unstable_instant = {
// ...
samples: { cookies: [{ name: "myCookie", value: "someValue" }] }
}
```
If it's not available, use
```ts
export const unstable_instant = {
// ...
samples: { cookies: [{ name: "myCookie", value: null }] }
}
```
This explicitness makes it harder to e.g. unintentionally validate a
logged-out state.
Note that even validating a `prefetch: 'static'` page might require
samples, because we need the shared parent for the navigation to be
(mostly) complete. We might be able to relax this in the future, but
it's not straightforward.
Another issue is that a segment might access a missing sample in a
"benign" way, i.e. where it wouldn't block validation (or be inside a
suspense boundary). Ideally we wouldn't require providing a sample for
usages like that, but currently we do.
### Instrumenting APIs to detect missing samples
All request APIs are instrumented in this way.
- `params` throw when accessing a property that was not declared in
`samples`. same for the object from `useParams()`. `in` is allowed
because we know the shape of the params object statically, based on the
routing structure.
- `searchParams` throw when accessing a property that was not declared
in `samples` and `in` checks.
- objects returned from `cookies()`, `headers()`, `useSearchPrams()`
throw on `get()` and `has()`.
There's a lot of tests for this behavior in `instant-validation-build`.
One gap is that iteration/enumeration is currently not instrumented.
We're punting on it for now and will revisit in a follow-up.
### Relation to prerender
We kick off the validation after the prerender, in
`renderToHTMLOrFlightImpl`. Ideally, we should kick this off somewhere
higher up, separately from the prerender itself, but we're leaving that
for future work.
Note that if we do multiple prerenders for the same page (e.g. due to
`generateStaticParams`, we'll only run validation for the first
prerender for that route. `generateStaticParams` is not currently
integrated into the validation at all, i.e. we don't use those params to
validate, so this is safe. All params need to be provided via `samples`.
This will likely change in the future, but we're not sure how yet.
### Disabling build validation
Build-time instant validation is experimental, and has more potential
problems to deal with than dev validation. To allow incremental rollout
and testing, we allow disabling it per instant config:
```ts
export const unstable_instant = {
unstable_disableBuildValidation: true
// ...
}
```
for parity, there's also `unstable_disableBuildValidation`, but we don't
expect that to see much use.
In utoo case, we need to support react classic jsx transform(To
compitable with react 16 or above project).
We have a case like:
```ts
// index.ts
import React from 'react';
export const App = () => <div></div>;
```
Under the classic jsx transform, the jsx will be transformed as follows:
```ts
export const App = () => React.createElement("div", /***/);
```
But turbopack's jsx transform was behind typescript transform, so in the
stage of typescript transform, the import of react will be deleted by
the default config of swc typescript transform(which set
`verbatimmodulesyntax` as false by default):
https://swc.rs/docs/migrating-from-tsc#verbatimmodulesyntax-true
Then the output of classic jsx transform will get a runtime error like
`React was not found`.
For solve the problem, this pr just support a config
`verbatimModuleSyntax` for turbopack's typescript transform, it will not
remove user's React import under classic jsx transform.
We test the case under utoopack in the following pr:
- https://github.com/utooland/utoo/pull/2653
- https://github.com/utooland/utoo/pull/2655
And Rspack also enable `verbatimModuleSyntax` to true by default under
2.0:
https://github.com/web-infra-dev/rspack/discussions/9270#discussioncomment-13429924
```
let x: Ident = IdentName::new(name, span).into();
```
should never be done. It uses `SyntaxContext::default()` for the Ident. So from swc's perspective, you are referring to a different variable than you think. When constructing an `Ident`, you should usually always provide a SyntaxContext.
I think this would pose a problem in Webpack because it serializes it into a code string again, but for Turbopack these SyntaxContext are important.
## What?
Calls `unref()` on the `ThreadsafeFunction`s created in
`NapiNextTurbopackCallbacks::from_js` (`throw_turbopack_internal_error`
and `on_before_deferred_entries`).
## Why?
These `ThreadsafeFunction`s are not unref'd, which means they keep the
Node.js event loop alive even after `project_shutdown` completes.
Currently this doesn't cause a visible hang in Next.js because turbopack
runs inside a worker thread (via `packages/next/src/lib/worker.ts`), and
the worker thread is terminated externally. However, if turbopack were
ever run in a child process or directly in the main process, the build
would hang indefinitely after completion — the Rust side of
`stop_and_wait()` finishes, but the Node.js process never exits because
the ref'd `ThreadsafeFunction`s prevent the event loop from draining.
This happened in `utoo`, caused some CI hang forever, the same fix
commit:
https://github.com/utooland/utoo/pull/2670/changes/9e8fd08e84470dce6ac59e976db1b556ce7fece8.
This fix makes the shutdown behavior more robust by following the same
pattern already used in `register_worker_scheduler` in
`turbopack-node/src/worker_pool/worker_thread.rs`, where both creator
and terminator `ThreadsafeFunction`s are explicitly unref'd.
## How?
- Added `Env` parameter to `NapiNextTurbopackCallbacks::from_js()`
- Called `unref(&env)` on both `throw_turbopack_internal_error` and
`on_before_deferred_entries` after creation
- Updated the call site in `project_new` to pass `&env`
### 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.
Previously, we used `RuleCondition::ReferenceType(ReferenceType)`. But there are some problem with using the type itself as a matcher:
1. the sub types' Undefined variant (like `CssReferenceSubType::Undefined`) always matched all possible variants. So it was actually impossible to match exactly `CssReferenceSubType::Undefined`
2. variants with more complex types were also impossible to match against, like `ReferenceType::Inner(ResolvedVc<...>)`
### What?
Implements dependency-level HMR accept and decline for Turbopack,
covering both ESM (`import.meta.turbopackHot`) and CJS (`module.hot`)
modules.
Previously Turbopack only supported self-accept (`module.hot.accept()`
with no arguments) and self-decline. This PR adds the full
dependency-targeted API:
- `module.hot.accept(dep, cb)` / `import.meta.turbopackHot.accept(dep,
cb)` — single dep
- `module.hot.accept([depA, depB], cb)` /
`import.meta.turbopackHot.accept([depA, depB], cb)` — array of deps
- `module.hot.decline(dep)` / `import.meta.turbopackHot.decline(dep)` —
single dep
- `module.hot.decline([depA, depB])` /
`import.meta.turbopackHot.decline([depA, depB])` — array of deps
### Why?
Libraries like `react-refresh` and user code often need to accept
updates for specific dependencies rather than self-accepting the entire
module. Without this, those HMR patterns fall back to full page reloads
in Turbopack.
### How?
**Runtime** (`turbopack-ecmascript-runtime`):
- Extended `HotState` with `acceptedDependencies`,
`acceptedErrorHandlers`, and `declinedDependencies` maps (keyed by
`ModuleId`)
- Updated `hot.accept()` and `hot.decline()` to handle string and array
signatures
- Updated `getAffectedModuleEffects` to check accepted/declined
dependencies when propagating updates through the module graph
- Added `'declined'` effect type that throws an `UpdateApplyError`
- Track `outdatedDependencies` (map of parent → set of updated deps)
alongside `outdatedModules`
- In the apply phase, invoke per-dependency accept callbacks with the
correct outdated deps
- Use `Set` for `outdatedDependencies` dedup (O(1) vs O(n) lookups)
**Compiler** (`turbopack-ecmascript`):
- Added `ModuleHotReferenceAssetReference` — a single asset reference
type for both accept and decline deps, with shared resolve logic for ESM
(`esm_resolve`) and CJS (`cjs_resolve`)
- Added `ModuleHotReferenceCodeGen` — generates code that replaces dep
string literals with resolved module IDs at compile time
- ESM binding auto-update: when an ESM module accepts a dependency that
it also `import`s, the compiler wraps the accept callback to re-import
the namespace variable (`__TURBOPACK__imported__module__<id> =
__turbopack_import__(<id>)`) before the user callback runs, so ESM
bindings reflect updated values without needing `require()`
- Added `import.meta.turbopackHot` as the ESM equivalent of
`module.hot`, with TypeScript type declarations in
`packages/next/types/global.d.ts`
- Static analysis extracts dep strings from
`module.hot.accept`/`decline` calls; non-analyzable deps emit a warning
with distinct error codes (`TP1204` for accept, `TP1205` for decline)
**HMR gating** (`CompileTimeInfo`):
- Added `hot_module_replacement_enabled` flag to `CompileTimeInfo` to
gate recognition of `module.hot` and `import.meta.turbopackHot` as
well-known objects
- Without this flag, production builds would recognize
`module.hot.accept(...)` and generate HMR-specific code, leading to
runtime errors
- Flag set to `true` for dev servers and `false` for production builds
across Next.js and turbopack-cli entry points
- `import.meta.turbopackHot` getter is only emitted when HMR is enabled
**Server HMR** (`next-api`, `next-core`):
- Server-side `compile_time_info` now also sets
`hot_module_replacement_enabled` so that `module.hot` /
`import.meta.turbopackHot` are recognized during analysis of server
modules
- Server HMR requires the `--experimental-server-fast-refresh` CLI flag;
the flag is passed through `ProjectOptions` to the Rust side so
`server_compile_time_info` only enables HMR when appropriate
**Tests** (`test/development/app-dir/hmr-dep-accept/`):
- ESM single dep accept — verifies parent module is not re-evaluated,
accept callback fires, ESM bindings auto-update
- ESM array dep accept — same as above with `accept(['./dep-a',
'./dep-b'], cb)`
- CJS `module.hot.accept` — pure CJS dep observer pattern with `.cjs`
files
- Single dep decline — verifies full page reload occurs
- Array dep decline — verifies full page reload with
`decline(['./dep-a', './dep-b'])`
I don't think we have any tests that the server utility chunking actually works.
But I can't actually properly remember why we added it in the first place: https://github.com/vercel/next.js/pull/70461
Now all of the chunking context functions accept `ChunkGroup`
The names are bad, but `entry_chunk_group` and `ChunkGroup::Entry` have nothing to do with each other:
- `entry_chunk_group` means: it's a standalone file where `node path/to/chunk.js` will execute it
- `ChunkGroup::Entry` = it's the top-level chunk group
For example a `evaluated_chunk_group(ChunkGroup::Entry)` is also fine
This method is needed with `turbo-tasks` to implement `OperationVc`, but exposing it to other crates turns it into a footgun, because it allows you to attempt to read a `Vc` or `ResolvedVc` with strong consistency, which doesn't have a well-defined meaning.
## Summary
A series of bug fixes, refactors, and code organization improvements to Turbopack's tree shaking and side effect optimization:
- **Fix `ModulePart` handling for `FreeVarReference::EcmaScriptModule`** — pass `ModulePart` correctly so free variable references resolve to the right module part
- **Extract `EcmascriptModuleRenameModule`** — split the rename/reexport logic (`RenamedExport`, `RenamedNamespace`) out of `EcmascriptModuleFacadeModule` into a dedicated `rename` module, shared between side effects optimization and tree shaking
- **Simplify `EcmascriptModuleFacadeModule`** — now only handles the `Facade` case (locals + reexports), no longer overloaded with rename variants
- **Reorganize tree shaking code** — moved `asset.rs` → `part/module.rs`, `chunk_item.rs` → `part/chunk_item.rs` + `side_effects/chunk_item.rs`, `side_effect_module.rs` → `side_effects/module.rs` for clearer separation between part modules and side effect modules
- **Use `EsmExport` in `FindExportFromReexportsResult`** — reduces code duplication by reusing the existing `EsmExport` enum
- **Minor simplifications** — code deduplication, reordered checks, and general cleanup
## Summary
- Add a generic `TraceEvent` compilation event type that carries a name, wall-clock timing, and arbitrary attributes
- Emit `TraceEvent` from Turbopack's Rust backend for cache persistence and compaction operations
- Expose `eventJson` on compilation events through the NAPI bridge
- Record `turbopack-persistence` and `turbopack-compaction` trace spans in `.next/trace` with memory usage snapshots, in both dev and build workflows
## Details
Turbopack persistence and compaction operations were invisible in `.next/trace`. The only signal was a console log for operations exceeding 10s.
A new generic `TraceEvent` type replaces the need for per-operation event structs. It carries a name, start/end wall-clock timestamps, and a `Vec` of key-value attributes. On the JS side, `backgroundLogCompilationEvents` handles all `TraceEvent`s with a single code path — creating trace spans via `manualTraceChild` and recording memory usage snapshots. Adding new traced operations requires only a few lines of Rust and zero JS changes.
At @bgw's suggestion, I considered integrating as a `tracing_subscriber::Layer` to automatically forward spans to the compilation event system, but this ended up requiring quite a bit of code (a new subscriber, 'target' impl, span lifecycle management, new crate dependencies) for what is currently just 2 events, and we don't really anticipate adding more.
## Test plan
- [x] Integration test in `test/e2e/filesystem-cache` verifying `turbopack-persistence` spans exist with correct attributes
- [x] `pnpm test-dev-turbo test/e2e/filesystem-cache`
- [x] `pnpm test-start-turbo test/e2e/filesystem-cache`
# What
Add an ast-grep rule to use `bail!` rather than `Err(anyhow!(` in a
return position.
# Why
This is slightly more compact, and allows us to port more code over to
turbofmt/turbobail.
Note: _this PR was previously merged into another branch due to a GH
bug_
# What
Add turbofmt! and turbobail! proc macros for async string formatting
with Vc<T> and ResolvedVc<T> values.
A new, agent-friendly `FORMATTING.md` file is added to help agents
figure out how these macros work.
# Why
Formatting strings that include turbo-tasks values currently requires
manually .await?ing each Vc, then calling format!, then converting to
RcStr via .into(). By using macros, we can reduce a lot of boilerplate
and potentially offer some opportunities for optimizations by reducing
intermediate, throwaway tasks.
# How
We make use of the "auto-deref" pattern in Rust to choose the "best"
formatting option: ValueToString or Display.
- Zero-alloc FormatIter: A shared iterator parser over format strings
(RawString, EscapedBrace, VarRef, VarRefFormat) that both macros and the
derive use, replacing duplicated manual parsing.
- Borrow-based capture: turbofmt! uses async { .. } (not async move), so
non-Copy types like FileSystemPath can be used in multiple turbofmt!
calls without cloning.
- Debug on StringifyType: Enables {:?} format specs in turbofmt!.
- Converted ~30 call sites across next-core, turbo-tasks-fs,
turbopack-core, turbopack-ecmascript, and others.
# What's Left
- Constants cannot be formatted with turbofmt! yet because of arcane
Rust hygiene rules (similar issue to
https://github.com/rust-lang/rust/issues/131446)
- rust-analyzer doesn't show these format strings as "real" format
strings yet because of limitations of the analyzer, but this should be
possible to make work.
- Automatic derive for ValueToStringRef to avoid intermediate tasks in a
few places where formatting is trivial
- turbowrite!/turbowriteln! could clean up a bit more code
## 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?
Adds a lint script (`scripts/check-unused-turbo-tasks.mjs`) that detects
unused `#[turbo_tasks::function]`, `#[turbo_tasks::value]`, and
`#[turbo_tasks::value_trait]` definitions across the Rust codebase, and
removes all currently unused items.
### Why?
Dead turbo-tasks definitions add compilation overhead (each generates
trait impls, registrations, etc.) and make the codebase harder to
navigate. A lint check prevents regressions.
### How?
**New script** (`scripts/check-unused-turbo-tasks.mjs`):
1. Discovers all `.rs` files in `turbopack/crates/` and `crates/`
(excluding `turbo-tasks-macros-tests`)
2. Parses three kinds of turbo-tasks annotations:
- `#[turbo_tasks::function]` → extracts `fn` name + context (free,
inherent impl, trait impl, trait default)
- `#[turbo_tasks::value]` → extracts `struct`/`enum` name
- `#[turbo_tasks::value_trait]` → extracts `trait` name
3. Scans all files for identifier references, excluding definition sites
4. Reports any name that has no external references
The script is added to the `pnpm lint` command (runs in parallel with
other checks).
**Removed 23 unused items** across 18 files:
| Kind | Count | Examples |
|------|-------|---------|
| `value` (struct/enum) | 9 | `OutputAssetsWithAvailability`,
`ModuleNameMap`, `OptionModuleIds`, `LockedVersions`,
`AssetContentSnapshot` |
| `function` | 14 | `client_env`, `next_js_file`, `convert_path`,
`is_hot_module_replacement_enabled`,
`any_content_changed_of_output_asset` |
Also cleaned up imports that became unused after the removals.
### What?
Skip the Pages Router global CSS restriction for `data:` URL imports
(similar to app router and mixed app/pages router)
### Why?
`data:text/css` should work the same no matter if pages router, app
router or mixed router usage
### How?
the validation in `crates/next-api/src/module_graph.rs` already had a
carve-out for `node_modules`. This adds the same for data URL modules.
fixes#89900
- [x] Related issues linked using `fixes #89900`
- [x] Tests added
---------
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
### 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.
In a few `next build` commands i saw multiple snapshots due to this. Idle callbacks were firing near the end of the build.
```
Creating an optimized production build ...
WARNING: The git repository is dirty, but File System Cache is still enabled. Manual removal of the filesystem cache database might be required.
✓ Finished writing to filesystem cache because: idle timeout in 6.1s
✓ Compiled successfully in 69s
✓ Collecting page data using 13 workers in 85ms
Build ran with "compile" mode, to finalize the build run either "generate" or "generate-env" mode as well
✓ Finalizing page optimization in 32ms
Route (app)
...ommitted route table...
✓ Finished writing to filesystem cache because: stop in 3.5s
```
This is a bit wasteful since it is more efficient to write everything in a single batch at the end.
The solution is just to not schedule snapshotting operations in 'short sessions' (aka `next build`)
`unstable_instant` requires `cacheComponents` to function — without it,
validation is entirely skipped and the config silently does nothing.
This adds a compile-time error in the SWC transform that catches the
misconfiguration early, matching the existing pattern used for segment
configs like `revalidate` that are incompatible with `cacheComponents`
(but inverted — `unstable_instant` requires it rather than being
incompatible with it). The error points directly to the export in the
user's source file.
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:
## For Contributors
### Improving Documentation
- Run `pnpm prettier-fix` to fix formatting issues before opening the PR.
- Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide
### Fixing a bug
- Related issues linked using `fixes #number`
- Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md
### Adding a feature
- Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md
## For Maintainers
- Minimal description (aim for explaining to someone not on the team to understand the PR)
- When linking to a Slack thread, you might want to share details of the conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic behind a change
### What?
### Why?
### How?
Closes NEXT-
Fixes #
-->
Follow-up to https://github.com/vercel/next.js/pull/86266
This was mostly LLM-generated, but it's just moving things around and threading through a config option.
We'd like to be able to turn this on/off with an experimental config option:
\- Fixes issues where RA thinks a bunch of code is dead.
\- Lets us isolate potential issues to the worker thread implementation.
\- Provides an escape hatch for users in case there are issues.
\- Better fits the cargo "additive feature" model.
This allows specifying a different deployment id for static immutable assets
- [x] rename `getImmutableAssetToken` to `getAssetToken` (because it can be non-immutable as well)
- [x] disable for webpack
Closes PACK-6534
For https://github.com/nextjs/adapter-vercel/pull/25
Part of PACK-6802
Instead of just limiting the transform to any file within the app dir, we now restrict it to any page, default, or layout file in the app dir. This avoids having the transform applied to random modules. The inserted value would not have been used by anything.
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.
## Summary
Adds the Next.js version as a `version` field on the Turbopack
"initialize project" tracing span. The version is passed from the
TypeScript side (`process.env.__NEXT_VERSION`) through the NAPI bindings
into Rust `ProjectOptions`, where it is recorded on the span.
This makes it easy to correlate trace data with the specific Next.js
version that produced it.
Also standardizes all version reads in `packages/next/src/` to use
`process.env.__NEXT_VERSION` (which is inlined at build time by
`taskfile-swc.js`) instead of `require('next/package.json').version` or
`import { version } from 'next/package.json'`.
### Changes
**Trace span (commit 1):**
- **`crates/next-api/src/project.rs`** — Added `next_version` field to
`ProjectOptions`; recorded as `version` on the `"initialize project"`
span
- **`crates/next-napi-bindings/src/next_api/project.rs`** — Added
`next_version` to `NapiProjectOptions` and wired it through the `From`
impl
- **`crates/next-build-test/src/main.rs`** — Added `next_version` to
test `ProjectOptions` init
- **`packages/next/src/build/swc/generated-native.d.ts`** — Added
`nextVersion` to the TypeScript `NapiProjectOptions` interface
- **`packages/next/src/server/dev/hot-reloader-turbopack.ts`**,
**`packages/next/src/build/turbopack-build/impl.ts`**,
**`packages/next/src/build/turbopack-analyze/index.ts`** — Pass
`nextVersion: process.env.__NEXT_VERSION` when creating the Turbopack
project
- **`test/development/basic/next-rs-api.test.ts`** — Added `nextVersion`
to both `createProject` call sites
**Consistent `__NEXT_VERSION` usage (commit 2):**
- **`packages/next/src/server/dev/hot-reloader-shared-utils.ts`** —
`require('next/package.json').version` → `process.env.__NEXT_VERSION`
- **`packages/next/src/lib/patch-incorrect-lockfile.ts`** —
`nextPkgJson.version` → `process.env.__NEXT_VERSION`; narrowed import to
only `optionalDependencies`
- **`packages/next/src/telemetry/events/swc-load-failure.ts`** — `import
{ version as nextVersion }` → `process.env.__NEXT_VERSION`; narrowed
import to only `optionalDependencies`
## Test Plan
- Verified Rust compilation with `cargo check -p next-api`, `cargo check
-p next-build-test`
- Verified TypeScript types with `pnpm --filter=next types`
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Replaces the runtime `mark_root()` call with a declarative `root` flag
on the `#[turbo_tasks::function]` attribute macro. This moves root task
aggregation number assignment from an imperative runtime call to a
compile-time declaration, making the intent clearer and applying the
aggregation number at task creation time rather than during execution.
### What changed
- **Macro parsing**: Added `root` as a new flag option for
`#[turbo_tasks::function(root)]` (can be combined with other flags, e.g.
`#[turbo_tasks::function(operation, root)]`)
- **NativeFunction**: Added `is_root: bool` field and parameter to all
three constructors (`new_function`, `new_method`,
`new_method_without_this`)
- **Backend**: In `get_or_create_persistent_task` and
`get_or_create_transient_task`, checks `native_fn.is_root` on brand-new
task creation (not cache hits or backing storage restores) and sets
`UpdateAggregationNumber` with `u32::MAX`
- **Usage site**: Changed `whole_app_module_graph_operation` in
`crates/next-api/src/project.rs` from
`#[turbo_tasks::function(operation)]` + `mark_root()` to
`#[turbo_tasks::function(operation, root)]`
- **Cleanup**: Removed `mark_root()`, `set_own_task_aggregation_number`
from `TurboTasksApi` trait, `Backend` trait, and all implementations
(turbo-tasks-backend, turbo-tasks-testing)
### Key design decisions
- Root aggregation number is only set on **brand-new task creation**
(Vacant entry in the task cache when not found in backing storage
either), not on cache hits or when restoring from backing storage
- The `root` flag is stored on `NativeFunction` (which is a static
singleton per function), so it's accessible via
`CachedTaskType.native_fn.is_root` in the backend without any additional
runtime overhead
## Test Plan
- `cargo check -p turbo-tasks-macros -p turbo-tasks -p
turbo-tasks-backend -p next-api` passes
- `cargo test -p turbo-tasks-backend` passes
- `pnpm swc-build-native` builds successfully
The `unstable_instant` route segment config is not allowed in Client
Components because the API mainly depends on prefetches, which Client
Components aren't part of.
Closes NAR-774
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
## What
Refactors module splitting and re-export handling to prepare for the name mangling PR.
### Changes
1. **`EsmExports::reexport_including_default` helper** — New helper that creates re-export facades (default + star export) from a module reference. Replaces duplicated boilerplate in `NextDynamicEntryModule`, `NextServerComponentModule`, and `NextServerUtilityModule`.
2. **`get_locals_if_split` / `get_facade_if_split` on `EcmascriptModuleAsset`** — Encapsulates the split-check-then-wrap logic that was previously inlined in `turbopack/src/lib.rs`. Returns the locals or facade module when the module has re-exports that should be separated, otherwise returns self.
3. **Simplified `apply_module_type` in `turbopack/src/lib.rs`** — The tree-shaking `ReexportsOnly` path now uses the new helpers instead of manually checking `split_locals_and_reexports` and constructing facade/locals modules inline.
4. **Documentation improvements** — Added doc comments to `EsmExport` variants, `EsmExports` fields, and `split_locals_and_reexports`.
## Why
Extracting these helpers simplifies the upstream code and reduces the diff for the name mangling PR. The `reexport_including_default` helper is needed because the next stage will require additional work for virtual facade modules to function with mangled names. The `get_locals_if_split` / `get_facade_if_split` methods centralize split logic so that next.js module wrappers (which need unmangled export names) can use them directly.
The `wasm` and `next-custom-transforms` crates were still on Rust edition 2018 while the rest of the crates in this repo use edition 2024. This caused a pre-commit hook conflict:
- `cargo fmt` passes the crate's own edition (2018) to `rustfmt`, which uses **case-insensitive** import sorting
- The lint-staged pre-commit hook runs `rustfmt --edition 2024 --`, which uses **ASCII order** import sorting (uppercase before lowercase)
These two sort orders disagree on items like `{custom_before_pass, TransformOptions}` vs `{TransformOptions, custom_before_pass}`, causing an infinite flip-flop: every commit would reorder imports, and the next `cargo fmt` would reorder them back.
## Changes
- Bump `crates/wasm` and `crates/next-custom-transforms` from edition 2018 to 2024
- Add `+ use<C>` precise capturing to `server_actions()` return type to satisfy edition 2024's stricter `impl Trait` lifetime capture rules
- Add `style_edition = "2024"` to `.rustfmt.toml` as a safety net so `cargo fmt` uses 2024-style formatting regardless of individual crate editions
- Reformat all affected files with `cargo fmt`
- Apply new clippy rules
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.