Skip to content

[pull] canary from vercel:canary#1007

Merged
pull[bot] merged 15 commits intocode:canaryfrom
vercel:canary
Apr 29, 2026
Merged

[pull] canary from vercel:canary#1007
pull[bot] merged 15 commits intocode:canaryfrom
vercel:canary

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Apr 29, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

sokra and others added 15 commits April 28, 2026 21:45
…ations (#93200)

## What?

Adds performance optimizations for the common case of single-item lost
follower operations in the turbo-tasks-backend aggregation update
system, along with improved tracing capabilities for debugging.

## Why?

When profiling turbopack operations, we observed that many aggregation
update jobs involve only a single upper losing a single follower. The
previous implementation always used vectors and iteration even for these
single-item cases, adding unnecessary allocation overhead.

Additionally, the existing `trace_aggregation_update` feature was
incomplete (compilation errors) and lacked visibility into task data and
operation statistics.

## How?

### Single-item optimization
- Added a dedicated `InnerOfUpperLostFollower` job variant and
`inner_of_upper_lost_follower` function to handle the single upper +
single follower case without vector allocation
- Optimized the plural variants (`InnerOfUppersLostFollowers`,
`InnerOfUppersLostFollower`, `InnerOfUpperLostFollowers`) to delegate to
the singular function when they contain only one item

### Tracing improvements  
- Added `trace_aggregation_update_stats` feature flag that enables
counters for all aggregation update job types (new_followers,
lost_followers, balance_edge, optimize_task, etc.) recorded in trace
spans
- Fixed `trace_aggregation_update` feature compilation errors by using
`task.get_task_description()` instead of
`ctx.get_task_description(task_id)`
- Gated the `TaskDataCategory` used in aggregation update
`ctx.task(...)` calls behind a const that resolves to `Meta` by default
and `All` when tracing is enabled, so traces can see full task data
without affecting default performance

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
### What?

Scope-hoisted execution of a module that re-exports its own namespace
(`export * as X from './self'`) returned the wrong namespace for named
imports of the module.

Given:

```js
// data.js
import * as Self from './data'
export function foo() { return 'foo' }
export function bar() { return 'bar' }
export function fooViaSelf() { return Self.foo() } // Self.foo undefined
export * as Data from './data'
```

Any binding imported from `./data` that relied on the module's own
namespace (e.g. `Self.foo`, `Data.foo`, `Data.Data.foo`) was `undefined`
at runtime. Without scope hoisting the same code worked correctly.

This PR fixes the bug and adds execution tests covering self-namespace
re-exports — with and without scope hoisting — including chained
re-exports (`Data.Data.foo`, `Data.Data.Data.bar`).

### Why?

For an access like `Self.Data` where `Self = import * as Self from
'./data'`
and `Data` is exposed through `export * as Data from './data'`, the
namespace-member-access optimization rewrites the reference to a named
import resolving to a synthesized rename module
(`./data <export * as Data>`). `ReferencedAsset::get_ident_inner` then
recurses through the rename's `EsmExport::ImportedNamespace("Data")` and
returns a `namespace_ident` derived from the inner module's chunk-item
id — but `EsmAssetReference::code_generation` independently took
`id = referenced_asset.chunk_item_id` and emitted

```js
var <inner-data-ident> = __turbopack_context__.i("<rename-id>");
```

so the variable named like `ns(data.js)` actually held
`ns(rename) = { Data: ns(data.js) }`. The non-optimized
`import * as Self` uses the same mangled name and sees the rename's
namespace, so `Self.foo()` evaluates to `undefined`.

### How?

Keep the variable name and the `.i(...)` argument consistent by moving
the "what to import" decision onto the ident itself:

- `ReferencedAssetIdent::Module` gains an `import_source: ImportSource`
field that describes what to import to populate the namespace variable.
- `ImportSource` is an enum:
  - `Module { asset }` — carries a reference to the final module in any
    re-export chain, from which the chunk-item id is lazily computed.
  - `External { request, ty }` — carries everything needed to emit
    `__turbopack_external_import` / `__turbopack_external_require`.
- The `namespace_ident` is cached in `ReferencedAssetIdent::Module` at
  resolution time (computed via `ImportSource::get_namespace_ident()`)
  so downstream sync visitors can read it without re-entering the async
  layer.
- `ReferencedAsset::get_ident` / `get_ident_inner` populate the field.
  For in-group re-exports the inner module propagates up; for external
  references the `External` variant is used.
- `EsmAssetReference::code_generation` destructures
`ReferencedAssetIdent::Module { namespace_ident, ctxt, import_source, ..
}`
  and dispatches purely on `import_source`; it no longer reads
  `referenced_asset` after the `get_ident` call. The hoisted-statement
  dedup key still uses the directly-referenced asset's id, so two
  references that happen to resolve to the same inner module via
  different paths (e.g. direct vs. through a rename) still emit
  separate `var` declarations for AST merging to rename.
- ESM-external gating (`__turbopack_external_import` vs.
  `__turbopack_external_require`) stays where it was — the emit site
  reads `self.import_externals` from the surrounding
  `EsmAssetReference`, so `ImportSource::External` does not carry it.

No additional `MergeableModuleExposure` or `additional_ids` changes are
needed. The rename module is never referenced at runtime; no snapshot
files change.

### Tests

-
`turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/self-reexport-star/`
  — scope-hoisted execution test covering self-namespace re-exports,
  nested access (`Data.Data.foo`, `Data.Data.Data.bar`), chained
  re-exports through another module, and namespace key enumeration.
-
`turbopack/crates/turbopack-tests/tests/execution/turbopack/exports/self-reexport-star-no-hoisting/`
  — same test cases, run with scope hoisting disabled, reusing the
  fixtures from the sibling directory.

Verified by `cargo test --test execution` (213 passed) and
`cargo test --test snapshot` (89 passed) in `turbopack-tests`. No
snapshot files are modified.

Closes NEXT-
Fixes #

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
The intel runners were quite a bit slower than the base arm runners. We
were previously compiling both architectures on self-hosted arm so this
just back the status quo on GH runners to improve speed. This shaves off
~10/20 mins.

Validated
[here](https://github.com/vercel/next.js/actions/runs/25071926790/job/73454315459)
Currently doesn't have a measureable performance impact.

Previously we assigned module ids for all nodes in the graph, even the subgraphs we detect as unused. They weren't chunked already, but the codegen that references those wasn't gracefully handling `.chunk_item_id(m)` failing (where `m` is a unused skipped module)

So `.iter_nodes()` should basically be deprecated. It doesn't respect `removed_unused_imports` and the side-effect-free inference. 
And this would get much much worse once NFT is on the module graph, because then there are way more modules that should be skipped via the same mechanism

This manifested as the following error after switching from `.iter_nodes` to `.traverse_edges_unordered` in global_module_ids.rs
```
Debug info:
- An error occurred while generating the chunk item [project]/node_modules/.pnpm/next@file+..+next.js+packages+next_@babel+core@7.29.0_@opentelemetry+api@1.7.0_@playwri_9a8ea08672cbf59c2720abbb0c8879be/node_modules/next/dist/esm/server/stream-utils/node-web-streams-helper.js [app-rsc] (ecmascript)
- Execution of <MergedEcmascriptModule as EcmascriptChunkPlaceable>::chunk_item_content failed
- Execution of *EcmascriptChunkItemContent::new failed
- Execution of EcmascriptModuleContent::new_merged failed
- ModuleId not found for ident: [project]/node_modules/.pnpm/next@file+..+next.js+packages+next_@babel+core@7.29.0_@opentelemetry+api@1.7.0_@playwri_9a8ea08672cbf59c2720abbb0c8879be/node_modules/next/dist/esm/shared/lib/hash.js [app-rsc] (ecmascript)

```
In dev mode, the static shell validation only runs during the initial
page load and HMR refreshes, not during client-side navigations. During
a client-side navigation an invalid dynamic usage error — for example a
`'use cache'` fill timeout, or a request API like `cookies()` called
inside a `'use cache'` scope — does still reach the browser via the
errored `'use cache'` stream. But when the cache invocation is wrapped
in a user-space `try`/`catch`, the error is caught and swallowed in user
code, and because the static shell validation isn't running to surface
it separately, the error is then neither sent to the browser nor logged
to the terminal.

When we skip the validation, we now wait for the full render stream to
finish and then, if an error was recorded during the render, send it to
the browser and log it to the terminal. Waiting for the stream to finish
mirrors what the static shell validation already does, and ensures we
also catch errors from `'use cache'` invocations that only run in the
dynamic stage.
…bursts of changes (#93229)

This adds a debounce to the _ui_ in development, reducing churn and
flicker from the Next.js logo indicator's "Compiling" and "Rendering"
messages. This is wholistic and covers any rapid changes in dev, usually
caused by agents making a series of changes.

**There's currently a debounce of 30ms coming from the subscription to
changes in Turbopack itself, we could consider lengthening that as
well.**

This commit adds a useDebouncedValue hook and uses it to debounce status
transitions in NextLogo, so rapid active->active alternations (e.g.
Compiling→Rendering→Compiling) are smoothed into a single stable state
for 300ms. Transitions to/from None remain immediate so:

- Fast single builds that complete before the 400ms enterDelay still
never show the pill.
- The pill still appears promptly when a long build starts (no added
latency on top of the existing enter delay).

Changes:
- use-debounced-value.ts: new generic trailing-edge debounce hook with
an optional leading predicate to bypass the delay for specific
transitions.
- next-logo.tsx: apply the debounce to the computed status; derive
shouldShowStatus from the debounced value so both pill mount-gating and
  label transitions are smoothed together.

Test Plan: Ran a script that simulated an agent making hundreds of
changes in rapid successions. Verified before this change, the indicator
would flash between Compiling and Rendering rapidly. Now, it
consistently stays on "Compiling" before the final render.
Previously, it only used the rust files as the cache key via `rust-fingerprint`.
So changing fixtures didn't invalidate the turborepo run.
Add `iter_reachable_modules` and `iter_reachable_nodes`. They actually respect unused references
…93323)

Currently not implemented, but maybe in the future
<!-- 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

### Signed commits

- This repository requires verified commit signatures on protected
branches.
- If this pull request is blocked for unsigned commits, re-sign the
commits and force-push the branch.
- A `Signed-off-by` line in the commit message is not enough.


## 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 #

-->
From
vercel/nft@baf9df6

Still need to eventually look into actually enabling all of these
Land a few optimizations to the trace server

* Change `SpanEvent` so it is 32 bytes instead of 40 bytes by triggering a `niche` optimization
* Change `args` and `events` to be a `smallvec` with inline size 1
  * for `args` it is size <=1 ~31% of the time
  * for `events` it is size <=1 69% of the time
* Compute min/max timestamps in a single pass instead of 2 when inserting into the selftimetree
* Bundle dynamically computed 'total' fields behind a single OnceLock
   * saves 40 bytes per span due to `Oncelock` overheads
* Inline SpanTimeData and SpanNames into Span
   * We get little benefit from deferring the allocations and by inlining we save time and improve memory locality.
     * post load SpanTimeData is allocated for 94% of spans, but after loading `trace.nextjs.org` it is 100%
     * post load SpanNames is allocated for 0% of spans, but after loading it is 96.2% of spans
* Remove the `inner` `OnceLocks` from `SpanNames` we can just allocate these all together


Measuring with one 10gb trace file I see loading times progress from 75.7s (33G of ram)  to 60.5s (19.5G of ram).  With loading times hitting >200mb/s occasionally
## What?

Updates the tokio and tokio-util dependencies to their latest versions:
- `tokio`: `~1.47.3` → `1.52.1`
- `tokio-util`: `0.7.13` → `0.7.18`

## Why?

Keep Rust dependencies up-to-date to benefit from bug fixes, performance
improvements, and security patches.

## How?

Updated version constraints in `Cargo.toml` and regenerated
`Cargo.lock`.

<!-- NEXT_JS_LLM_PR -->
## What

Remove another lazy lock from trait vtables.

---------

Co-authored-by: Luke Sandberg <lukesandberg@users.noreply.github.com>
@pull pull Bot locked and limited conversation to collaborators Apr 29, 2026
@pull pull Bot added the ⤵️ pull label Apr 29, 2026
@pull pull Bot merged commit e1bb911 into code:canary Apr 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants