mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/ledgers-react-sync
1409 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
143d9ca1ab |
feat(turbopack): change DiskFileSystem root to Vc<RcStr> for portable cache (#92603)
### What? Changes the `root` parameter of `DiskFileSystem::new`, `DiskFileSystem::new_with_denied_paths`, and the internal `DiskFileSystem::new_internal` from `RcStr` to `Vc<RcStr>`. ### Why? Part of the effort to make the Turbopack persistent cache portable when the absolute path of the project directory changes. By accepting the root as a `Vc<RcStr>` instead of a plain `RcStr`, the path is tracked as a turbo-tasks value. This allows the cache to remain valid even when the project root moves (e.g. on a different machine, a different CI working directory, or a relocated checkout), since the value can be substituted independently without invalidating unrelated cached computations. ### How? - `new_internal` is now an `async fn` that awaits the `Vc<RcStr>` to extract the underlying `RcStr` for constructing `DiskFileSystemInner`. It returns `Result<Vc<Self>>` accordingly. - `new` and `new_with_denied_paths` (the public constructors, which are not `#[turbo_tasks::function]` themselves) are updated to accept `Vc<RcStr>` and forward it to `new_internal`. - All 23 call sites across the codebase are updated to wrap their root string with `Vc::cell(...)` before passing it. Verified with `cargo clippy --all-targets` (clean), `cargo test -p turbo-tasks-backend` (all pass), and `cargo test -p turbopack-tests` (all 89 pass). <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
74cdcde87e |
feat(turbopack): add LocalPathOrProjectPath PostCSS config resolution (#91338)
### What? Adds a new `experimental.turbopackLocalPostcssConfig` option and corresponding `PostCssConfigLocation::LocalPathOrProjectPath` variant so Turbopack can resolve `postcss.config.js` starting from the CSS file's directory first, falling back to the project root. Also fixes an issue that surfaces when multiple per-directory `postcss.config.js` files are used: **Evaluate pool asset name collisions** — output file names for the Node.js evaluate pool are now derived from an xxh3 hash of the full module ident rather than the bare file name, so multiple `postcss.config.js` files in different directories no longer collide. ### Why? Currently Turbopack uses `ProjectPathOrLocalPath` which checks the project root first, then falls back to the local directory. This means per-directory `postcss.config.js` files cannot override the root config — the root config always wins. For projects that need different PostCSS transforms in different directories (e.g. a monorepo with multiple apps or style directories), the more specific config should take precedence. ### How? **Turbopack PostCSS resolution** (`turbopack/crates/turbopack-node/src/transforms/postcss.rs`): - Added `PostCssConfigLocation::LocalPathOrProjectPath` enum variant with doc comments on all variants - Refactored `find_config_in_location()` to express the search order as a `Vec<FileSystemPath>` built from a single `match`, making each variant's strategy immediately clear **Evaluate pool asset file naming** (`turbopack/crates/turbopack-node/src/evaluate.rs`): - Replaced file-name-based output path derivation with a deterministic xxh3 hash of the full module ident - Adds `turbo-tasks-hash` dependency to `turbopack-node` - Eliminates name collisions when multiple entries share the same file name (e.g. per-directory `postcss.config.js` files) **Next.js config flag** (`packages/next/src/server/config-shared.ts`, `config-schema.ts`): - Added `experimental.turbopackLocalPostcssConfig: boolean` option (opt-in, default `false`) - Wired through `crates/next-core/src/next_config.rs` → both client and server contexts (`next_client/context.rs`, `next_server/context.rs`) - When `true`, uses `LocalPathOrProjectPath`; when `false`/unset, uses the existing `ProjectPathOrLocalPath` (no behavior change by default) **E2E test** (`test/e2e/app-dir/turbopack-postcss-multiple-configs/`): - Runs in both **Turbopack dev and production modes** — skips webpack only (webpack does not support function-valued PostCSS plugins and the `turbopackLocalPostcssConfig` flag is Turbopack-specific) - 5 style directories, each with its own `postcss.config.js` passing a **unique color option** (`blue`, `purple`, `orange`, `cyan`, `magenta`) to a shared PostCSS plugin, proving that per-directory config resolution produces distinct results rather than all sharing the same transform - Root `postcss.config.js` is a no-op — if only the root config were used, CSS would remain unmodified - Test validates all 15 elements (5 dirs × 3 files) render with CSS module classes - Test collects all CSS (inline `<style>` tags for dev, linked `.css` files for production) and asserts each directory's expected color is present and `color: red` is absent **Documentation** (`docs/01-app/03-api-reference/05-config/01-next-config-js/turbopackLocalPostcssConfig.mdx`, `docs/01-app/03-api-reference/08-turbopack.mdx`): - New dedicated reference page for `experimental.turbopackLocalPostcssConfig` covering usage, behavior table, and a directory-tree example - Added to the experimental options table in the Turbopack API reference ## Test plan - [x] `cargo check -p turbo-tasks-backend` passes - [x] `cargo check -p turbopack-node` and `cargo check -p next-core` pass - [x] E2E test `turbopack-postcss-multiple-configs` validates per-directory PostCSS config resolution across 5 directories with distinct per-directory colors (Turbopack dev and production modes) - [x] Test correctly skips in webpack mode and deployment - [x] Documentation page and experimental options table updated <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> |
||
|
|
35b5582647 |
Remove a ton of turbo task functions from Issue trait items (#92623)
## What? Remove `#[turbo_tasks::function]` from all methods on the `Issue` value_trait, following the same pattern as #92593 which did the same for `ValueDebug`. The `Issue` trait previously had 8 methods decorated with `#[turbo_tasks::function]`, which caused the turbo-tasks machinery to generate `NativeFunction` statics, task registration, vtable entries, and scheduling/caching infrastructure for each method on each of the ~48 `impl Issue for` types in the codebase — most of which are trivially synchronous (returning a constant string, enum variant, or stored struct field). ## How? - Change all 8 `Issue` trait methods to plain methods. Methods that are always synchronous become direct return-type methods (`severity`, `stage`, `source`, `documentation_link`). Methods that are genuinely async on at least one impl use `Pin<Box<dyn Future<Output = Result<T>> + Send + '_>>` for object safety. - Callers that need to invoke trait methods on a `Vc<Box<dyn Issue>>` use `issue.into_trait_ref().await?` to get a `TraitRef<Box<dyn Issue>>` and call methods directly on that. - `PlainIssue::from_issue` (the sole hot caller that materializes all fields) resolves one `trait_ref` and calls all methods on it, similar to the `ValueDebug` pattern. - `ResolvingIssueWithLocation` (which wraps an inner `ResolvedVc<Box<dyn Issue>>`) eagerly copies `severity`, `stage`, `documentation_link`, and `source` at construction time rather than delegating async calls to the inner issue. - Delete `OptionIssueSource` and `OptionStyledString` wrapper types, which existed solely as `Vc<T>` return type wrappers for the old turbo-tasks methods. ## Tradeoffs **Lost per-call caching:** Previously each `Issue` method was a turbo-tasks task, so results were cached per `(Vc<Issue>, method)`. In practice this caching was useless — issues are emitted once and their fields read once by `PlainIssue::from_issue`, so there was never a second call to hit the cache. The only real cost is that `IssueFilter::matches` previously cached per `(filter, issue)` pair; that caching is preserved because `matches` itself remains a `#[turbo_tasks::function]` and now calls `into_trait_ref().await?` once at the top. **`ResolvingIssueWithLocation` eagerly reads inner fields:** Instead of lazily delegating to the inner issue, `stage` and `documentation_link` are copied at construction. This is a minor semantic change (the values are snapshotted rather than live), but in practice these fields are always constant values so it makes no difference. ## Binary size impact (darwin-arm64, compared against #92593) | Metric | #92593 baseline | This PR | Delta | |---|---|---|---| | Unstripped dylib | 124.6 MB | 124.0 MB | **-0.6 MB (-0.5%)** | | Stripped | 84.7 MB | 84.4 MB | **-0.3 MB (-0.4%)** | | Stripped + gzip (npm) | 29.2 MB | 29.1 MB | **-0.1 MB (-0.3%)** | Smaller savings than `ValueDebug` (~2.5 MB stripped) because there are ~48 `Issue` impls vs. one `ValueDebug` impl per every `#[turbo_tasks::value]` type. But real and cumulative. <!-- NEXT_JS_LLM_PR --> |
||
|
|
e934009e0e |
Turbopack: cut number of client references in half (#92607)
Currently, every client reference exists twice, but one of them is always dead code.
This shouldn't have an impact on the responses sent to the browser, but the server code is smaller. Doesn't have a measureable impact on cold build time.
For one page, this shrank page_client-reference-manifest.js from 979k to 742k
```js
"[project]/app/[slug]/counter-2.js [app-rsc] (client reference proxy) <module evaluation>", ((__turbopack_context__) => {
"use strict";
// UNUSED
__turbopack_context__.s(["default",()=>__TURBOPACK__default__export__]);
var reactDom = __turbopack_context__.i("react-server-dom-turbopack-server.js [app-rsc] (ecmascript)");
const __TURBOPACK__default__export__ = (0, reactDom["registerClientReference"])(function() {
throw new Error("Attempted to call the default export of [project]/app/[slug]/counter-2.js <module evaluation> from the server, but it's on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");
}, "[project]/app/[slug]/counter-2.js <module evaluation>", "default");
}),
"[project]/app/[slug]/counter-2.js [app-rsc] (client reference proxy)", ((__turbopack_context__) => {
"use strict";
__turbopack_context__.s(["default",()=>__TURBOPACK__default__export__]);
var reactDom = __turbopack_context__.i("react-server-dom-turbopack-server.js [app-rsc] (ecmascript)");
const __TURBOPACK__default__export__ = (0, reactDom["registerClientReference"])(function() {
throw new Error("Attempted to call the default export of [project]/app/[slug]/counter-2.js from the server, but it's on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");
}, "[project]/app/[slug]/counter-2.js", "default");
}),
"[project]/app/[slug]/counter-2.js [app-rsc] (ecmascript)", ((__turbopack_context__) => {
"use strict";
// UNUSED:
var client__reference__proxy$29$__$3c$module__evaluation$3e$__ = __turbopack_context__.i("[project]/app/[slug]/counter-2.js [app-rsc] (client reference proxy) <module evaluation>");
// Only this is exported
var client__reference__proxy$29$__ = __turbopack_context__.i("[project]/app/[slug]/counter-2.js [app-rsc] (client reference proxy)");
;
__turbopack_context__.n(client__reference__proxy$29$__);
}),
"[project]/app/[slug]/page.js [app-rsc] (ecmascript)", ((__turbopack_context__) => {
var counter = __turbopack_context__.i("[project]/app/[slug]/counter-1.js [app-rsc] (ecmascript)");
```
and also twice in the manifest
```js
'[project]/bench/client-refs/app/[slug]/counter-1.js <module evaluation>': {
id: '[project]/bench/client-refs/app/[slug]/counter-1.js [app-client] (ecmascript)',
name: '*',
chunks: [
'/_next/static/chunks/2li6a5uw43ak2.js',
'/_next/static/chunks/08svqy8591o8a.js',
],
async: false,
},
'[project]/bench/client-refs/app/[slug]/counter-1.js': {
id: '[project]/bench/client-refs/app/[slug]/counter-1.js [app-client] (ecmascript)',
name: '*',
chunks: [
'/_next/static/chunks/2li6a5uw43ak2.js',
'/_next/static/chunks/08svqy8591o8a.js',
],
async: false,
},
```
|
||
|
|
6344e18ac1 |
Turbopack: extract inline base64 decode helper to shared @turbopack/base64 module (#92549)
### What?
When Turbopack generates a module using `import foo from './blah' with {type:'bytes'}`, it needs a base64 decode helper. Previously this was inlined into every generated module. This PR extracts it to a shared `@turbopack/base64` module imported via a special import map, and encapsulates the import map registration inside the turbopack crate itself.
### Why?
1. **Deduplication** — The base64 decode helper is shared across all `{type:'bytes'}` imports instead of being duplicated inline in every generated module.
2. **Encapsulation** — The `@turbopack/*` and `@vercel/turbopack-ecmascript-runtime/*` import map aliases are now injected automatically by `ModuleAssetContext::resolve_options` in the turbopack crate. Consumers (next-core, turbopack-cli) no longer need to manually register these aliases in their import maps.
3. **Extensibility** — Future turbopack built-in modules can be added to a single `turbopack_runtime_import_map()` function, with no changes needed in any consumer.
### How?
**Base64 module (`turbopack-ecmascript-runtime`):**
- Added `turbopack/crates/turbopack-ecmascript-runtime/js/src/shared/base64.ts` with `base64Decode()` using ES2024 `Uint8Array.fromBase64` with an `atob()`-based fallback.
- The ES2024 feature check is evaluated once at module load time (top-level const), not per-call.
- The module is marked `"use turbopack no side effects"` for tree-shaking.
**BytesSourceTransform (`turbopack-ecmascript`):**
- Changed `BytesSourceTransform` to emit `import { base64Decode } from '@turbopack/base64'` instead of inlining the helper.
**Import map encapsulation (cycle-breaking refactor):**
- Moved `get_runtime_asset_context` from `turbopack-ecmascript-runtime` → `turbopack` crate, breaking the cyclic dependency so turbopack can now depend on `turbopack-ecmascript-runtime`.
- Changed `get_nodejs_runtime_code` and `get_browser_runtime_code` to accept an `AssetContext` parameter instead of constructing one internally.
- Added `turbopack_runtime_import_map()` to `turbopack-ecmascript-runtime` containing both the `@vercel/turbopack-ecmascript-runtime/*` wildcard and `@turbopack/base64` exact aliases.
- `ModuleAssetContext::resolve_options` now injects this import map automatically.
- Removed `insert_turbopack_dev_alias` from `next-core` and the equivalent block from `turbopack-cli`.
<!-- NEXT_JS_LLM_PR -->
|
||
|
|
7d09d1e605 |
Fix NextFontIssue IssueStage: CodeGen → Resolve (#92588)
### What? Changes `NextFontIssue`'s `IssueStage` from `CodeGen` to `Resolve`. ### Why? `NextFontIssue` is emitted during font resolution — when validating font configuration options (`util.rs`) and when fetching/resolving Google Font metadata (`google/mod.rs`, `google/font_fallback.rs`). None of these occur during code generation; they all happen at the resolve stage of the build pipeline. Using `IssueStage::CodeGen` was incorrect and caused these font issues to be grouped/sorted with code generation errors rather than resolution errors. The local-font counterpart (`FontResolvingIssue` in `next_font/local/mod.rs`) already correctly uses `IssueStage::Resolve`. ### How? Single-line fix: change `IssueStage::CodeGen.cell()` to `IssueStage::Resolve.cell()` in the `stage()` method of `impl Issue for NextFontIssue`. <!-- NEXT_JS_LLM_PR --> Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a24462d9e5 |
Replace wasmer with wasmtime for SWC plugin WASM engine (#91533)
## What? Replace wasmer with wasmtime as the WASM runtime for SWC plugins. ## Why? - Wasmer has been a source of build complexity due to conflicting feature flags (`wasmer/default` vs `wasmer/js-default`) and large binary size - Wasmtime is actively maintained by the Bytecode Alliance with better performance and smaller footprint - The upstream SWC project already provides `swc_plugin_backend_wasmtime` on crates.io, so we can use it directly instead of maintaining our own implementation ## How? - Use the upstream `swc_plugin_backend_wasmtime` v9.0.0 crate from crates.io instead of a custom local implementation - Replace `swc_core/plugin_transform_host_native` (which pulls in wasmer via `__plugin_transform_env_native`) with granular features: `__plugin_transform_host` + `__plugin_transform_host_schema_v1` - Provide `WasmtimeRuntime` explicitly in both the NAPI bindings and turbopack plugin paths - Remove all direct `wasmtime`/`wasmtime-wasi` workspace dependencies — the upstream crate manages its own wasmtime version - Replace wasmer-related release profile size optimizations with equivalent wasmtime ones (`swc_plugin_backend_wasmtime`, `swc_plugin_runner`, `wasmtime`, `wasmtime-internal-cranelift`) - Pin `vergen = "=9.0.6"` to avoid a `vergen-lib` version conflict with `vergen-gitcl` 1.0.8 ## Binary size impact (darwin-arm64, compared against current canary) | Metric | Canary | Wasmtime (this PR) | Delta | |---|---|---|---| | Unstripped dylib | 133.1 MB | 128.2 MB | **-5.0 MB (-3.7%)** | | Stripped | 91.2 MB | 86.9 MB | **-4.3 MB (-4.8%)** | | Stripped + gzip (npm) | 30.9 MB | 29.4 MB | **-1.5 MB (-5.0%)** | <!-- NEXT_JS_LLM_PR --> |
||
|
|
7e741b4207 |
Handle target option in React Compiler for Pages + React 18 (#91500)
### Why?
`test/e2e/app-dir/catch-error/catch-error-react-compiler.test.ts` is
skipped in CI due to incompatibility with React 18 tests.
```
Module not found: Can't resolve 'react/compiler-runtime'
> 1 | import { c as _c } from "react/compiler-runtime";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
Failed CI:
https://github.com/vercel/next.js/actions/runs/23170738194/job/67327462789#step:36:317
x-ref:
https://react.dev/reference/react-compiler/target#targeting-react-17-or-18
---------
Co-authored-by: Sebastian "Sebbie" Silbermann <12292047+eps1lon@users.noreply.github.com>
Co-authored-by: Benjamin Woodruff <180404+bgw@users.noreply.github.com>
Co-authored-by: Sebastian Silbermann <eps1lon@users.noreply.github.com>
|
||
|
|
87e8b29700 |
Turbopack: Improve unable to resolve next error (#91394)
## What Improves the error message shown when Turbopack cannot resolve the `next` package. The issue now includes the filesystem root used for resolution, making it easier to diagnose misconfigured `turbopack.root` settings. ## Why When `next` can't be resolved, the previous error message showed the context directory but not the root used for resolution. This made it difficult to determine whether the resolution root was incorrect, especially in monorepo setups. ## Changes - Added `root` field to `MissingNextFolderIssue` to track the filesystem root used for resolution - Display the filesystem root path in the error message - Updated the fix suggestion to reference "project directory" instead of "this directory" for clarity |
||
|
|
7ac71949ab | Turbopack: switch from immutable token to path prefix (#92164) | ||
|
|
108fe00fbd |
fix: replace docs/canary links with stable docs paths in error messages (#92528)
### What? Error messages for the `"use cache"` directive and `after()` API (in `cookies()`, `headers()`, `connection()`) pointed to `/docs/canary/...` instead of `/docs/...`. ### Why? On canary builds, `docs/canary` links can create circular or confusing references. These APIs are stable and their docs live at the standard `/docs/app/...` path. ### How? Replaced all `nextjs.org/docs/canary/` URLs with `nextjs.org/docs/` in: - `crates/next-custom-transforms/src/transforms/server_actions.rs` — the `"use cache"` feature flag error - `crates/next-custom-transforms/tests/errors/use-cache-not-allowed/1/output.stderr` and `2/output.stderr` — matching test fixtures - `packages/next/src/server/request/cookies.ts`, `headers.ts`, `connection.ts` — `after()` errors - `packages/next/errors.json` — compiled error message index (6 occurrences) <!-- NEXT_JS_LLM_PR --> |
||
|
|
c5596dd4e0 |
Turbopack: Write trace file to .next-profiles (#92189)
## What? Builds on #92078. Also writes the Turbopack trace to the same .next-profiles directory. |
||
|
|
1297f606be |
perf: use whole_app_module_graph for get_compilation_issues MCP tool (#92473)
### What? Optimizes the `get_compilation_issues` MCP tool to use a single whole-app module graph computation instead of iterating all endpoint groups and computing per-route module graphs individually. ### Why? The `get_compilation_issues` MCP tool was too slow for large apps because `get_all_compilation_issues_inner_operation` called `endpoint_group.module_graphs()` for every endpoint group, computing separate per-route module graphs. This is O(routes) graph computations when a single whole-app graph would suffice. The reason the original code didn't use `whole_app_module_graphs()` was that it calls `drop_issues()` in development mode (to prevent every route from re-reporting all issues during HMR). This would suppress the issues the MCP tool needs to collect. ### How? - **New method `Project::whole_app_module_graphs_without_dropping_issues()`** — calls the same underlying `whole_app_module_graph_operation` (the `#[turbo_tasks::function(operation, root)]` computation) but connects the result without dropping issues. Issues propagate normally so callers can collect them. - **`get_all_compilation_issues_inner_operation`** now calls this new method instead of iterating endpoint groups, reducing the work to a single graph computation. - **Extracted `scale_down_node_pool` helper** — the Node.js process pool scale-down logic (shared between both `whole_app_module_graphs` variants) is extracted into a free function to avoid duplication. - **Existing `whole_app_module_graphs()`** is unchanged in behavior — still drops issues in dev mode for HMR per-route subscriptions. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5636400139 | Adding more system info to the 'initialize project' trace (#92427) | ||
|
|
df6cd599b3 |
Turbopack: Remove turbo_tasks::apply_effects, use Effects::apply instead (#91858)
Having both `apply_effects` and `Effects::apply` is confusing. `apply_effect`'s API seems a bit bad/dangerous because it combines work that should be done inside of a strongly-consistent operation (collecting the effects and reading their `Vc`s) with work that should be done at the top-level outside of any operation or turbo-task function (applying the effects). The first commit in this PR removes the `apply_effect` API and tries to improve documentation, including adding a doctest that compiles. The second commit in this PR is an LLM-driven refactor to remove the `apply_effect` callsites. |
||
|
|
81d5e070f4 |
next-core: deduplicate output assets and detect content conflicts on emit (#92292)
### What? Adds deduplication and conflict detection to the asset emission stage in `crates/next-core/src/emit.rs`, and a new `IssueStage::Emit` variant in `turbopack-core`. Before emitting, assets are grouped by their output path. If multiple assets map to the same path: - If their content is identical, one is silently chosen (deduplication). - If their content differs, both versions are written to `<node_root>/<content_hash>.<ext>` and an `EmitConflictIssue` is raised for each conflict. All assets are still emitted — conflicts do not abort the build. ### Why? Previously, duplicate output assets for the same path were emitted unconditionally — whichever write happened last silently won. This masked build graph bugs where two different modules produced conflicting output files. Reporting conflicts as issues (rather than silently overwriting) makes them visible and easy to diagnose without breaking the build. ### How? - Collect all assets with their resolved paths via `try_flat_join`. - Bucket them into two `FxIndexMap<FileSystemPath, Vec<ResolvedVc<Box<dyn OutputAsset>>>>` — one for node-root assets and one for client assets. - For each bucket entry, call `check_duplicates`: compare every asset against the first using `assets_diff`. If content differs, emit an `EmitConflictIssue` as a turbo-tasks collectible — but still return the first asset so emission continues. - `assets_diff` is a `#[turbo_tasks::function]` that takes only `(asset1, asset2, extension, node_root)` — the `asset_path` stays out of the task key to avoid unnecessary task cardinality. When file content differs, it hashes each version with xxh3, writes them to `<node_root>/<hash>.<ext>`, and returns the paths in the detail message so the user can diff them. - `EmitConflictIssue` implements the `Issue` trait with `IssueStage::Emit` (new variant added to `turbopack-core`), `IssueSeverity::Error`, a descriptive title, and a detail message explaining the type of conflict. - Node-root and client assets are emitted in parallel via `futures::join!` (not `try_join!`) to ensure deterministic error reporting — both branches always run to completion so errors are reported in a consistent order. --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f65b10a54d |
turbo-tasks: replace async resolve fns with custom Future types (ResolveRawVcFuture, ResolveVcFuture, ToResolvedVcFuture) (#91554)
### What? Replace the `async fn resolve()`, `async fn resolve_strongly_consistent()`, and `async fn to_resolved()` methods on `RawVc`, `Vc<T>`, and `OperationVc<T>` with hand-written custom `Future` implementations, following the existing `ReadRawVcFuture` pattern. New types: - **`ResolveRawVcFuture`** (`raw_vc.rs`) — core implementation, replaces `async fn resolve_inner()` - **`ResolveVcFuture<T>`** (`vc/mod.rs`) — typed wrapper over `ResolveRawVcFuture`, returned by `Vc::resolve()` - **`ResolveOperationVcFuture<T>`** (`vc/operation.rs`) — typed wrapper, returned by `OperationVc::resolve()` - **`ToResolvedVcFuture<T>`** (`vc/mod.rs`) — typed wrapper, returned by `Vc::to_resolved()` All new future types expose a `.strongly_consistent()` builder method, enabling `resolve_strongly_consistent()` to be replaced by `.resolve().strongly_consistent()` at call sites. `ReadRawVcFuture` is also updated to delegate its phase-1 resolve loop to `ResolveRawVcFuture` instead of duplicating the logic. `std::task::ready!` is used throughout to simplify poll implementations. Also adds `#[inline(never)]` to `ReadRawVcFuture::poll` and `ResolveRawVcFuture::poll` to avoid inlining large poll implementations into every await site. ### Why? Performance, binary size, and improved API ergonomics: - The hand-written `Future` pattern (already used by `ReadRawVcFuture`) gives the compiler more predictable, smaller code than the state machines generated for `async fn`. The `#[inline(never)]` attributes on `poll` prevent large poll bodies from being duplicated at every await site, which the async desugaring otherwise allows. - The new builder API (`.resolve().strongly_consistent()`) is more composable and removes the need for separate `_strongly_consistent` method variants, reducing the number of methods on `RawVc`/`Vc`/`OperationVc`. - Having `ReadRawVcFuture` delegate to `ResolveRawVcFuture` removes the duplicated resolve loop and ensures both paths stay in sync. ### How? - `ResolveRawVcFuture` stores `current: RawVc`, `read_output_options: ReadOutputOptions`, `strongly_consistent: bool`, and `listener: Option<EventListener>`. Its `poll` replicates the loop from the old `resolve_inner` using `try_read_task_output` / `try_read_local_output`. - On `Err(listener)` from a `try_*` call, the listener is stored in `self.listener` and `Poll::Pending` is returned. At the top of the loop, `ready!(poll_listener(...))` re-polls it and short-circuits if still pending. - Consistency is downgraded to `Eventual` after the first `TaskOutput` hop, matching the previous behavior. - `strongly_consistent: true` keeps the `SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK` suppression across all polls (same logic as `ReadRawVcFuture`). - `ReadRawVcFuture` now holds a `ResolveRawVcFuture` for phase 1 and drives it via `Pin::new(&mut self.resolve).poll(cx)` before proceeding to the cell read in phase 2. This eliminates the duplicated loop that previously existed in both types. - Typed wrappers (`ResolveVcFuture<T>`, `ResolveOperationVcFuture<T>`, `ToResolvedVcFuture<T>`) delegate `poll` to the inner `ResolveRawVcFuture` and map the output to the appropriate typed result. - `OperationVc::resolve_strongly_consistent()` is removed; 16 call sites updated to `.resolve().strongly_consistent()`. - All new types implement `Unpin` and are exported from `lib.rs`. - `std::task::ready!` is used in all `poll` implementations to reduce boilerplate. No behavioral changes — this is a pure implementation refactor. ### Binary size impact A release build (`pnpm swc-build-native --release`) was measured before and after the branch changes on the same merge-base commit (`a41bef94`): | | Size | |---|---| | Base (`a41bef94`, before branch) | 199,690,656 bytes (~190.4 MB) | | Branch (`6f7846f9`, after changes) | 199,252,384 bytes (~190.0 MB) | | **Difference** | **−438,272 bytes (−428 KB, −0.22%)** | The branch produces a slightly smaller binary. The reduction comes primarily from the `#[inline(never)]` attributes preventing large `poll` bodies from being duplicated at every await site. --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f6663b75fd |
Turbopack: Dedupe our two filesystempath extension/extension_ref functions (#92048)
PR is AI-generated - `extension` and `extension_ref` do the same thing. - `extension_ref`'s API that returns an `Option` was better than `extension`'s API that returns a potentially-empty string. - In some places we were splitting into a stem and extension, and joining them back to a filename, but those places could just get the original path's filename. --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Benjamin Woodruff <bgw@users.noreply.github.com> |
||
|
|
2bf38b0115 |
feat: add experimental.swcEnvOptions for SWC preset-env configuration (#92272)
### What? Add `experimental.swcEnvOptions` to expose SWC's preset-env `env` configuration options — including `mode`, `coreJs`, `include`, `exclude`, `skip`, `shippedProposals`, `forceAllTransforms`, `debug`, and `loose`. ### Why? Currently Next.js only passes `env.targets` (derived from browserslist) to SWC for **syntax downleveling**, but does not expose the polyfill injection capabilities that SWC already supports. This means: - Users who need automatic core-js polyfills (e.g. `Array.prototype.at()`, `Promise.withResolvers()`, `Set` methods) have no built-in way to get them. - The only workarounds are importing `core-js` globally (which bloats bundles significantly) or ejecting to Babel with `useBuiltIns: 'usage'` (which sacrifices SWC's performance benefits). - In the Babel era, Next.js supported this via `@babel/preset-env`'s `useBuiltIns` (PR #10574). That capability was lost when Next.js migrated to SWC. ### How? A new `experimental.swcEnvOptions` config is added. Its properties are spread into the `env` block that Next.js passes to SWC for client-side compilation, alongside the existing browserslist-derived `targets`. Server-side compilation is unaffected (always targets `node`). The option surface mirrors [SWC's preset-env docs](https://swc.rs/docs/configuration/supported-browsers) 1:1, keeping it familiar and forward-compatible. ```js // next.config.js module.exports = { experimental: { swcEnvOptions: { mode: 'usage', coreJs: '3.38', }, }, } ``` #### Changes: config-shared.ts — type definition with JSDoc config-schema.ts — zod validation next-swc-loader.ts → swc/options.ts — plumb config into the SWC env block Unit tests (6 cases) + e2e test (dev & production) Related issues #66562, #63104, #74978 Related discussion #46724 --------- Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com> |
||
|
|
3c301acc32 |
Turbopack: Remove FileSystemPath::value_to_string in favor of ValueToStringRef::to_string_ref (#92284)
`FileSystemPath::value_to_string` was just calling `ValueToString::to_string`. But: - It's better to call `ValueToStringRef::to_string_ref` - We don't really get much value from this inherent method except for avoiding a few imports? I suspect this is left over from when we used to always wrap `FileSystemPath` in `Vc`. |
||
|
|
d6906eabd6 | Update Rust toolchain to nightly-2026-04-02 (#92288) | ||
|
|
41839289de |
Add MCP get_compilation_issues tool and NAPI method (#92062)
### What?
Adds a new \`get_compilation_issues\` MCP tool (and the underlying
\`projectGetAllCompilationIssues\` NAPI method) that returns all
compilation issues from all routes in a single call.
**New files:**
- \`crates/next-napi-bindings/src/next_api/project.rs\` —
\`project_get_all_compilation_issues\` NAPI function +
\`get_all_compilation_issues_operation\` /
\`get_all_compilation_issues_inner_operation\` turbo-tasks operations
- \`packages/next/src/server/mcp/tools/get-compilation-issues.ts\` — MCP
tool implementation
-
\`packages/next/src/server/mcp/tools/utils/format-compilation-issues.ts\`
— output formatter
-
\`test/development/mcp-server/mcp-server-get-compilation-issues.test.ts\`
— e2e test
- \`test/development/mcp-server/fixtures/compilation-errors-app/\` —
fixture app with three routes (valid page, missing-module error, syntax
error)
**Modified files:**
- \`packages/next/src/build/swc/generated-native.d.ts\` + \`types.ts\` +
\`index.ts\` — expose the new NAPI method
- \`packages/next/src/server/mcp/get-or-create-mcp-server.ts\` —
register the new tool, accept \`getTurbopackProject\` option
- \`packages/next/src/server/dev/hot-reloader-turbopack.ts\` — pass
\`project\` to MCP middleware
- \`packages/next/src/telemetry/events/build.ts\` — add
\`'mcp/get_compilation_issues'\` to the \`McpToolName\` union
### Why?
The existing MCP tools require a browser session (and thus a specific
route to be rendered) to surface compilation errors.
\`get_compilation_issues\` works without a browser session and covers
all routes proactively — useful for AI coding agents that want to check
for errors across the whole app before trying to render a page.
### How?
**NAPI layer:**
\`project_get_all_compilation_issues\` calls a two-level turbo-tasks
operation pair:
1. \`get_all_compilation_issues_inner_operation\` — iterates all
endpoint groups via \`project.get_all_endpoint_groups(false)\` and calls
\`endpoint_group.module_graphs().as_side_effect()\` on each. This builds
the module graph (resolution + transformation) for every entrypoint
without chunking, emitting, or code generation. Issues are emitted as
turbo-tasks collectables.
2. \`get_all_compilation_issues_operation\` — wraps the inner op in
\`strongly_consistent_catch_collectables\` to harvest
issues/diagnostics/effects, then returns an \`OperationResult\`.
> **Why not \`project.whole_app_module_graphs()\`?**
> \`whole_app_module_graphs()\` calls \`drop_issues()\` in development
mode to prevent every per-route HMR subscription from seeing all global
issues. Calling it here would return zero issues in dev. Per-endpoint
\`module_graphs()\` calls don't have this suppression.
**Output formatting (\`format-compilation-issues.ts\`):**
The raw Turbopack wire types are transformed before being returned to
MCP consumers:
- **StyledString → plain string**: \`title\`, \`description\`, and
\`detail\` are \`StyledString\` union trees (recursive \`{type, value}\`
objects). These are flattened to plain strings —
\`text\`/\`code\`/\`strong\` variants return their \`.value\` directly,
\`line\` joins with \`""\`, \`stack\` joins with \`"\n"\`.
- **ANSI codes stripped**: \`codeFrame\` is a pre-rendered string from
the Rust NAPI layer that contains ANSI terminal colour codes. These are
stripped via \`next/dist/compiled/strip-ansi\`.
- **1-indexed source positions**: Turbopack's \`SourcePos\` is 0-indexed
for both \`line\` and \`column\`. The formatter adds \`+1\` to each so
consumers get the conventional editor-style 1-indexed values.
- **Deduplication**: the same issue can surface from multiple endpoints
during the module graph traversal. Issues are deduplicated by a
\`severity|filePath|title|startLine:startCol\` key.
- **No diagnostics**: Turbopack diagnostics are internal telemetry
(\`EVENT_BUILD_FEATURE_USAGE\` feature-adoption counters). They are not
actionable for the user, so the \`diagnostics\` field is omitted
entirely from the response.
**MCP tool (\`get_compilation_issues\`):**
Calls \`project.getAllCompilationIssues()\`, passes the issues through
\`formatCompilationIssues()\`, and returns \`{ issues: FormattedIssue[]
}\` as JSON. Works without a browser session.
**Test:**
A fixture app with three routes (valid \`app/page.tsx\`,
\`app/missing-module/page.tsx\` importing a non-existent package,
\`app/syntax-error/page.tsx\` with unclosed JSX) is used to verify:
1. The tool returns a result without any browser session
2. Module-not-found errors are detected
3. Syntax errors are detected
4. Issues include \`severity\`, \`title\`, \`filePath\` metadata — all
as plain strings (not StyledString objects)
e2e tests: added —
\`test/development/mcp-server/mcp-server-get-compilation-issues.test.ts\`
---------
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
3e0158846e |
feat: add NEXT_HASH_SALT env var for content-hash filename salting (#91871)
### What? Adds a `NEXT_HASH_SALT` environment variable **and** a `experimental.outputHashSalt` config option that mix a user-supplied string into every content-addressed hash used to generate chunk filenames and static asset filenames. This works for both Webpack and Turbopack. When both are set, the values are concatenated (`outputHashSalt + NEXT_HASH_SALT`), so a per-project salt can be baked into `next.config.js` while a per-deployment salt is injected at build time via the environment variable. ### Why? Content-addressed filenames (e.g. `chunk.abc123.js`) are derived from file content, so they only change when the content changes. There are deployment scenarios where you need to force all filenames to rotate — for example after a CDN misconfiguration has poisoned caches for a particular hash space — without actually changing source code. A stable, opt-in salt lets operators do this without touching application code. Some customers prefer the config-file approach (`turbopack.outputHashSalt`) over environment variables, so both are supported. ### How? **Webpack** already has `output.hashSalt` in its config. We simply forward `NEXT_HASH_SALT` to that option. **Turbopack** required threading the value through several layers: 1. The effective hash salt is computed once in `assignDefaultsAndValidate` as `config.turbopackHashSalt = (turbopack.outputHashSalt ?? '') + (NEXT_HASH_SALT ?? '')` and stored on `NextConfigComplete`. Both `turbopackBuild` (production) and `createHotReloaderTurbopack` (dev) read from this single field. 2. `ProjectOptions.hash_salt` receives the pre-computed salt. 3. `Project` stores the salt and passes it into the three chunking context option structs (`ClientChunkingContextOptions`, `ServerChunkingContextOptions`, `EdgeChunkingContextOptions`). 4. Both `BrowserChunkingContext` and `NodeJsChunkingContext` gain a `hash_salt: RcStr` field. 5. A new `deterministic_hash_with_salt(salt, input, algorithm)` function in `turbo-tasks-hash` writes the salt bytes first, then the content bytes, into a single hasher — one pass, no hash-of-hash composition. 6. A matching `content_hash_with_salt` method is added to `FileContent` and `AssetContent`. 7. `ChunkingContext::asset_path` is changed to accept `Vc<AssetContent>` (instead of a pre-computed `Vc<RcStr>`) so the chunking context can choose the correct hash path itself. `StaticOutputAsset::path` simplifies accordingly. Without `NEXT_HASH_SALT` and without `turbopack.outputHashSalt` set, behaviour is identical to before — no hash change, no performance impact. **e2e test** (`test/production/app-dir/hash-salt/`) verifies: - Two builds with the same salt produce identical chunk and static asset filenames. - A build with a different salt produces different filenames. - `turbopack.outputHashSalt` (config) changes filenames vs no salt. - Combined config + env salt differs from either alone. - Runs for both Turbopack and Webpack. --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Luke Sandberg <lukesandberg@users.noreply.github.com> |
||
|
|
feb594bd3c | Turbopack: use same hash for immutable-static-hashes.json (#92157) | ||
|
|
2aabb6de35 |
Revert "turbo-tasks: add hashed cell mode for hash-based change detection without cell data" (#92103)
Reverts vercel/next.js#91576 That PR leads to failures in `test/e2e/filesystem-cache/filesystem-cache.test.ts`, e.g.: ``` FATAL: An unexpected Turbopack error occurred: Failed to write app endpoint /page Caused by: - failed to analyze ecmascript module '[project]/node_modules/next/dist/esm/build/templates/app-page.js?page=/remove-me/page { MODULE_0 => "[project]/app/layout.tsx [app-rsc] (ecmascript, Next.js Server Component)", MODULE_1 => "[project]/node_modules/next/dist/client/components/builtin/not-found.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_2 => "[project]/node_modules/next/dist/client/components/builtin/forbidden.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_3 => "[project]/node_modules/next/dist/client/components/builtin/unauthorized.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_4 => "[project]/node_modules/next/dist/client/components/builtin/global-error.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_5 => "[project]/app/remove-me/page.tsx [app-rsc] (ecmascript, Next.js Server Component)" } [app-rsc] (ecmascript)' - failed to parse [project]/node_modules/next/dist/esm/build/templates/app-page.js?page=/remove-me/page - Cell CellId { type_id: ValueTypeId { id: 234, name: ValueType { name: "turbo_tasks_fs::FileContent" } }, index: 1 } no longer exists in task TaskId { id: 793 } get_app_page_entry (no cell of this type exists) Debug info: - Execution of get_all_written_entrypoints_with_issues_operation failed - Execution of EntrypointsOperation::new failed - Execution of all_entrypoints_write_to_disk_operation failed - Execution of output_assets_operation failed - Execution of <AppEndpoint as Endpoint>::output failed - Failed to write app endpoint /page - Execution of AppEndpoint::output failed - Execution of whole_app_module_graph_operation failed - Execution of Project::get_all_entries failed - Execution of <AppEndpoint as Endpoint>::entries failed - Execution of get_app_page_entry failed - Execution of *ProcessResult::module failed - Execution of <ModuleAssetContext as AssetContext>::process failed - Execution of *EcmascriptExports::split_locals_and_reexports failed - Execution of <EcmascriptModuleAsset as EcmascriptChunkPlaceable>::get_exports failed - Execution of analyze_ecmascript_module failed - failed to analyze ecmascript module '[project]/node_modules/next/dist/esm/build/templates/app-page.js?page=/remove-me/page { MODULE_0 => "[project]/app/layout.tsx [app-rsc] (ecmascript, Next.js Server Component)", MODULE_1 => "[project]/node_modules/next/dist/client/components/builtin/not-found.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_2 => "[project]/node_modules/next/dist/client/components/builtin/forbidden.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_3 => "[project]/node_modules/next/dist/client/components/builtin/unauthorized.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_4 => "[project]/node_modules/next/dist/client/components/builtin/global-error.js [app-rsc] (ecmascript, Next.js Server Component)", MODULE_5 => "[project]/app/remove-me/page.tsx [app-rsc] (ecmascript, Next.js Server Component)" } [app-rsc] (ecmascript)' - Execution of parse failed - failed to parse [project]/node_modules/next/dist/esm/build/templates/app-page.js?page=/remove-me/page - Cell CellId { type_id: ValueTypeId { id: 234, name: ValueType { name: "turbo_tasks_fs::FileContent" } }, index: 1 } no longer exists in task TaskId { id: 793 } get_app_page_entry (no cell of this type exists) ``` [x-ref](https://github.com/vercel/next.js/actions/runs/23707462785/job/69156136747?pr=91830) |
||
|
|
d84e3bb56e |
Use RcStr directly in napi(object) structs instead of converting to String (#92014)
### What? Replace `String` fields in `#[napi(object)]` structs within `crates/next-napi-bindings/` with `RcStr`, and remove the corresponding unnecessary `.to_string()` / `.into_owned()` conversions at the NAPI boundary. Affected structs and fields: | File | Struct | Fields | |---|---|---| | `utils.rs` | `NapiIssue` | `file_path`, `documentation_link` | | `utils.rs` | `NapiAdditionalIssueSource` | `description` | | `utils.rs` | `NapiSource` | `ident`, `file_path` | | `utils.rs` | `NapiDiagnostic` | `category`, `name`, `payload` | | `project.rs` | `NapiRoute` | `pathname` | | `endpoint.rs` | `NapiAssetPath` | `path`, `content_hash` | ### Why? `RcStr` already implements `ToNapiValue` and `FromNapiValue` (behind the `napi` feature flag in `turbo-rcstr`, already enabled in `next-napi-bindings`). Despite this, many `#[napi(object)]` structs were still declared with `String` fields and converted from `RcStr` source data via `.to_string()` or `.into_owned()`, allocating a new heap `String` on every call just to hand it straight to the NAPI serializer. Since `RcStr` is reference-counted, using it directly avoids the allocation entirely — the refcount is simply incremented on clone and the underlying bytes are passed to `napi_create_string_utf8` directly. ### How? - Changed `String` → `RcStr` in the struct field declarations. - Updated the corresponding `From` / constructor impls to use `.clone()` instead of `.to_string()` / `.into_owned()` on `RcStr` values, and direct field moves where ownership is transferred. - Added `use turbo_rcstr::RcStr;` imports where not already present (`utils.rs`, `endpoint.rs`). `project.rs` already imported `RcStr`. - The JavaScript API surface is unchanged: both `String` and `RcStr` serialize to a JS `string` via the same underlying `napi_create_string_utf8` call. Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0090db224d |
turbo-tasks: add hashed cell mode for hash-based change detection without cell data (#91576)
## Summary
### Core: hash-based cell change detection
- Adds `cell_data_hash: AutoMap<CellId, [u8; 16]>` to the storage schema
(`category = "data"`) so the backend can persist a hash of transient
cell data across evictions
- Stored as `[u8; 16]` (little-endian bytes of a u128) rather than
`u128` to keep the 1-byte alignment out of `AutoMap` and therefore out
of the `LazyField` enum — a bare `u128` would grow the enum from 56 to
64 bytes due to its 16-byte alignment requirement
- Adds `content_hash: Option<u128>` to `UpdateCellOperation::run` and
threads it through the full call chain: `CurrentCellRef` →
`TurboTasksCallApi::update_own_task_cell` → `Backend::update_task_cell`
→ operation
- **New invalidation logic** in `UpdateCellOperation::run` for the
`assume_unchanged = false` path:
- Old content available → real equality compare (unchanged)
- Old content evicted, hashes match → write new content but **skip
dependent invalidation**
- Old content evicted, hashes differ (or missing) → write + invalidate
as before
- `cell_data_hash` is updated whenever content is written (skipped if
hash is unchanged; always updated for non-serializable cells regardless
of `assume_unchanged`)
- Adds `hashed_compare_and_update` /
`hashed_compare_and_update_with_shared_reference` methods to
`CurrentCellRef` (require `T: PartialEq + DeterministicHash`); hash is
computed lazily (only after equality check fails when old value is
available)
- Adds `VcCellHashedCompareMode<T>` in `cell_mode.rs` which implements
`VcCellMode<T>` for `T: VcValueType + PartialEq + DeterministicHash`
### Macro: `serialization = "hash"` and `hash = "manual"`
- **`serialization = "hash"`** — behaves like `serialization = "none"`
(no disk serialization, transient) but uses `VcCellHashedCompareMode` so
the stored hash prevents spurious downstream invalidation when transient
data is evicted and re-executed
- Valid only with `cell = "compare"` (the default); combining with `cell
= "new"` or `cell = "keyed"` is a compile error
- Automatically derives `DeterministicHash` on the annotated type
- **`hash = "manual"`** — opt-out of the auto-derive when a custom
`DeterministicHash` impl is needed (analogous to `eq = "manual"`); using
`hash = "manual"` without `serialization = "hash"` is a compile error
### turbo-tasks-fs: `PersistedFileContent`
- Adds `PersistedFileContent` — a mirror of `FileContent` that is
returned by `Vc<FileContent>::persist()`, storing the same data but
obtained through the persistent cache path
- `FileContent` is switched to `serialization = "hash"` so the macro
auto-derives `DeterministicHash`
- `DiskFileSystem::write()` now calls `.persist().await?` before
emitting the write effect, using `PersistedFileContent` for the file
comparison and write — this ensures full content is in the persistent
cache on restore, avoiding spurious downstream invalidation
- `WriteContent::File` in `invalidator_map.rs` is updated to hold
`ReadRef<PersistedFileContent>`
### turbopack-core: `Code` with hash-based serialization
- Adds `DeterministicHash` impls for `SmallVec<[T; N]>` and `()` in
`turbo-tasks-hash`
- Switches `Code` to `serialization = "hash"` so module factory code
cells use hash-based change detection
- Calls `.persist()` on module factory code cells in
`turbopack-ecmascript`
### next-core: duplicate asset detection and parallel emit
- `emit_assets` now emits node and client assets concurrently via
`try_join!`
- Detects duplicate assets (same output path, different content) and
returns a hard error with a diff summary — previously the last writer
silently won
## Macro syntax
```rust
#[turbo_tasks::value(serialization = "hash")]
struct MyTransientType { ... }
// opt out of auto-derived DeterministicHash when you need a custom impl:
#[turbo_tasks::value(serialization = "hash", hash = "manual")]
struct MyTransientType { ... }
```
## Test plan
- [x] Integration tests in
`turbopack/crates/turbo-tasks-backend/tests/hashed_cell_mode.rs`:
- `test_hashed_cell_mode_change_triggers_invalidation` — value change
triggers consumer re-execution
- `test_hashed_cell_mode_equal_value_no_invalidation` — same hash
prevents consumer re-execution
- [x] `cargo test -p turbo-tasks-macros-tests` passes
- [x] `cargo test -p turbo-tasks-backend --test hashed_cell_mode` passes
- [x] `cargo check --workspace` passes
---------
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
|
||
|
|
548fbca6df |
turbopack: Remove Vc::resolve(), migrate all callsites to Vc::to_resolved() (#91725)
### What? Remove the `Vc::resolve()` method and migrate all ~37 callsites across the turbopack and next crates to use `Vc::to_resolved()` instead. ### Why? `Vc::resolve()` returned `Result<Vc<T>>` — a resolved `Vc` with no static guarantee that it is resolved. This is a footgun: the type is indistinguishable from any other unresolved `Vc<T>`, so it's easy to accidentally lose the "resolved" property at a type boundary or store it in a struct without the compiler enforcing invariants. `Vc::to_resolved()` returns `Result<ResolvedVc<T>>`, which statically encodes the resolved guarantee in the type. `ResolvedVc<T>` implements `Deref<Target = Vc<T>>`, so it coerces seamlessly to `Vc<T>` at call sites that still need the plain type. Removing `Vc::resolve()` eliminates this footgun entirely and pushes callers toward the type-safe API. ### How? - Removed the `Vc::resolve()` method from `turbo-tasks/src/vc/mod.rs`. - Updated `Vc::to_resolved()` to construct the inner `Vc` directly via `self.node.resolve()` rather than calling `self.resolve()`. - Updated `Vc::debug_identifier` to go through `to_resolved()`. - Updated `TaskInput::resolve_input` for `Vc<T>` to use `to_resolved()`. - Updated all callsites across turbopack-core, turbopack-ecmascript, turbopack-resolve, turbopack-browser, turbopack-css, turbopack-node, turbopack-ecmascript-runtime, next-api, next-core, and test/fuzz files: - Where `ResolvedVc<T>` is usable (deref coercion handles method calls, or the value is stored in a `HashMap` etc.), simply replaced `.resolve()` with `.to_resolved()`. - Where a plain `Vc<T>` is strictly required by a callee signature, dereferenced with `*x.to_resolved().await?`. - Updated the `AfterResolvePluginWithCondition` type alias in `resolve/mod.rs` to use `ResolvedVc<AfterResolvePluginCondition>` now that the tuple member is produced by `to_resolved()`. - Updated doc comments referencing `Vc::resolve` to point to `Vc::to_resolved`. Verified with `cargo check --all-targets` — compiles cleanly with no errors. --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
43f9017719 |
Add next internal post-build CLI command for Turbopack database compaction (#91336)
### What? Adds a \`next internal post-build\` CLI command that performs Turbopack persistent cache database compaction as a separate step after \`next build\`. ### Why? Database compaction during shutdown can be slow and blocks the build process. By deferring compaction to a separate post-build step, we can: - Keep the main \`next build\` fast by skipping compaction at shutdown (controlled via \`NEXT_USE_POST_BUILD=1\`) - Run compaction independently, e.g. as a separate CI step or in the background - Perform a full compaction (\`max_merge_segment_count: usize::MAX\`) that is more thorough than the runtime shutdown compaction **Intended usage in a build pipeline:** \`\`\`sh NEXT_USE_POST_BUILD=1 next build # fast build, skips compaction at shutdown next internal post-build # separate step: full compaction of the cache \`\`\` ### How? **Rust side:** - Added \`compact_database()\` to \`turbo-tasks-backend\` that opens the database and runs a full compaction using \`TurboTasksParallelScheduler\` backed by a dedicated multi-threaded Tokio runtime (same scheduler as the normal runtime path, enabling parallel work-stealing) - Added \`turbopack_database_compact\` NAPI binding that resolves the versioned DB path and calls \`compact_database()\` - Added \`skip_compaction\` flag to \`NapiTurboEngineOptions\` / \`TurboKeyValueDatabase\` to skip compaction during normal shutdown when post-build will handle it; asserted that \`skip_compaction=true\` requires \`is_short_session=true\` (enforcing the logical dependency) - Extracted shared \`DB_CONFIG\`, \`FAMILIES\`, and \`COMPACT_CONFIG\` constants and \`git_version_info()\` helper to avoid duplication between the runtime DB and standalone compaction paths **TypeScript side:** - Added \`next internal post-build\` command that loads the Next.js config, finds the Turbopack cache directory, and calls the native compaction binding - Added \`databaseCompact\` to the turbo bindings interface (native + WASM stub) - \`turbopack-build/impl.ts\` sets \`skipCompaction: true\` when \`NEXT_USE_POST_BUILD=1\` **Tests:** - Added \`test/production/app-dir/post-build/\` integration test that verifies compaction runs successfully on a persistent-cache build and reports nothing to do when persistent caching is disabled --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7d451fe01e |
Add module count field to module graph tracing spans (#91697)
### What?
Adds a `modules` field to two tracing spans that cover module graph
construction:
- `"module graph for endpoint"` span (per-page graph, in `app.rs`) —
records the total number of modules across all `SingleModuleGraph`s
built for that endpoint.
- `"whole app module graph"` span (new info-level span wrapping
`whole_app_module_graph_operation` in `project.rs`) — records the
combined module count of the base graph and the additional-entries
graph.
Also adds a `module_count()` method to `SingleModuleGraph` (as a
`#[turbo_tasks::function]` returning `Vc<u64>`) that exposes
`number_of_modules`.
### Why?
Module graph size directly affects build and HMR performance. Without a
count in the trace, it is impossible to correlate span duration with
graph size or spot regressions where the graph grows unexpectedly.
Surfacing the count as a structured span field makes it queryable
without requiring log parsing.
### How?
- `SingleModuleGraph::module_count()` is a cheap
`#[turbo_tasks::function]` that wraps the already-computed
`number_of_modules: usize` field as a `Vc<u64>`.
- Both call sites follow the deferred-field pattern already used in
`ProjectContainer::initialize`: the span is created with `modules =
Empty` before the `async move` block, `span.record("modules", count)` is
called once the graphs are ready, and `span_clone` is passed to
`.instrument()` so the field is recorded on the correct span.
- Module counts are summed across all graphs that contribute to the
endpoint/app.
---------
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
|
||
|
|
118bdaf68c |
Turbopack: lazy require metadata and handle TLA (#91705)
A regression from #88487 1. Make metadata import lazy with `() => require()`, just like for the layout segments 2. Properly await the return value to better handle TLA modules This align with Webpack which does this: <img width="1535" height="509" alt="Bildschirmfoto 2026-03-20 um 11 58 00" src="https://github.com/user-attachments/assets/f2864c86-ccce-4884-8417-c8ae06c05f78" /> Closes PACK-6927 Closes #91700 Closes https://github.com/vercel/next.js/issues/91676 |
||
|
|
6a8a31a7f4 |
Fix layout segment optimization: move app-page imports to server-utility transition (#91701)
## Summary - Add `'turbopack-transition': 'next-server-utility'` to all server-side imports in the `app-page.ts` template so they are properly placed in the server utility layer, rather than relying on `SharedMerged` chunk groups to handle them - Remove the now-unnecessary `next-server-utility` transition from `fillMetadataSegment` import in `app_page_loader_tree.rs` - Add `SharedMultiple` chunk group variant to support multiple shared entries without a merge tag - This avoid passing the `parent` into `SharedMerged`, which did break the layout segment optimization. - Refactor `app_module_graphs` to combine `client_shared_entries` + `has_layout_segments` into a single `Option` parameter, simplifying the API and removing the dead code path for non-page endpoints ## Test plan - [ ] Verify dev and production builds work with app pages that have layout segments - [ ] Verify route handlers still build correctly (no client runtime entries passed) - [ ] Run existing app-dir e2e tests |
||
|
|
62393b0402 |
Turbopack: enable server HMR for app route handlers (#91466)
App router handlers are already built with the Turbopack runtime, so they can use server HMR. **We also already get subscription events for their chunks.** However, unlike app pages which use `__next_app__.require()` for dynamic devModuleCache lookup, route handlers capture userland exports statically in AppRouteRouteModule at construction time. This change makes routes behave a lot like pages, dynamically loading the user's code when requests are made. This way, we can freely invalidate and reload it when changes are made without evicting the entire require cache. Only `next dev` uses this lazy evaluation, as `next start` continues to eagerly import route handlers. This change removes the `isAppPage` restriction from `usesServerHmr`, extending server HMR coverage to all App Router entries (pages and route handlers) built with the Node.js runtime. For route handlers, we also clear the entry chunk from Node.js `require.cache` on each rebuild so the next `requirePage()` call re-executes the entry and obtains fresh module exports from `devModuleCache` (which HMR updates in-place). Test Plan: Added an additional e2e test, `with-dep` |
||
|
|
0ca967b863 |
Add group depth tracking to instant validation boundary discovery (#91208)
The depth-based instant validation previously only iterated URL-consuming segments as potential shared/new boundaries. Route groups between URL segments were invisible — when a route group layout with Suspense was shared in a client navigation, its Suspense appeared to cover blocking code in the validation render even though it wouldn't in reality. This adds a second dimension to the validation iteration: group depth. At each URL depth, we now also step through route group boundaries. A new `discoverValidationDepths` function walks the LoaderTree and returns an array where each entry represents the max group depth at that URL depth. The validation loop iterates from deepest group depth to shallowest within each URL depth, stopping on the first error. Key design decisions: - URL depth and group depth counters are advanced before the boundary check, making the boundary condition a simple `nextUrlDepth > depth && currentGroupDepth >= groupDepth` - The synthetic `(slot)` segment that Next.js inserts for parallel slots is excluded from group depth counting since it doesn't represent a real navigation boundary - Group depths are discovered from the LoaderTree rather than the URL pathname, making the tree the source of truth for validation bounds |
||
|
|
ee1f9d79ad |
Remove webpack stats generation from Turbopack (#91515)
### What? Remove the `TURBOPACK_STATS` feature that generated webpack-compatible `webpack-stats.json` files from Turbopack's output. **Deleted:** - `crates/next-api/src/webpack_stats.rs` — the entire 305-line Rust implementation that built webpack-format stats (assets, chunks, modules, entrypoints) from Turbopack's module graph **Rust changes:** - `crates/next-api/src/lib.rs` — removed `mod webpack_stats;` - `crates/next-api/src/project.rs` — removed `should_create_webpack_stats()` which read the `TURBOPACK_STATS` env var - `crates/next-api/src/pages.rs` — removed `generate_webpack_stats` import and stats output emission - `crates/next-api/src/app.rs` — same **TypeScript changes:** - `packages/next/src/shared/lib/constants.ts` — removed `WEBPACK_STATS = 'webpack-stats.json'` constant - `packages/next/src/shared/lib/turbopack/manifest-loader.ts` — removed `loadWebpackStats()`, `writeWebpackStats()`, `mergeWebpackStats()`, the `webpackStats` field, webpack type imports, and the `TURBOPACK_STATS` env guard in `writeManifests()` - `packages/next/src/server/dev/turbopack-utils.ts` — removed `shouldCreateWebpackStats` flag and two `loadWebpackStats()` call sites - `packages/next/src/build/handle-entrypoints.ts` — same ### Why? This feature is no longer needed and adds dead complexity. It was opt-in via `TURBOPACK_STATS=1` but nothing in the current codebase or tooling depends on it. ### How? Pure deletion — no behavior change for any existing user, as the feature was opt-in via an environment variable that is no longer checked. **Intentionally left untouched:** - Webpack's own `stats.json` generation (non-Turbopack path) - The Turbopack bundle analyzer / `analyze` feature - `route-bundle-stats.json` in `.next/diagnostics/` Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
66478522a8 |
Improve RSC error messages (#90702)
Improve the RSC error messages to be more comprehensive. x-ref: https://github.com/vercel/next.js/pull/89688#discussion_r2865847895 --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com> |
||
|
|
924d39dcef |
docs(turbopack): Fix all warnings in cargo doc --no-deps --workspace, and run rustdoc as part of our CI checks (#91339)
This gets `cargo doc` clean, and enables it in CI so that we won't regress it (I'm open to removing this if it proves to be annoying). I also cleaned up some documentation and even found some dead code (deleted `turbopack/crates/turbopack-dev-server/src/source/conditional.rs`). |
||
|
|
0f867bb219 |
Turbopack: Enable server HMR by default for app pages (#91476)
This enables server HMR (formally, this is called server fast refresh) by default for app router pages. For now, it's still disabled for routes and middleware even with app router. - Replaces `--experimental-server-fast-refresh` with `--no-server-fast-refresh` to opt out - Updates telemetry to record `feature.serverFastRefreshDisabled` when the disable flag is passed - [x] Verify `next dev` enables server HMR by default (Turbopack, app dir, non-edge routes) - [x] Verify `next dev --no-server-fast-refresh` disables it - CI |
||
|
|
fa32daad8d |
Add unstable_catchError() API for custom error boundary (#89688)
This PR adds `unstable_catchError()` API for a granular custom error boundary. The error component generated by this API is not a true component format, as the wrapper provides additional args. Therefore, the API is a function call by design rather than a boundary component to avoid merging the `errorInfo` with the user-provided props from the wrapper. This API works for both the App/Pages Router. However, `retry()` is not allowed in Pages Router as it depends on `router.refresh()`, and will throw. Also, it's exported from `next/error` as there was already an `Error` component for the [Pages Router](https://nextjs.org/docs/pages/building-your-application/routing/custom-error#reusing-the-built-in-error-page). ### DevTools <img width="508" height="90" alt="CleanShot 2026-03-13 at 03 02 46@2x" src="https://github.com/user-attachments/assets/8b79b1f6-877d-4901-99f7-6a2b4d3e34fe" /> Docs to follow up: https://github.com/vercel/next.js/pull/89847 Closes NAR-768 --------- Co-authored-by: Josh Story <story@hey.com> |
||
|
|
46789444d5 |
Remove unused layer from server actions manifest (#91425)
```
// This is only used by Webpack to correctly output the manifest. It's value shouldn't be relied
// upon externally. It's possible that the same action can be in different layers in a single
// page, which cannot be modelled with this API anyway.
```
|
||
|
|
261922df82 |
Show generated code from loaders in parse error messages (#89898)
## What?
When a webpack/turbopack loader produces broken code, error messages now
display **both** the original source and the generated code with source
map information, making it much easier to debug loader issues.
## Why?
Previously, when loaders returned invalid code, error messages only
showed the original source file (after source-map remapping). Users had
no way to see what the loader actually generated, making it hard to
diagnose why the code failed to parse. Showing both sides gives full
context about what went wrong.
## How?
### Turbopack Core (`turbopack-core`)
- **`Source::description()`** — New method on the `Source` trait
providing human-readable descriptions of where code comes from.
Implemented across all source types (`FileSource`, `VirtualSource`,
`WebpackLoadersProcessedAsset`, `PostCssTransformedAsset`, etc.),
producing chains like `"loaders [sass-loader] transform of file content
of ./styles.scss"`.
- **`AdditionalIssueSource`** — New struct to hold a labeled source
location. The `Issue` trait gains an `additional_sources()` method so
issues can expose supplementary code frames.
- **`GeneratedCodeSource`** — A wrapper that strips `GenerateSourceMap`
support from a source, ensuring the *generated* code is displayed as-is
rather than being remapped back to the original.
- **`IssueSource::to_generated_code_source()`** — Helper that detects
sources implementing `GenerateSourceMap` and wraps them in
`GeneratedCodeSource` for display. Used by `AnalyzeIssue` and
`ParsingIssue` to automatically attach generated code frames.
### Error Formatting
- **`turbopack-cli-utils`** — Renders additional sources in CLI issue
output.
- **`format-issue.ts`** — Renders additional sources in the browser
error overlay. Extracted `formatSourceCodeFrame()` helper to deduplicate
code-frame rendering between primary and additional sources.
- Long-line truncation (e.g. minified CSS from SCSS) is handled natively
by the Rust-based `codeFrameColumns` implementation.
### Type Definitions
- Added `SourcePosition`, `IssueSource`, and `AdditionalIssueSource`
interfaces to TypeScript types.
- Updated `PlainSource` (added `file_path`), `PlainIssue` (added
`additional_sources`), and NAPI bindings to pass the data through.
### Test Coverage
- **E2e tests** (`test/e2e/webpack-loader-parse-error/`) with custom
broken JS and CSS loaders, covering all 4 modes:
- **Development (Turbopack)** — Verifies parse errors show both original
and generated code via browser error overlay
- **Development (Webpack)** — Verifies error overlay shows the parse
error (webpack doesn't support additional sources)
- **Production (Turbopack)** — Verifies build failure output with full
error extraction and inline snapshots
- **Production (Webpack)** — Verifies build failure output with inline
snapshots
- Updated `test/development/sass-error/` snapshot to include the new
generated code frame for minified SCSS output.
### Example Output
When a loader produces broken code, users now see:
```
⨯ ./app/data.broken.js:3:1
Parsing ecmascript source code failed
1 | // This file will be processed by broken-js-loader
2 | // The loader will return invalid JavaScript with a source map
> 3 | export default function Data() {
| ^
4 | return <div>original source content</div>
5 | }
6 |
Expected '</', got '{'
Generated code of loaders [./broken-js-loader.js] transform of file content of app/data.broken.js:
./app/data.broken.js:3:46
1 | // Generated by broken-js-loader
2 | export default function Page() {
> 3 | return <div>this is intentionally broken {{{ invalid jsx
| ^
4 | }
5 |
Import trace:
Server Component:
./app/data.broken.js
./app/page.js
```
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <lukeisandberg@gmail.com>
|
||
|
|
d9751eaa14 |
Turbopack: use ChunkGroupEntry::Shared, part 2 (#91279)
Followup to https://github.com/vercel/next.js/pull/90978, which fixed the `ChunkGroup`s passed to `.chunk_group()`, forgot to update the the module graph entry types, which didn't match up. The `ChunkGroupEntry::Shared(ResolvedVc::upcast(*server_component))` one was more involved: - We used to have `layout.js [app-rsc] (Next.js Server Component)` --ChunkingType::Shared--> `layout.js [app-rsc]` (the actual module) - But this meant that the actual chunk group was `Shared(server_component.await?.module)` (the inner one) - This wasn't possible to use for the module graph construction though, as the client reference transform needs to see the `layout.js [app-rsc] (Next.js Server Component)` module during the traversal, to infer the parent server component of the given subgraph. So instead, move to - `loader-tree in the template` - --ChunkingType::Shared--> - `layout.js [app-rsc] (Next.js Server Component)` - --ChunkingType::Parallel--> - `layout.js [app-rsc]` The reason for that `ChunkingType::Shared` is so that we can chunk arbitrary modules that are loaded by `ChunkGroup::Entry`. Meaning that we can do `chunk_group(Shared(root_layout))).concat(chunk_group(Shared(layout))).concat(chunk_group(Entry(page)))` and rely on turbotask caching to deduplicate layout chunking across pages. |
||
|
|
2e4f08ca0a |
fix(build): don't block SSG on telemetry flush, add persistence spans to trace-build (#91335)
### What? Two fixes for the Turbopack build tracing introduced in #90397: 1. **Don't block SSG on Turbopack shutdown**: `workerMain()` no longer awaits the shutdown promise before returning. Trace event collection is deferred to `waitForShutdown()`, which the parent process awaits *after* SSG completes. This allows static generation and Turbopack persistence/cache-flush to run in parallel. 2. **Add persistence spans to `trace-build` allowlist**: `turbopack-build-events`, `turbopack-persistence`, and `turbopack-compaction` are now included in the `to-json-build.ts` allowlist so they appear in `.next/trace-build`. ### Why? - The `await shutdownPromise` in `workerMain()` was too eager — it prevented the caller from acknowledging the build as complete and starting SSG until Turbopack persistence finished flushing to disk. - The persistence/compaction spans emitted by Rust (`turbopack-persistence`, `turbopack-compaction`) were not in the `to-json-build.ts` allowlist, so they were silently filtered out of `.next/trace-build`. ### How? **`impl.ts` (worker)**: - Removed `await shutdownPromise` from `workerMain()` — it now returns build results immediately - `waitForShutdown()` now returns `{ debugTraceEvents }` after awaiting shutdown, so trace events are collected only after all compilation events (including persistence spans) have been processed **`index.ts` (parent)**: - Moved `recordTraceEvents(debugTraceEvents)` from the `workerMain` result handler into the `shutdownPromise` `.then()` chain, so events are replayed into the parent reporter after shutdown completes **`to-json-build.ts`**: - Added `turbopack-build-events`, `turbopack-persistence`, `turbopack-compaction` to the allowlist **Test updates**: - Enabled `turbopackFileSystemCacheForBuild: true` in the trace-build test fixture - Updated the Turbopack inline snapshot to include `turbopack-build-events` |
||
|
|
2b4bde2ebb |
Enable thin LTO for release builds (#91343)
# What Add `lto = "thin"` to the workspace-level `[profile.release]` in `Cargo.toml`. # Why Thin LTO enables cross-crate optimization at link time, improving runtime performance of the final artifacts (next-napi-bindings native module, turbopack-cli binary, and benchmarks) without the full build time cost of fat LTO. # How Added `lto = "thin"` to the workspace root `[profile.release]` section. This applies to all release-profile link targets in the workspace, including binaries, cdylibs, and benchmarks. The `release-with-assertions` and `release-with-debug` profiles inherit from `release`, so they also benefit. |
||
|
|
202db657d8 |
Turbopack: don't emit polyfill chunk for API routes (#91331)
1. This is misldeading in the bundle analyzer view 2. If you have a Nextjs app consisting of only API routes, then there is no need to ever write out any polyfill chunk. |
||
|
|
e22988e5ee |
Turbopack: switch chunk/asset hashes from hex to base40 encoding (#91137)
### What?
Switch Turbopack's hash encoding for chunk and asset output filenames
from hexadecimal (base16) to base40, using the alphabet \`0-9 a-z _ - ~
.\`. Version hashes (used for HMR update comparison, not filenames) use
base64 instead.
### Why?
Base40 encodes the same number of bits in fewer characters than hex,
producing shorter output filenames. All 40 characters are RFC 3986
unreserved (URL-safe) and safe on case-insensitive filesystems (macOS
HFS+/APFS, Windows NTFS).
Hash truncation lengths are reduced proportionally to maintain
equivalent collision resistance:
| Context | Before (hex) | After (base40) | Entropy |
|---|---|---|---|
| Content hash in chunk filenames | 16 chars | 13 chars | ~69 bits |
| Content hash in asset filenames | 8 chars | 13 chars | ~69 bits |
| Ident disambiguator hash | 8 chars | 7 chars | ~37 bits |
| Long-path prefix hash | 5 chars | 4 chars | ~21 bits |
### How?
**New encoding module** (\`turbo-tasks-hash/src/base40.rs\`):
- Defines the base40 alphabet and length constants (\`BASE40_LEN_64 =
13\`, \`BASE40_LEN_128 = 25\`)
- Implements a generic \`encode_base40_fixed<N>\` helper to avoid
duplication
- Public API: \`encode_base40(u64) -> String\` and
\`encode_base40_128(u128) -> String\`
**New base64 encoding** (\`turbo-tasks-hash/src/base64.rs\`):
- \`encode_base64(u64) -> String\` — 11-char base64 (no padding) for
version hashes
- Version hashes don't appear in URLs or filenames, so base64 is safe
and shorter
**New \`HashAlgorithm\` variants** (\`turbo-tasks-hash/src/lib.rs\`):
- \`Xxh3Hash64Base40\` and \`Xxh3Hash128Base40\` added alongside
existing hex variants
- Existing hex variants kept for internal manifests and identifiers
**\`ContentHashing\` moved to \`turbopack-core\`**:
- Moved from \`turbopack-browser\` to
\`turbopack-core/src/chunk/mod.rs\` so both \`BrowserChunkingContext\`
and \`NodeJsChunkingContext\` can use it
**Separate chunk vs asset content hashing**:
- \`BrowserChunkingContext\`: \`content_hashing\` renamed to
\`chunk_content_hashing\` (optional), new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- \`NodeJsChunkingContext\`: new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- Builder methods: \`use_content_hashing()\` renamed to
\`chunk_content_hashing()\`, new \`asset_content_hashing()\`
**Version hashes switched to base64**:
- \`turbopack-nodejs/src/ecmascript/node/version.rs\`
- \`turbopack-dev-server/src/html.rs\`
- \`turbopack-browser/src/ecmascript/version.rs\`,
\`merged/version.rs\`, \`list/version.rs\`
**Other callers updated** (15 files across turbopack and next-core):
- All chunk/asset content hashing switched from \`Xxh3Hash128Hex\` →
\`Xxh3Hash128Base40\`
- \`ContentHashing::Direct { length }\` reduced from 16 → 13
- Asset path truncations use full 13-char base40 hash (matching chunk
filenames)
**Exception — \`wasm_edge_var_name\`** (\`turbopack-wasm/src/lib.rs\`):
- Kept as \`Xxh3Hash128Hex\` because the hash is used as part of a
JavaScript variable name (\`wasm_{hash}\`), and base40 characters \`-\`,
\`~\`, \`.\` are not valid JS identifier characters.
**Scope — NOT changed:**
- Webpack configuration (unchanged)
- Internal manifests (\`routes_hashes_manifest\`,
\`project_asset_hashes_manifest\`)
- Internal identifiers (font naming, external module hashing, data URI
sources, debug IDs)
- SRI hashes (SHA-based Base64, different purpose)
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
236a76dd0f |
[turbopack] Remove turbo_tasks::function from ModuleReference getters (#91229)
### What? Refactors the `ModuleReference` trait to make `chunking_type()` and `binding_usage()` methods return direct values instead of `Vc<T>` wrapped values, removing the need for async task functions. Also removes the `get_referenced_asset` task from `EsmAssetReference`, inlining its logic into the callers. ### Why? This change simplifies the API by eliminating unnecessary async overhead for methods that typically return simple, computed values. The previous implementation required `#[turbo_tasks::function]` annotations and `Vc<T>` wrappers even when the methods didn't need to perform async operations or benefit from caching. ### Impact | Metric | Base | Change | Delta | |--------|------|--------|-------| | Hits | 35,678,143 | 35,845,124 | **+166,981** | | Misses | 9,418,378 | 7,910,986 | **-1,507,392** | | Total | 45,096,521 | 43,756,110 | **-1,340,411** | | Task types | 1,306 | 1,277 | **-29** | 29 task types were removed, eliminating **2.6M total task invocations** (1.1M hits + 1.5M misses): - **`chunking_type`** — 21 task types removed across all `ModuleReference` implementors (~952k invocations) - **`binding_usage`** — 6 task types removed (~527k invocations) - **`BindingUsage::all`** — helper task removed (~36k invocations) - **`EsmAssetReference::get_referenced_asset`** — removed and inlined (~1.08M invocations: 628k hits + 451k misses) The removed `get_referenced_asset` hits reappear as +628k hits on `EsmAssetReference::resolve_reference` and `ReferencedAsset::from_resolve_result` (with zero increase in misses), confirming the work is now served from cache through the existing callers. No tasks had increased misses — the removal is clean with no cache invalidation spillover. I also ran some builds to measure latency ``` # This branch $ hyperfine -p 'rm -rf .next' -w 2 -r 10 'pnpm next build --turbopack --experimental-build-mode=compile' Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile Time (mean ± σ): 52.752 s ± 0.658 s [User: 376.575 s, System: 106.375 s] Range (min … max): 51.913 s … 54.161 s 10 runs # on canary $ hyperfine -p 'rm -rf .next' -w 2 -r 10 'pnpm next build --turbopack --experimental-build-mode=compile' Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile Time (mean ± σ): 54.675 s ± 1.394 s [User: 389.273 s, System: 114.642 s] Range (min … max): 53.434 s … 58.189 s 10 runs ``` so a solid win of almost 2 seconds MaxRSS also went from 16,474,324,992 bytes to 16,359,309,312 bytes (from one measurement) so a savings of ~100M of max heap size. ### How? - Changed `chunking_type()` method signature from `Vc<ChunkingTypeOption>` to `Option<ChunkingType>` - Changed `binding_usage()` method signature from `Vc<BindingUsage>` to `BindingUsage` - Removed `ChunkingTypeOption` type alias as it's no longer needed - Updated all implementations across the codebase to return direct values instead of wrapped ones - Removed `#[turbo_tasks::function]` annotations from these methods - Updated call sites to use `into_trait_ref().await?` pattern when accessing these methods from `Vc<dyn ModuleReference>` - Removed `EsmAssetReference::get_referenced_asset`, inlining its logic into callers - Added validation for `turbopack-chunking-type` annotation values in import analysis - Fixed cache effectiveness analysis script |
||
|
|
07189c5224 |
Expose edge runtime fields in build-complete (#91239)
### What? - Add canonical edge entrypoint metadata to edge function definitions in the middleware manifest. - Surface that canonical entrypoint through `build-complete` as `edgeRuntime` metadata in adapter outputs. - Update middleware and adapter tests to assert the new metadata instead of relying on bundler-specific emitted file names. ### Why? Adapter consumers need a stable way to load and invoke edge outputs without depending on `next-server`'s sandbox handling or guessing which emitted file is the executable entrypoint. The existing checks were also brittle across webpack and Turbopack because the emitted file lists can differ. ### How? - Add an optional `entrypoint` field to `EdgeFunctionDefinition` and populate it in both the webpack middleware manifest generator and the Turbopack manifest emitters for app, pages, and middleware edge functions. - In `packages/next/src/build/adapter/build-complete.ts`, use that canonical entrypoint for `filePath` and expose `edgeRuntime.modulePath`, `edgeRuntime.entryKey`, and `edgeRuntime.handlerExport` for edge outputs. - Adjust the middleware manifest tests to validate cross-bundler invariants and add adapter-config coverage for the new `edgeRuntime` fields. |
||
|
|
d7de6f105c |
Turbopack: Remove the IntoTraitRef trait, make it an inherent method on Vc (#91223)
This is basically one-shot from Opus 4.6, with a minor tweak to the doc comment by hand. Discussed this with @lukesandberg, and we realized that there's only one implementation of this trait, so it doesn't make a ton of sense as a trait. |
||
|
|
a6b36200ff |
Wire cache handlers in edge paths and add e2e regression coverage (#91236)
### What? This PR wires custom `cacheHandler` / `cacheHandlers` through the remaining edge entrypoints so edge app pages, edge app routes, edge pages SSR, middleware, and edge API routes can all see the configured handlers. It also adds an e2e regression suite covering: - pages-router ISR revalidation with a custom incremental cache handler - app-router cache handlers with cache components enabled - edge app page + edge app route wiring when cache components are disabled ### Why? We already support custom cache handlers in non-edge paths, but several edge code paths were not forwarding that configuration into the generated entrypoints/runtime wrappers. That meant custom cache handlers could be skipped in edge rendering and edge route execution, and we did not have regression coverage around those cases. ### How? - pass `cacheHandler` / `cacheHandlers` through the JS build entry plumbing for edge server entries - inject cache handler imports/registration into the webpack edge templates and loaders - mirror the same wiring in the Turbopack/Rust entry generation for edge app pages, app routes, pages SSR, and middleware - update the edge route wrapper to initialize and register cache handlers before invoking the route module - extend `next-taskless` template expansion with raw injection support so the generated edge templates can add imports and registration code - add `test/e2e/cache-handlers-upstream-wiring` fixtures to cover pages/app, edge/non-edge, and revalidation behavior |