mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/fallback-root-cache
1409 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7b58e5880c |
Turbopack: Add support for specifying additional roots (#98003)
Full motivation and plan here: https://app.notion.com/p/vercel/Turbopack-pnpm-Global-Virtual-Store-383e06b059c480579403ddfd71cc2d40?source=copy_link The goal is to allow `DiskFileSystem` to traverse outside of it's own root to other configured `DiskFileSystem`s when following symlinks. We may allow traversal in other situations in the future, but this is limited to symlink resolution for now. ## Global Virtual Store The motivation for this is to enable [pnpm's Global Virtual Store feature](https://pnpm.io/global-virtual-store) (and there are other package managers doing this, including nub and bun). We'd expose the ability to manually configure this in `next.config.js`, but we should also auto-configure ourselves for popular package managers (or at least make a best effort to do so, the `PNPM_HOME` semantics can be complicated). The `ignoreIfMissing` option is provided for this situation: We can configure a bunch of roots automatically, and they only actually get set up if they exist, the check for directory existence is cheap. ## NFT changes This requires a couple extensions to the `*.nft.json` file format: https://github.com/vercel/next.js/pull/98469 ## Related Issues - #93556 - https://github.com/pnpm/pnpm/issues/14972 |
||
|
|
469a7c5573 |
Turbopack: Add symlinks and additional roots to NFT metadata (#98469)
Implements the following extension:
```typescript
export interface NftFileList {
// File paths relative to the directory containing the `.nft.json` file, or
// the current root (if inside of `NftAdditionalRoot`).
//
// When using webpack, these paths may exist outside of the tracing root. The
// [`@vercel/next` package ignores these paths][vc-next].
//
// When using Turbopack, these paths are all guaranteed to exist within the
// `turbopack.root` specified or inferred from the `next.config.js` file.
//
// [vc-next]: https://github.com/vercel/vercel/blob/%40vercel/next%404.20.5/packages/next/src/server-build.ts#L1022-L1026
files: string[]
// Turbopack extension: A parallel array to `files` (same indices and length)
// with file content hashes. For symlinks, the hash of the path of the target
// is stored.
fileHashes?: string[]
// Turbopack extension: Explicit symlink mapping information.
//
// If included, it's safe to assume that if a file is not in `symlinks` that
// it is not a symlink. If this is an empty array, there are no symlinks.
//
// If omitted, the processor of the nft file must call `read_link` on every
// file to determine if it is a symlink and determine the target path.
//
// This field is always included if `NftJson` includes `additionalRoots`, and
// it is always included on `NftAdditionalRoot`.
symlinks?: NftSymlink[]
}
export interface NftJson extends NftFileList {
version: 1
// Turbopack extension: A hash of the entrypoint that refers to these traced
// files. This hash only depends on the content of the entrypoint file, and
// not all of its traced dependencies.
entryHash?: string
// Turbopack extension: Paths stored with different base paths, typically
// outside of the tracing root.
additionalRoots?: NftAdditionalRoot[]
}
// Turbopack extension: A collection of paths stored with a different base path.
export interface NftAdditionalRoot extends NftFileList {
// Stable unique identifier provided in the `next.config.js`. This can be used
// to generate the output path where these files are copied to (e.g.
// `.additionalRoots/$[name}`).
//
// This is guaranteed to use a character set that is valid on most
// filesystems, and the identifiers are guaranteed to not have overlaps on
// case-insensitive filesystems.
name: string
// A source path on the build machine that the paths in `files` are relative
// to. The final build output directory should not depend on this path.
absolutePath: string
// Always specified on NftAdditionalRoot.
symlinks: NftSymlink[]
}
// Turbopack extension: Information on a symlink, including which additional
// root it maps to. Symlinks that do not cross root boundaries (the common case)
// omit the index into `additionalRoots`.
//
// It is often complicated to transform raw symlink targets to root-relative
// paths, and including this information here ensures that the NFT reader gets
// the same result that Turbopack's tracing system expects.
//
// Because the link target type is unspecified, on Windows the reader needs to
// call `stat` to determine if a link target is a directory or file.
export type NftSymlink =
| [
// Index of `files` that refers to a symlink.
number,
// The target path of the link. In `NftJson`, this path is relative to the
// directory containing the `.nft.json` file. In `NftAdditionalRoot`, this
// is relative to the current root.
string,
]
| [
// Index of `files` that refers to a symlink.
number,
// The target path of the link relative to the specified root.
string,
// An index into `additionalRoots`, -1 if the target path is relative to
// the `.nft.json` file's directory,
//
// If the symlink target is relative to the same root as the symlink
// itself (the "current root"), this field is omitted.
number,
]
```
Nothing currently populates `additionalRoots`.
https://github.com/vercel/next.js/pull/98003 will do it.
|
||
|
|
389f164d3c |
Fix generated Turbopack GC option docs (#98888)
I was noticing the generated file change every time I ran `pnpm swc-build-native`. This comment was added [here](https://github.com/vercel/next.js/pull/98378/changes#diff-0db38a2a3ab56ce5ce08014e3f096a6043c5fedd54a4048d231de4cbaa4ed6f1R512) in the generated file, but not in the source |
||
|
|
e6688470ef |
Properly disable laziness on next/dynamic (#98828)
next/dynamic assumes a eager semantic for css gathering, we are making sure that holds even when lazy dynamic imports are enabled |
||
|
|
5d9ab72cef |
Add next upgrade --ai and security vulnerability coverage (#98562)
> [!TIP] > Recommended to review commit by commit. This PR adds `next upgrade --experimental-ai="security"` flag (alias `--ai`), which is targeted to help users leverage agents to upgrade their app to the safe major version when their app's Next.js version has any security advisories. Once the command is ran from the user, Next.js will detect the installed agent harness in user's device, currently limited to Codex and Claude, and will proceed with starting an agent session once approved. If it is called within an agent session, the work will continue off within that agent. `next upgrade --ai` simply does two things: - prepare the relevant context to temporary dir - print hand off prompt, guiding to read those context The context will guide the agent to run relevant codemods and migration checklist to proceed. This PR is a base core of the workflow, and will have wrappers of entry point around this. Also, will add "latest" and "future" as follow up, which will cover the app to be always latest, and adopt the future defaults like Cache Components. This PR also sets up the evals infra and adds evals. |
||
|
|
bf13ab8ac8 |
Introduce config options for Turbopack GC (#98378)
### What?
Replaces the Turbopack GC environment variables with an
`experimental.turbopackGc`
config option
```js
// next.config.js
experimental: {
turbopackGc: true, // on, defaults
turbopackGc: { minProgressMs: 100, rootTtlMs: 432e6 }, // on, tuned
}
```
This replaces the env vars which were unused and not the ergonomic
|
||
|
|
02f3bebc63 |
Turbopack: Remove wasmer from lockfile by deleting dead feature option from our wasm crate (#98761)
Apparently we don't support swc plugins when using the wasm binary, and this feature flag is never used. It was causing wasmtime to show up in our lockfile. Actual impact on compilation time is likely zero, as we never enabled this feature. |
||
|
|
16c8ea5d55 |
Prefer using join over try_join when you don't want a vec (#98547)
Adopt `join` over `try_join` in a number of places to avoid temporary Vec allocations `try_join` always copies a `Vec<Result<T>>` to a `Result<Vec<T>>` which is sometimes convenient but at least sometimes wasteful if you are immediately transforming into another collection type. Adopt it to turn `primary_modules` into a `SmallVec` since in the vast majority of cases there is only one module and so we can save a bunch of allocations of tiny arrays --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bdbf63aef7 |
fix(next-custom-transforms): never place generated imports before directives (#98717)
## Summary
**What?** Fixes the error reported for a module that has a top-level
`"use client"` directive and an inline `"use server"` function
directive, e.g. `<form action={async () => { "use server" }} />` in a
client page.
**Why?** Under Turbopack this reported a bogus error — `The "use client"
directive must be placed before other expressions` — even though the
directive was already at the top of the file. The actual mistake (an
inline server action inside a Client Component) was never surfaced.
**How?** On Turbopack's RSC layer the server actions transform runs
*before* the React Server Components assert. When it hoists an inline
action it prepended its generated imports (`registerServerReference`,
action encryption, cache runtime) at index 0 of the module — *above* the
`"use client"` directive, which the transform does not consume. The RSC
assert then saw an import before the directive and reported it as
misplaced.
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
|
||
|
|
3dc93da27e |
Improve tree shaking through Next.js module wrappers (#98638)
## What? Extends the export-usage passthrough introduced by #98621 to the transparent wrappers used for dynamic entries, server components, and shared server utilities. The existing reference-tree-shaking production fixture now covers each wrapper and verifies that unused sibling exports do not remain in Turbopack's server chunks. ## Why? These wrappers forward another module's export surface, but previously marked every target export as used. That kept otherwise unreachable modules in production output and limited inner-graph tree shaking. ## How? The dynamic-entry and server-component wrappers now pass their own resolved export usage to their targets. The custom server-utility reference reports the same passthrough binding usage while preserving its shared chunking and merge behavior. A public Turbopack constructor creates passthrough export-usage values for references outside `turbopack-core`, consistent with the existing constructors for other usage modes. ## Verification - `cargo check -p next-core` - `pnpm build-all` - `pnpm test-start-turbo test/production/app-dir/reference-tree-shaking/reference-tree-shaking.test.ts` - `pnpm test-start-webpack test/production/app-dir/reference-tree-shaking/reference-tree-shaking.test.ts` <!-- NEXT_JS_LLM --> <!-- fleet df1472be-5cd1-4bd6-9fab-4efc1defa4f9 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
e3ababa0d5 |
Trace export usage through client component proxies (#98621)
## Summary Turbopack previously widened export usage to every export when a Server Component crossed a client-component proxy. As a result, importing one named export from a `use client` module retained unrelated implementations in both browser and SSR bundles. This extends Turbopack's import annotations with a generic export-usage passthrough. The server transform for `use client` modules marks its generated namespace import as passthrough, so the ordinary binding-usage fixed point carries the Server Component's used export set into `EcmascriptClientReferenceModule`. Its client-reference edges use the same signal to forward that set to browser and SSR targets. Side-effect-only evaluation references retain their evaluation semantics instead of inheriting the annotation. The forwarded names remain namespace-observable because React Flight resolves client references by their original export names, so unused exports can be removed without changing the protocol-visible identity. An importer whose usage is `All` still forwards `All`. The client-component tree-shaking test now checks that unused markers are absent from every browser chunk and verifies the same behavior in Turbopack SSR chunks. ## Verification - `cargo fmt --all -- --check` - `cargo check -p next-core` - `cargo test -p turbopack-core module_graph::binding_usage_info::tests --lib` - `pnpm build-all` - `pnpm test-start-turbo test/production/app-dir/client-components-tree-shaking/index.test.ts` - `pnpm test-start-webpack test/production/app-dir/client-components-tree-shaking/index.test.ts` <!-- NEXT_JS_LLM --> <!-- fleet 10716dab-2d36-4687-94ae-cd7bdfeac985 --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
000390a8fc |
fix: detect proxy.ts correctly with compound pageExtensions (#93246)
## Maintainer status - Current with `canary` as of 2026-05-18; this branch includes a clean merge from latest `origin/canary`. - Lightweight checks pass; full fork workflows are still `action_required` until a maintainer approves CI. - No unresolved review threads or failing jobs are reported after the refresh. - Review focus: shared convention-file basename extraction for proxy, middleware, and instrumentation under compound `pageExtensions`; TS and Rust paths use the same rule. --- ## Summary Fixes #85648 Fixes #86303 Fixes #91600 Fixes #85646 Related to #86122 Fixes #92342 Closes #92934 When `pageExtensions` is set to compound extensions like `['page.ts', 'page.tsx']`, proxy files must be named `proxy.page.ts`. However, the proxy detection logic used `file_stem()` (Rust/Turbopack) and `path.parse().name` (JS/webpack), both of which only strip the **last** extension — so `proxy.page.ts` becomes `proxy.page` instead of `proxy`, and the proxy is never detected. **Root cause:** `path.parse('proxy.page.ts').name` returns `'proxy.page'`, not `'proxy'`. Same issue with Rust's `file_stem()` which uses `rsplit_once('.')`. **Fix:** Use `file_name().split('.')[0]` (JS) / `file_name().split('.').next()` (Rust) to extract the first segment before any dot. This correctly returns `'proxy'` for both `proxy.ts` and `proxy.page.ts`. The same `fileBaseName` extraction is also used for `middleware` and `instrumentation` convention file detection, fixing compound pageExtensions for those as well. ### Changes - **Turbopack (Rust):** `crates/next-api/src/project.rs` (2 locations) + `crates/next-api/src/middleware.rs` (1 location) - **Webpack (JS):** `packages/next/src/build/index.ts` (build-time detection) + `packages/next/src/server/lib/router-utils/setup-dev-bundler.ts` (dev-time detection) - **E2e tests:** - `proxy-page-extensions/` — proxy + instrumentation with compound extensions - `middleware-page-extensions/` — middleware with compound extensions (separate fixture because the build refuses both `proxy.*` and `middleware.*` simultaneously) ### Verified locally | Mode | Bundler | Result | |------|---------|--------| | Dev | Turbopack | PASS (5/5) | | Dev | Webpack | PASS (5/5) | | Production (build+start) | Turbopack | PASS (5/5) | | Production (build+start) | Webpack | PASS (5/5) | Existing proxy test suites also verified (proxy-runtime-nodejs, proxy-with-middleware, proxy-missing-export, proxy-runtime) — no regressions. ## Test plan - [x] `proxy-page-extensions.test.ts` covers proxy header injection, page render through proxy, and `instrumentation.page.ts:register()` running - [x] `middleware-page-extensions.test.ts` covers middleware header injection and page render through middleware - [x] All 5 cases pass in dev/turbopack, dev/webpack, start/turbopack, start/webpack - [x] Existing proxy-runtime-nodejs tests pass (dev/webpack, dev/turbopack, production/webpack) - [ ] CI passes on all existing proxy tests <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
39f5d797ab |
Optimize strict module factory chunks (#98421)
### What? Reduce Turbopack ECMAScript chunk size by emitting strict module factories in a shared strict-mode scope instead of repeating `"use strict"` in every factory. The chunk format now chooses among the existing flat representation, a mixed representation with flat non-strict factories plus a nested strict array, and an all-strict representation that wraps the complete chunk in a strict IIFE. Each factory is generated through the existing single code path; the production minimizer removes its redundant directive when the factory is created inside a strict wrapper. Browser and Node.js emitters share the mode selection and serialization implementation, while the runtime remains compatible with the existing flat format. ### Why? ECMAScript modules are always strict, so large chunks currently repeat the same directive across many factories. Moving strictness to the scope where those factories are created removes redundant bytes without copying arrays or changing strict/sloppy execution semantics. For `bench/basic-app`, this reduces total raw JavaScript by 16,831 bytes (0.161%). Partitioning factories changes compression behavior, resulting in a 1,286-byte (0.051%) gzip increase across all JavaScript; format selection therefore remains based on emitted raw bytes. ### How? - Determine each chunk item's strictness from the already-collected chunk metadata. - Generate every factory once with its normal directive, then rely on the minimizer to remove redundant directives inside strict wrappers. - Compare the expected minified directive bytes removed with the exact wrapper bytes added and keep small chunks in the existing flat format. - Keep non-strict factories flat in mixed chunks and append strict factories as an array produced by a strict IIFE. - Wrap the complete registration/export in a strict IIFE when every factory is strict, keeping those factories flat and avoiding a nested array entirely. - Use the shorter arrow IIFE when the target supports it, with a function IIFE fallback for older targets. - Preserve source-map sections, scope-hoisted module IDs, factory naming, and ordering within each partition. - Add focused strict, sloppy, mixed, all-strict, old-target, and below-threshold coverage and update affected Turbopack snapshots. ### Verification - `cargo nextest run -p turbopack-tests -E 'test(snapshot)'` - `cargo clippy -p turbopack-ecmascript -p turbopack-browser -p turbopack-nodejs` - `pnpm build-all` - `pnpm --dir turbopack/crates/turbopack-ecmascript-runtime/js check:nodejs` - `bench/basic-app` production Turbopack build, repeated to confirm deterministic size totals <!-- NEXT_JS_LLM --> <!-- fleet 528ffe69-eb27-466b-a132-842272a782cf --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
d5276f04a1 |
Make TurbopackResult payloads disjoint (#98575)
### What? Refactors the JavaScript-facing `TurbopackResult<T>` into a stable wrapper whose payload lives under `value` and whose issues remain top-level. Native API consumers, entrypoint conversion, development-server paths, HMR handling, and direct API tests now follow the disjoint shape. Two event subscriptions that had inaccurate wrapper declarations now expose their existing plain runtime payloads explicitly: update-info events remain `UpdateMessage`, and compilation events remain `CompilationEvent`. Nullable native entrypoint payloads are also declared accurately and normalized at the JavaScript API boundary. ### Why? The previous intersection-based representation merged payload fields with result metadata. That allowed fields such as `issues` to overwrite one another and caused non-object payloads to be discarded, making the result shape depend on `T`. A dedicated payload property avoids those collisions and preserves every payload type, including `null`. Keeping plain event streams distinct from result wrappers also ensures `TurbopackResult<T>` consistently means the native API actually provides wrapper metadata. ### How? The N-API serializer now always creates a fresh wrapper rather than mutating an object payload. The shared TypeScript type models that wrapper directly and requires an explicit payload type. Conversion layers replace only the nested payload while explicitly preserving wrapper issues. Call sites continue to process wrapper issues while reading domain data through `value`. The update-info and compilation-event declarations were aligned with their native callback types instead of introducing new runtime wrappers for streams that do not collect issues. Rust `Option<NapiEntrypoints>` payloads are declared as nullable, then normalized to the existing empty-entrypoints representation after a null-safe route check. ### Verification - `pnpm build-all` - `pnpm --filter=next types` - `pnpm swc-build-native` - `cargo check -p next-napi-bindings` - `cargo fmt --all -- --check` - ESLint and Prettier on changed files - `pnpm test-dev-experimental-turbo test/development/app-aspath/app-aspath.test.ts` - `pnpm test-dev-turbo test/development/basic/next-rs-api.test.ts` — 26 passed, 1 skipped, 15 snapshots; Jest reported lingering open handles after the green summary - `pnpm test-dev-turbo test/development/app-dir/concurrent-install/concurrent-install.test.ts` - `pnpm test-dev-experimental-turbo test/development/app-dir/concurrent-install/concurrent-install.test.ts` <!-- NEXT_JS_LLM --> <!-- fleet 74688340-f99a-462e-8f75-88d12c88000a --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
f41441a0c8 |
Durable use cache: optimize env var existence checks (#98504)
We now separately track code that accesses non-inlined env vars at runtime in two categories - (existing) actual reads, the full value is accessed - (new) only unset/falsy/truthy is read at runtime For the second case, we only need to invalidate use-cache functions when the given env var transitioned betwen unset/falsy/truthy. But transitioning between two different truthy values doesn't caused invalidation. |
||
|
|
30d6d8f497 |
Adds this.mode for webpack loaders (#98532)
Pretty straightforward. But a number of loaders depend on it including - postcss-loader - sass-loader - stylus-loader - vue-loader - nunjucks-loader - thread-loader |
||
|
|
d155ba9ebf |
[turbopack] Lazily compile dynamic imports in development (client side) (#97203)
## Summary Defer compiling client-side dynamic import targets in Turbopack development until the browser requests their manifest chunk. This avoids compiling untouched dynamic imports while preserving server-side imports, CSS loading, Server Actions, source maps, and Fast Refresh behavior. One caveat on this is next/dynamic still does some eagerness. I looked a bit at changing things. But it breaks some of the guarantees there and decided to leave it off the table. --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
c1156571cd |
Turbopack: Deduplicate project options documentation (#98465)
By documenting both the partial and non-partial versions of these objects, we were documenting everything multiple times. Just reference the non-partial object from the partial object's documentation. |
||
|
|
e674165f36 |
Report incompatible parallel route slots (#97430)
With strict route matching a layout can have sibling slots whose pages
can never form a complete route. For example:
```
app/
layout.tsx
@left/
foo/
page.tsx
@right/
bar/
page.tsx
```
`/foo` is always missing `@right`, and `/bar` is always missing `@left`.
The pages were authored as route targets, but neither URL can construct
the complete slot tree owned by `app/layout.tsx`. Catchall pruning
removes broad matchers that cannot render and the unmatched-page
validation reports dangling files, but neither explains this
project-level mistake.
This validates the finalized ordinary matcher set and reports each
incomplete URL at the layout that owns the conflicting slots. Adding the
corresponding pages or an explicit `default.tsx` makes the topology
valid. Framework-owned defaults do not invent a `children` slot, and
interception matchers remain exempt because they intentionally describe
partial router state updates.
<!-- NEXT_JS_LLM -->
|
||
|
|
dea15ec603 |
Require canonical routes for interception routes (#97428)
Interception routes only describe the branch used during a soft
navigation, but every URL they can produce still needs to be handled by
an ordinary App Router page matcher for a direct request or refresh.
Today an interception-only URL can appear to work during client
navigation and then 404 when the same URL is loaded directly. This makes
strict route matching reject that configuration.
For example, this setup is incomplete:
```
app/
@modal/
(.)photo/
[...id]/
page.tsx
photo/
[id]/
page.tsx
```
The ordinary route handles `/photo/1`, but the interception route also
handles `/photo/1/2`, which has no ordinary page matcher. Widening the
ordinary route makes the setup valid:
```
app/
@modal/
(.)photo/
[...id]/
page.tsx
photo/
[...id]/
page.tsx
```
The validation checks coverage rather than requiring an identical
matcher. A broader catchall can cover the intercepted pattern, and
multiple ordinary matchers can cover different accepted lengths. For
example, `items/page.tsx` together with `items/[...rest]/page.tsx`
covers an intercepted `items/[[...parts]]` matcher. Route handlers and
generated metadata routes do not count as canonical page matchers.
Webpack and Turbopack now apply the same coverage check and report the
interception matcher and the canonical pattern that is not fully
covered. Existing interception fixtures now include semantically
complete ordinary routes instead of relying on interception-only URLs.
<!-- NEXT_JS_LLM -->
|
||
|
|
7b7cee3086 |
Error when app pages do not match any route (#97401)
## What?
This reports an error under `experimental.strictRouteMatching` when a
`page.tsx` is left out of every retained matcher. A page can lose its
own broad catchall matcher and still be valid if it participates in a
more specific matcher, so this checks the final matcher set instead of
treating every pruned candidate as an error.
Both Webpack and Turbopack report every unmatched page in development
and production builds.
## Why?
Pages that are never selected by any complete loader tree are
unreachable by construction. This usually means parallel slots define
incompatible paths without matching pages or defaults, and silently
ignoring those files makes the project look like it has routes that
cannot actually exist.
For example:
```text
app/disagreeing-slots/
├── layout.tsx
├── @first/foo/page.tsx
├── @second/bar/page.tsx
└── [...slug]/page.tsx
```
Every candidate is incomplete: `/foo` has no match in `@second`, `/bar`
has no match in `@first`, and URLs handled only by the catchall have no
matching page or applicable default in either named slot. Strict route
matching prunes all three matchers, and this change reports all three
page files instead of silently leaving them out of the build.
The same validation catches a real `children` branch that only becomes
incomplete below its page:
```text
app/declared-children/
├── layout.tsx
├── page.tsx
└── @panel/
├── default.tsx
└── details/page.tsx
```
`/declared-children` is valid, but `/declared-children/details` only has
a page in `@panel`. Because the declared `children` slot has no matching
page or default at `/details`, that matcher is pruned and
`@panel/details/page.tsx` is reported as unreachable. The root
`page.tsx` is still part of the valid `/declared-children` matcher and
is not reported.
A pruned matcher is not automatically an error. If
`@slot/[...parts]/page.tsx` is combined into retained `/foo` and `/bar`
matchers, that page still participates in routes and remains valid.
## How?
Webpack snapshots the authored app page paths before pruning and
compares them with the pages used by the retained app paths. Turbopack
collects authored page source files from the directory tree and compares
them with the files referenced by the final accepted loader trees. The
tests cover disagreeing named slots, declared `children`, optional
catchalls, route groups, nested slots, and the valid case where a pruned
catchall is still used by more specific matchers.
## Verification
- `pnpm test-start-turbo
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-start-webpack
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-dev-turbo
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-dev-webpack
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-start-turbo
test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts`
- `pnpm test-start-webpack
test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts`
<!-- NEXT_JS_LLM -->
|
||
|
|
ec62d6a48f |
fix(turbopack): add get_relative_request_to for paths used as module requests (#98497)
### What?
Adds `get_relative_request_to`, next to `get_relative_path_to`, for
callers that need a relative
path they can hand to the resolver as a module request. It returns
`./c`, `../c` or `.`, where
`get_relative_path_to` returns `c`, `../c` or `.`.
Fixes `import.meta.glob`'s `base`-relative keys, which currently come
back without the leading `./`:
`{ base: '../outside' }` produces `one.js` where Vite (and our own docs,
and the existing execution
fixture) expect `./one.js`. That fixture is failing on `canary` today;
it passes here unchanged.
### Why?
The `./` prefix is not decoration — it is what makes a string a
*relative* request. Without it, `c`
resolves as the package `c` rather than the file next to `from`.
`get_relative_path_to` stopped emitting the prefix for a path below
`from`, and every caller that
builds a request had to add it back by hand. Eight of them do, each with
the same three lines. That
works, but it puts the requirement in the call sites instead of the API,
and nothing tells a new
caller that it exists — so the ninth request-building call site,
`import.meta.glob`'s `base`-relative
key, was written without it and silently produced the wrong keys. The
two changes were authored
independently and each was green on its own; only together do they
produce the bug, which is why CI
caught it on `canary` rather than on either pull request.
Rather than add a tenth hand-written prefix, this makes "give me a
request" something you can ask
for. The failure mode it removes is the silent one: a caller that
forgets is no longer possible,
because the choice is now the function you call.
### How?
`get_relative_request_to` shares its traversal with
`get_relative_path_to` through one private
helper, so the two can't drift; the only difference is whether a result
that would be bare gets the
prefix. `get_relative_path_to` keeps its signature and behaviour
exactly, including the fast path
that returns `target` by reference when `from` is empty — the request
form can't borrow there, and
the unit tests assert that difference so the fast path isn't reused by
mistake. `FileSystemPath`
gains the matching method.
The eight hand-written prefixes now call the new function and drop their
snippets. Deciding which
call sites to convert needed care, because the callers of
`get_relative_path_to` genuinely want
different things, and the ones that want a plain path deliberately keep
it:
- source-map paths reported back to the client,
- the Next.js template expander, whose invariant check expects the bare
form,
- the paths written into NFT files, which are data rather than requests.
Two locals that hold requests rather than paths are renamed accordingly,
and one leftover
`strip_prefix("./")` is removed — it had become a no-op undoing a prefix
that is no longer produced.
### Testing
The `import-meta-glob-relative` execution fixture already asserts the
correct keys and currently
fails, so the fix is verified by that fixture passing **unmodified** —
its expectations were right
all along.
The unit test that covered these cases before the prefix went away is
restored, now run against both
functions and against `FileSystemPath`, which had been left with no
coverage of either method. Its
original assertions turn out to describe the request form exactly, so
they pin that
`get_relative_request_to` reproduces the earlier contract rather than
merely resembling it.
Beyond that: the full `turbopack-tests` suite, and the `next-server-nft`
and `next-taskless` tests,
which are what would break if a call site that wants a plain path had
been converted by mistake.
<!-- NEXT_JS_LLM -->
<!-- fleet 3acc7e63-52a8-4472-89cd-b1bbb8ad59fa -->
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
|
||
|
|
76a0bdbdf4 |
[scripts] Move scripts (and benchmarks) off of node-fetch (#98347)
We're using Node.js versions with a built-in `fetch` implementation in all of these scripts. |
||
|
|
ed1aab5d38 |
fix(turbopack): resolve ../ and /-rooted import.meta.glob patterns (#96557)
### What?
`import.meta.glob()` now resolves `../`-relative and
`/`-project-root-absolute
patterns (and `base`), validates every pattern (positive and negative)
for
whether it escapes the filesystem root, and shares the underlying
"resolve leading `./`/`../` against a directory" primitive with
`next-core::relativize_glob` (used by `outputFileTracingIncludes`)
instead of
duplicating it.
### Why?
`import.meta.glob()` handed its patterns straight to Turbopack's `Glob`,
which
matches relative to the scanned directory and understands neither a
leading
`/` nor `..` segments. `import.meta.glob('../../content/*.js')` and
`import.meta.glob('/content/*.js')` therefore silently returned `{}`,
and a
`base` that walked above the filesystem root failed with an internal
error
and a panic report instead of a normal diagnostic. A negative
(exclusion)
pattern that escaped the root was silently dropped, which is the worst
outcome for an exclusion — it would silently include files the user
asked to
exclude.
### How?
- Patterns are split into the directory they're rooted in and the
remaining
glob, matched relative to a single common scan directory covering every
positive and negative pattern. `/` is resolved from the project root,
matching Vite's rule that patterns are relative to the importing file or
absolute from the project root.
- Map keys follow the pattern too: `base` now produces base-relative
keys
(`'./zeta.js'` instead of `'../../content/zeta.js'`, matching Vite), a
project-root-absolute pattern produces project-root-absolute keys, and
origin-relative keys are unchanged otherwise.
- Every pattern is validated up front; one that leaves the project (a
`base`,
a positive pattern, or a negative one) reports an error issue at the
call
site and yields an empty object, instead of crashing or being silently
dropped.
- The `./`/`../`-resolution loop is extracted into
`turbo_tasks_fs::glob::relativize_glob`, a crate both `next-core` and
`turbopack-ecmascript` already depend on. `next-core::relativize_glob`
is
now a thin wrapper over it (same name, signature and error message); its
5
unit tests are removed since the shared primitive's own tests cover the
same cases.
<!-- NEXT_JS_LLM -->
Co-authored-by: Niklas Mischkulnig
<4586894+mischnic@users.noreply.github.com>
---------
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
|
||
|
|
be2c3fdb3a |
Add a fast-path to 'get_relative_path_to' for a potential common case when 'from' is empty (#98440)
When creating an NFT file in https://github.com/vercel/next.js/pull/98003, I store a generic list of "base" paths and then construct the final path in the JSON file as relative to the base path. For most files, these paths are relative to the `*.nft.json` file, but for additional roots, this base path is always an empty string (relative to the root of the filesystem), so being able to re-use the original `RcStr` instead of constructing a brand new string may be beneficial. This also moves the unit tests to be colocated next to the functions that are actually being tested. |
||
|
|
b879776299 |
Adds this.target for webpack loaders (#98436)
There are a bunch of different loaders I found depending on this - vue-loader - svelte-loader - svg-sprite-loader - nunjucks-loader - workerize-loader - worklet-loader - nativescript-worker-loader - thread-loader - svg-sprite-generation-loader This doesn't by itself fix any of them fully, but does increase compatibility |
||
|
|
3c8e9c3855 |
Support durable use-cache entries with client components (#98140)
The current setup is: 1. A use-cache function returns an RSC value that may contain client reference proxies. Flight serializes each reference using the client module `id`, export `name`, client `chunk` list, and `async`. The resulting Flight payload is stored in the cache entry. 2. When decoding the cache entry, the client module `id` is resolved through `rscModuleMapping` to obtain the corresponding RSC module `id` and `export`. Cache decoding does not need the client chunk list because it does not load client chunks. The outer RSC serialization emits the client reference again using the current client reference manifest, including the current chunks. We can therefore serialize cached references with an empty chunk list. The client module `id` is also unstable across builds. Instead, cache entries can store the stable client reference name. When decoding, we resolve: client reference name -> current client module ID -> current RSC module ID This makes cached client references independent of build-specific module IDs and chunk filenames. |
||
|
|
73a26bfc84 |
feat(next-napi-bindings): build Turbopack for wasm (#97586)
### What? Builds Turbopack for wasm, so the wasm fallback can support Turbopack and not just SWC. ### Why? `next-napi-bindings` gated every Turbopack dependency behind `cfg(not(target_arch = "wasm32"))`, so the wasm fallback shipped SWC only. That is the reason issues like #96960 have no answer today: when the native binding cannot load there is no Turbopack at all. ### How? Moves the Turbopack dependencies into the shared dependency block, leaving genuinely native-only crates gated: - `lightningcss` / `turbopack-lightningcss-napi` and `swc_ecma_react_compiler` back the `css` and `react_compiler` modules; - `swc_plugin_backend_wasmtime` cannot run inside wasm; - SWC's plugin-host features, gated in the previous layer; - `tokio`'s `full` feature pulls in `process` and `signal`, which do not exist on wasi, so the wasi build gets its own `tokio`. This is the layer at which `swc` first gets compiled for wasm, which is why the plugin-host gating has to land **before** it: with that in place, `cargo check -p next-napi-bindings --target wasm32-wasip1-threads` passes here with no `[patch.crates-io]` override. ### What this does *not* do It makes Turbopack **compile** for wasm. It does not make it run: still outstanding are the `env.read_custom_section` loader hook, a wasm `module_init` for the tokio runtime and panic hook, a host-provided fetch, the persistent cache (WASI preview1 has no file-backed mmap or flock), and file watching. Shipping is separately blocked on the unreleased emnapi v2. `turbo-tasks-malloc`'s `custom_allocator` feature also stays off, so there is no per-thread allocation accounting on wasm and memory figures are absent from traces — an observability gap, not a correctness one. <!-- fleet b6d0486f-97c7-42a7-bdaf-3490774cdec3 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
49dc4c84b4 |
Turbopack: fix output tracing include glob traversal (#98382)
### What? Make Turbopack's `outputFileTracingIncludes` file patterns root-relative and targeted during output tracing. Reject unanchored partial globs when code attempts directory-prefix matching. Add production coverage using a real pnpm-installed WASM package, plus unit coverage for anchored directory pruning and include-pattern expansion. ### Why? Turbopack previously treated include file patterns as unanchored partial matches. The directory-prefix matcher could therefore accept every descendant after matching the first literal segment: an exact WASM include under `node_modules/.pnpm` caused 2,093 `read_glob_inner` executions instead of traversing only its 5 directory prefixes. Unanchored patterns also cannot soundly answer whether a directory may contain a future match, so allowing `contains: true` with directory-prefix matching risks both over-traversal and missed files. Include behavior also differed from webpack and the documented project-root-relative semantics. ### How? Build anchored alternatives for each include pattern: one for the direct match and one for descendants of a matched directory. Anchoring lets `read_glob` prune unrelated directories, while the recursive alternative preserves existing behavior for matched directories and directory symlinks, including their resolved target contents. `can_match_in_directory` now fails immediately for `contains: true`, while direct partial matching remains supported. Route-key and exclusion matching continue to use direct partial matches and are unchanged. The production fixture resolves `lightningcss-wasm/lightningcss_node.wasm` from a real `lightningcss-wasm@1.28.2` dependency, avoiding assumptions about pnpm's store naming or patch hashes. ### Verification - `CARGO_INCREMENTAL=0 pnpm build-all` - `cargo fmt -p turbo-tasks-fs -p next-api -- --check` - `cargo test -p turbo-tasks-fs glob` (84 passed) - `cargo test -p next-api` (17 passed) - `pnpm test-start-turbo test/production/app-dir/output-file-tracing-includes-read-glob/output-file-tracing-includes-read-glob.test.ts test/production/build-trace-extra-entries-turbo/build-trace-extra-entries-turbo.test.ts test/production/build-trace-extra-entries-monorepo/build-trace-extra-entries-monorepo.test.ts` (3 passed) - `pnpm test-start-webpack test/production/app-dir/output-file-tracing-includes-read-glob/output-file-tracing-includes-read-glob.test.ts` - The shared fixture asserts that both bundlers include a root-relative exact match and exclude a nested file with the same suffix - Measured the exact reproduction at 5 include-specific `read_glob_inner` executions, down from 2,093 <!-- NEXT_JS_LLM --> <!-- fleet b3cefb44-e1ce-4bd6-83a9-da2f22848cad --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
97e7d901c8 |
turbo-tasks: explicit GC root anchoring + cross-session orphan reclamation (#96857)
### What? Reworks how the reference-counting GC decides what is a **root** (a task that must not be collected), and adds a durable path to reclaim cross-session orphaned roots Currently GC roots are persisted and the only way to get collected is for another session to revive them, and then for their ref-counts to drop. This provides a new mechanism. 1. **In-session** — Top level operations get reference counted so they can be dropped in session. 2. **Cross-session** — live roots are recorded along with a simple aging mechanism so they can still be collected even if a later session never restores it. ### Why? This closes the final GC gap. Top level operations are ambiguous currently and need tracking. With cross session aging we close a basic gap where if you delete a route between sessions we never clean up the tasks. Now it will eventually age out. ### How? **Roots are anchored explicitly**. Via pins and RAII guards like `GcRoot` **Persisted roots set + TTL age-out** A new Infra-keyspace `GcRoots` entry persists `Vec<(TaskId, ttl)>`. That allows us to detect when roots are stale and can be dropped. **Dispose transient root tasks on drop.** `RootTask::Drop` now disposes tasks and cleans up references allowing the GC to work |
||
|
|
8b9cdc5eef |
perf(turbopack): skip ignored files during server tracing (#97475)
So I was playing around with profiling some very simple next apps when I noticed this. Basically, because of how things were set up we were spending a good chunk of our build time analyzing files we were going to reject. I went around and around trying different combinations to make sure we ignore those files (and only the files we should ignore) to speed up build times. Since it is a constant impact, mostly on small projects, but on some, it can make a big difference. I had some adversarial rounds with agents to see if they can break the config in the ways the old comments hints at why we didn't do that. And this final round finally couldn't get it to break. For a v0-generated Hello World app, compile time is 28.0% faster (+/-1.7%) and a full clean build is 15.9% faster (+/- 1.2%). --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
538f3f68e9 |
Bump swc to v77 (#97407)
There have been breaking changes to the AST: - `BlockStmtOrExpr` is now `ArrowFunctionBody` - function bodies are `FunctionBody` instead of `BlockStmt` - JSX text literals are `Wtf8Atom`, instead of `Atom` |
||
|
|
ec107c16dd |
lazy server hmr (#96566)
## Summary Make Turbopack server HMR demand-driven. Server updates are now compiled and applied when the next relevant request writes an endpoint, instead of eagerly evaluating changed server modules after every file change. This replaces the aggregate server HMR subscription with an on-demand update API, while preserving incremental updates and falling back to full cache eviction when a restart is required. Test Plan: added an e2e test <!-- NEXT_JS_LLM --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
7a15847525 |
Turbopack: add next_config.use_react_experimental getter (#98032)
Refactor to unify the multiple places where this computation was happening |
||
|
|
e3790aa911 |
More granular cache keys for use-cache entries (#95233)
Caveats: - I had to skip `react[-dom][/*]` and `private-next-rsc-server-reference` and `private-next-rsc-cache-wrapper` imports in the code hash and env-var tracking. Because those all end up pulling in app-page-turbo.runtime.prod.js which reads many env vars and would cause constant deopting. But this should still be correct. The code of these imports is included via the Next.js version, and no env vars should change the semantics of any of those imports. - Static env var reads are collected, but too dynamic accesses are silently ignored and don't lead to deopts (same goes for env var reads in native NAPI addons). This means that this cache reuse is not guaranteed to be 100% guaranteed to never lead to stale caches. - Code hashing and env var collection happens on a per-module basis. So if you put all use-cache functions into a single file and/or together with react components, then you will see extraneous invalidations. This will be fixed by either generally enabling module splitting for all of Turbopack, or by adding a special transform that does it for use-cache functions. Followups: - There are some env var static analysis gaps that will be immediate followups before broader testing. These are the various TODOs added in https://github.com/vercel/next.js/pull/95310 - Client components invalidation is very coarse grained right now (statically imports any client component anywhere -> deopt completely). Followup for the future. But the current setup is correct. This would just improve effectiveness further - Do this in dev as well. Currently there is no NFT at all in dev (for performance reasons) - Module splitting for more granular tracking - Include entropy when serializing server reference arguments for cache key Todo: - [x] Use implementation code hash (includes inlined env vars): from #94234 - [x] Include non-inlined runtime env vars: from #95310 - [x] Include client reference manifest (very coarse for now) - [x] Include Next.js version (for wire format, etc) - [x] This is now done for all use-cache entries now. Not just for `use cache: remote`. Is that the intended behavior? Yes - [ ] ~~if `NEXT_DEPLOYMENT_ID` is in the env vars. just deopt and don't care about stringifing and hashing the env vars~~ - [x] Is cache key size a problem? Currently you can get this: (values are always hashed) `CustomCacheHandler::get ["80e6f6560092f0078775e7e787c1c10ecf6dea0bc4",[],["d984fbaa996274737f3b59345a300a20","16.4.0-canary.5","__NEXT_NO_MIDDLEWARE_URL_NORMALIZE=undefined","NEXT_OTEL_PERFORMANCE_PREFIX=undefined","__NEXT_PRIVATE_ORIGIN=a04f4b9d6a8f42724740a480e9a2bc67c053dc04377be831c0e2c407a1422004","NEXT_PRIVATE_RESPONSE_CACHE_TTL=undefined","NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE=undefined","__NEXT_CACHE_COMPONENTS=b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b","__NEXT_ROUTER_BASEPATH=undefined","__NEXT_MANUAL_CLIENT_BASE_PATH=undefined","__NEXT_INSTRUMENTATION_CLIENT_ROUTER_TRANSITION_EVENTS=undefined","__NEXT_APP_NAV_FAIL_HANDLING=undefined","__NEXT_GESTURE_TRANSITION=undefined","__NEXT_USE_OFFLINE=undefined","NEXT_DEBUG_BUILD=undefined","__NEXT_VERBOSE_LOGGING=undefined...]] [["_N_T_/layout","_N_T_/page","_N_T_/","_N_T_/index"]]` |
||
|
|
17a901a74f |
Guard filesystem reads against unresolved symlinks (#97902)
### What? Adds debug-only OS realpath validation to successful `DiskFileSystem` file and directory reads. When a successfully canonicalized path differs from the supplied path, the read returns a normal task error naming both paths. Fixes pattern/glob traversal and NFT tracing so physical filesystem access uses resolved paths while logical paths remain available for user-visible specifiers and complete symlink-chain recovery. ### Why? Reading through an unresolved symlink parent gives the same filesystem object multiple path identities. That can make Turbo Tasks dependency tracking and invalidation inconsistent and can produce invalid deployment ZIPs when NFT output contains files below unresolved links. The checks return errors rather than asserting because paths can disagree temporarily under eventual consistency. Propagating a task error avoids panicking a worker thread while still exposing invalid callers during development. ### How? The validation lives directly in `DiskFileSystem::read` and `DiskFileSystem::raw_read_dir`. It calls the OS canonicalization API inline instead of the Turbo Tasks realpath task, keeping the diagnostic out of the task dependency graph. The guard runs only after the OS read succeeds, so missing/non-directory probes preserve their existing behavior. `read_matches` resolves each physical directory immediately before enumeration while retaining logical `PatternMatch` paths. `read_glob` and `track_glob` now resolve their initial directory before enumeration. Symlinks discovered later through wildcard segments are also traversed through resolved targets. `ReadGlobResult` deliberately retains logical paths rooted at the supplied base, allowing consumers to call `realpath_with_links` and recover the complete symlink chain. Consumers follow that contract explicitly: - NFT includes expand each logical match with `realpath_with_links`, emit resolved files and every traversed symlink, skip resolved directory targets, and deterministically deduplicate/sort output. - `import.meta.glob` uses recursive logical keys as the source of user-visible requests, while module resolution follows and tracks symlinks. - the hash-glob example resolves returned logical paths before reading. Webpack-loader context dependencies are covered for both `path/to/symlink/inner/path/*` and `path/to/*/inner/path/*`. The loader fixture performs its directory read with Node `fs`, reports the directory using `addContextDependency`, and Turbopack tracks the resolved target. ### Verification - `cargo fmt -p turbo-tasks-fs -p turbopack-ecmascript -p next-api -- --check` - `cargo clippy -p turbo-tasks-fs -p turbopack-ecmascript -p next-api --all-targets` - `cargo test -p turbo-tasks-fs` (128 passed) - `cargo test -p next-api` (7 passed) - `cargo check -p turbo-tasks-fs --examples` - `import.meta.glob` symlink execution fixture (1 passed) - Nine targeted node-file-trace CI cases with `release-with-assertions` (9 passed) - `pnpm build-all` - `webpack-loader-fs` Turbopack dev e2e (1 passed) - `build-trace-extra-entries-turbo` Turbopack production e2e (1 passed) - twoslash Turbopack production, normal mode (4 passed) - twoslash Turbopack production, cache-components mode (4 passed) - `bench/heavy-npm-deps` Turbopack development smoke test (HTTP 200) ### Notes The disk guard is cross-platform, while its symlink-parent regression test is Unix-only, matching neighbouring symlink tests. On Windows, OS canonicalization can also normalize casing and 8.3 short names; a debug read using a non-canonical spelling will therefore return the same diagnostic error. <!-- NEXT_JS_LLM --> <!-- fleet 1d32e12c-f4ec-4f22-862a-c85f0005805c --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
6f44fd34a1 |
Turbopack: enable export mangling by default in production builds (#97676)
Stacked on #97672 — enable export mangling by default on canary releases. ### What? Two defaults change: - **Next.js builds.** `experimental.turbopackMangleExportNames` is pinned to `false` for stable releases and left *unset* on canary, where Turbopack then defaults it from the build mode: on for production builds, off in development. Explicitly setting the option wins in either direction, so setting it to `true` in development is honoured. - **Turbopack's own execution test suite.** `TestOptions::mangle_export_names` defaults to `true`, so all 279 fixtures exercise mangling instead of only the handful that opt in. ### Why? The feature is verified by turbopack's own fixtures, targeted e2e suites, and a couple of bundle-size measurements — a narrow slice of what Next.js's test suites actually cover. Defaulting it on for canary puts every production-mode e2e and integration test through the mangled code path for real users of the canary channel, without committing stable users to it yet. Turning it on for stable is a separate, later decision that can stack on top of this once canary has soaked it. Broad exposure has already earned its keep several times over. Turning mangling on in CI and in the fixture suite surfaced bugs that no hand-picked suite had found, including a module-fragments helper handing out another module's exports value (#97672's `EcmascriptExports::borrowed()`), a client-reference proxy memoized per wrapper instance rather than per content, and a code-elimination bug for export-less modules (fixed in the follow-up PR). ### How? `defaultConfig` cannot see the build mode, so it only expresses the stable/canary split (`isStableBuild() ? false : undefined`) and `NextConfig::turbopack_mangle_export_names(mode)` supplies the mode-dependent default on the Turbopack side. This is deliberately *not* the shape of the neighbouring `turbopackSharedRuntime: !isStableBuild()`: mangling should not apply in development, and hard-forcing `false` there in Rust — as an earlier revision of this PR did — would silently ignore a user who asked for it explicitly. Mangling does **not** depend on minification. An earlier version of this PR gated it on `minify(mode)`, which was wrong and has been removed; `--no-mangling` is a minifier flag and does not affect export mangling. Verified with a real `next build` A/B on a small app: the option left unset mangles (959,174 B total emitted JS, 293,407 B gzipped) while explicitly setting it to `false` does not (966,519 B / 294,963 B gzipped), confirming both the default and the override. All 279 execution fixtures pass with the suite default flipped, with no fixture needing an opt-out. Note this layer does not change the `turbopack-emit-collect` snapshots on its own: making a module without re-exports split additionally requires the split trigger added in the follow-up PR, so those snapshots move there. This is the layer to revert if canary turns up problems specific to running with the option on by default; #97672 remains useful as an opt-in feature either way. <!-- NEXT_JS_LLM --> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> <!-- fleet ecdfa248-cd54-41ac-b4a2-c9d49e2a67ee --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> |
||
|
|
a84bc8de2a |
Turbopack: mangle exported names for smaller bundle sizes (#97672)
### What? Adds export-name mangling to Turbopack, behind a new experimental option `experimental.turbopackMangleExportNames` (default `false`). It is independent of minification: `--no-mangling` is a minifier flag and does not affect it. When enabled, each ECMAScript module's *used* export names — including `default` and `__esModule` — are replaced by short keys in the emitted output, both where the module registers its exports and where every consumer reads them. Modules whose export names could be observed by user code keep their original names, decided per module. This is a reland of #89060 (on top of the already-merged #89406), originally written by Matt Mastracci, who is credited as a co-author on the commit. Stacked on top: #97676 flips the default to `true` on canary releases, so Next.js's own CI exercises the feature broadly before it is considered for stable. ### Why? Bundle size. A module's export keys exist only to link modules together: the producing module emits `{ someVeryLongExportName: … }` and every consumer reads `ns["someVeryLongExportName"]`. Both sides are generated by us, so as long as producer and consumer agree — and the name isn't observable from user code — the key can be a single character. Long export names are extremely common in real dependency graphs (icon sets, utility packages, barrel files), and each one is paid for once in the module that defines it and once per importing module. ### How? **Ported, not rebased.** `canary` is ~2500 commits past the original stack's base, and the files it touched were independently rewritten in the meantime (export-analysis refactor #92781, CJS analysis for scope hoisting #95826, the `module_fragments` subsystem #95978). A probe rebase produced 16 conflicting files on the first commit alone, so the original branches were used as a reference implementation — for intent, the identifier alphabet, and test coverage — and the feature was rebuilt on today's infrastructure. #89561 from the original stack (erasing the Next.js wrapper module types) is deliberately **not** part of this change; it turned out to be unnecessary, because those modules already declare whole-module export usage and therefore back off on their own. **The name table** (`references/esm/mangle/table.rs`) hashes each name into a table of all valid JS identifiers of the smallest length that fits the name set — 15 exports get single-character keys — and resolves collisions by open addressing. Hashing rather than assigning `a`, `b`, `c`, … is what keeps names stable: an unrelated edit elsewhere in the module doesn't renumber every other export, and a collision only perturbs its own cluster. Assignment happens in two passes: every name that is *already* a valid identifier at the chosen length keeps itself and reserves its bucket first, and only then is anything hashed — so an export called `a` keeps `a`, and nothing else can be assigned it. Both passes iterate in sorted order, so the mapping depends only on the set of names. A module with exactly **one** mangleable export is special-cased to a fixed key, `f`, rather than a hashed one. `f` is the most common character in JS keywords (`if`, `for`, `function`), and every single-export module in the graph then emits the same `.f` / `.f()` byte sequences, which gzip's back-references pick up across the whole bundle — a bigger win than hashing, at the cost of that one key changing when a second export is added. A fixed list, `RESERVED_KEYS`, is withheld from every table for two different reasons: JS reserved words (`if`, `in`, `do`, `for`, `let`, `new`, `try`, `var`) are legal as quoted property keys but a minifier will not fold `ns["if"]` into the shorter `ns.if`, so handing one out costs bytes instead of saving them; and `__esModule` is withheld because the runtime's `esm()` helper defines that property on every module's exports object regardless of what the module itself exports, so an assigned key landing on it would collide. (`default` needs no such protection — once it is mangled like any other export, nothing else emits a property under that literal name.) **One source of truth for the mapping.** `mangled_export_names(module, chunking_context)` is a turbo-task that both the producing side (`EsmExports::code_generation`) and the consuming side (`ReferencedAssetIdent::Module`, the single place a cross-module export access is materialized) ask for the *target* module's map. Neither side computes a table of its own, so they cannot disagree, and the task derives export usage from the chunking context itself rather than accepting it as an argument, so a caller can't supply usage from the wrong graph. Re-export chains need no special handling, because the consumer side already resolves through re-exports to the module that produces the binding. The mangling decision itself lives on `EsmExports` as a `mangle_export_names: bool` field, rather than a separate trait method every module type has to override. A module that derives its exports from another one (a facade, a locals module, a part, a rename) inherits the flag with the data, which removed seven hand-written delegations and the possibility of a new wrapper type forgetting one. **A mangling decision must not cross module identities.** A few module types hand out *another* module's exports value as their own (the WASM loader module, the module-fragments side-effects wrapper, the client-reference proxy). If that borrowed value carried a real mangling decision, the producing and consuming sides would key their lookups on two different modules and could compute two different keys for the same export — this actually broke every WASM- and `@vercel/og`-based test once the default-on layer exercised it in CI. `EcmascriptExports::borrowed()` is the one place this is handled: it always returns an unmangled view, and every such pass-through site uses it. **Back-off is per module**, built on the export-usage information that landed after the original PR (`BindingUsageInfo` / `ModuleExportUsageInfo`) rather than the original's locals/facade-split heuristic. A module keeps its names when its usage is `All` (a namespace import that couldn't be lowered, a computed property access, an unresolvable `export *`, or a chunk-group entry — which covers client references and the Next.js wrapper modules), when it is read through a namespace value at all, when its exports are dynamic or not statically known ESM, or when names aren't being mangled in this build. `__webpack_exports_info__` gains `canMangle` and `mangledName` per export, which is how a running test can observe the mapping; with the option off it emits exactly what it emitted before. ### Testing - `turbo-tasks-hash`/table unit tests: encode/decode round-trip, degenerate-name rejection, table sizing, the single-export fixed key (including its own reservation), reserved-word withholding (including a reserved bucket-count test that stays in sync with the reserved list), the preserved-name pass running before any hashing, uniqueness under heavy collision, order independence, wrap-around probing, and same-tier stability. - 13 `turbopack-tests` execution fixtures under `tests/execution/turbopack/exports/mangle-*`, several ported from the original PR and from webpack's `test/configCases/mangle`: named imports, re-export chains and default exports (including one literally named `__esModule`), escaping namespaces (`Object.keys`, `delete ns.missing`, `export * as`, CJS interop), destructuring, prototype-shadowing names (`toString`, `$1`, `__1`), a 60-export two-character table, dynamic `import()` with `webpackExports` / `turbopackExports`, a CommonJS consumer of an ESM module, dynamic re-exports, scope hoisting on and off, and a control with the option off. - 3 committed snapshot fixtures under `tests/snapshot/mangle-exports`, so the emitted keys, the back-off, and the fixed single-export key are visible in review. - One fixture under `__skipped__`, which the harness asserts *fails*, recording the namespace-materialization gap below. - Full suite: 546 unit + 272 execution + 125 snapshot tests pass, with no snapshot churn across the several refactors this PR went through in review. - Verified against real builds: targeted app-dir, worker, WASM, and `@vercel/og`-based e2e suites pass with the option forced on (the failures that remain are external-network tests that fail identically with it off), and a small two-page app shrank by 0.76% of total emitted JS / 0.53% gzipped. ### Known limitations, each intentional - **A module read through `import * as ns` is never mangled**, even when every read is statically tracked, because the analysis doesn't yet distinguish a lowered named read from a materialized namespace object. Namespace imports are common, so this leaves real wins on the table; unlocking it is the highest-value follow-up. - **Escaping namespaces back off entirely.** Webpack instead keeps mangling and materializes a namespace object keyed by the original names. That is the `__skipped__` fixture: implementing it turns the suite red until the fixture is moved out. - **CommonJS export mangling is out of scope.** The producing side is reachable, but the consuming accesses live in user source and nothing rewrites them today; it needs its own design pass. Closes PACK-435 <!-- NEXT_JS_LLM --> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> <!-- fleet ecdfa248-cd54-41ac-b4a2-c9d49e2a67ee --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> |
||
|
|
1f52cb42ad |
Turbopack: shorten CSS module class names (#97944)
The class names were unnecessarily long. Use the same as the lightningcss default: `[hash]_[local]` which is `<hash of the full file path>_<original class name or identifier>` Keep the previous longer mechanism to aid in debugging in dev Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> |
||
|
|
6d6228c0c6 |
Prune incomplete parallel route matchers (#97108)
## Summary
This adds an experimental `strictRouteMatching` flag that leaves a
matcher out when its finalized loader tree contains a synthesized
default that will always call `notFound()` for a slot actually declared
by the owning layout. Adding a `default.tsx` keeps the matcher, and
structural router-state branches that are not real slots of that layout
do not make it incomplete. The flag defaults to `false` so matcher
pruning can roll out independently from the preceding loader-tree
correction.
The main goal is to make `children` and named slots behave the same way.
If a URL cannot construct a complete route tree on its own, it should be
treated as an unmatched URL instead of sometimes showing a slot 404 and
sometimes producing a missing page or default error.
## Semantics
For example:
```text
app/split-matcher/
├── layout.tsx
├── foo/page.tsx
├── bar/page.tsx
└── @slot/[...parts]/page.tsx
```
With strict route matching disabled, Next.js emits matchers for
`/split-matcher/foo`, `/split-matcher/bar`, and the broad
`/split-matcher/[...parts]`. The broad matcher can match any URL, but
its `children` branch has neither a matching page nor a default, so it
can only construct a permanent 404 tree. With strict route matching
enabled, that broad matcher is omitted while `/foo` and `/bar` remain
because each combines a real `children` page with the named-slot
catchall. Adding `app/split-matcher/default.tsx` deliberately makes the
broad matcher complete and keeps it.
The same rule applies without a catchall:
```text
app/disagreeing-slots/
├── layout.tsx
├── @first/foo/page.tsx
└── @second/bar/page.tsx
```
`/foo` is incomplete because `@second` has no matching page or default,
and `/bar` is incomplete for the corresponding reason in `@first`, so
neither matcher is emitted. A later PR in this stack reports the
now-unreachable page files as a project misconfiguration.
A declared `children` route is treated like any named slot:
```text
app/declared-children/
├── layout.tsx
├── page.tsx
└── @panel/
├── default.tsx
└── details/page.tsx
```
`/declared-children` is complete because `children` uses `page.tsx` and
`@panel` uses its default. `/declared-children/details` is incomplete
because `@panel` matches its page while the declared `children` slot has
no matching page or default at `/details`, so that matcher is pruned.
A route composed entirely from declared named slots is also complete:
```text
app/named-only/
├── layout.tsx
├── @left/[...slug]/page.tsx
└── @right/[...slug]/page.tsx
```
The preceding PR's default-on `explicitParallelRouteChildren` behavior
means this loader tree contains only `left` and `right`. Strict matching
keeps `/named-only/[...slug]` because both declared slots match; it does
not invent `children` and then prune the route for failing to satisfy
that nonexistent slot.
We got here incrementally.
[#47872](https://github.com/vercel/next.js/pull/47872) introduced the
404 fallback for unmatched parallel slots,
[#60186](https://github.com/vercel/next.js/pull/60186) added a
development warning because this was confusing in practice and linked
[#51805](https://github.com/vercel/next.js/issues/51805) and
[#49569](https://github.com/vercel/next.js/issues/49569), and
[#84702](https://github.com/vercel/next.js/pull/84702) later made a
missing default a build error for named slots while leaving `children`
on the old fallback for backwards compatibility. Strict matching takes
the next step and treats a matcher that can only construct a permanent
404 tree as unmatched.
This changes soft navigations that only worked by preserving a
previously active slot even though the URL could not be loaded directly,
which is why matcher pruning remains behind its own experimental flag.
The interception retention markers backed by `default-null`, including
named host slots from the first PR in this stack, are complete route
patches and are not pruned. This change however is well motivated
because if you did client nav to a route that only matches a named slot
and then hard refresh you will end up getting a 404. This is a sign our
current semantics are actually broken.
If you want to preserve the perma 404 behavior of the
slot-without-default you can just add a default and make it call
`notFound()` unconditionally.
## Verification
- `pnpm build-all`
- Focused `normalize-catchall-routes` unit coverage
- Production and development e2e coverage on Turbopack and Webpack
- The pruning e2e matrix with Cache Components enabled
<!-- NEXT_JS_LLM -->
|
||
|
|
05ed7c1be2 |
Omit undeclared children slots from app routes (#97184)
## Summary Parallel route layouts can be composed entirely from named slots, but loader tree construction currently synthesizes a `children` fallback whenever any named slot exists. This makes `children` semantically required even when no page, default, or ordinary route branch declares it. This adds `experimental.explicitParallelRouteChildren` and enables it by default. When enabled, `children` is included in a layout slot set only when the filesystem declares an ordinary route that can render at that level. A layout by itself is only structure and does not declare a route target. Ordinary descendants are traced until they reach a page or default, including through deeper named slots, before they cause `children` to be included. Setting the flag to `false` temporarily restores the legacy implicit `children` fallback. This flag only controls whether `children` exists in the loader tree. It does not prune incomplete matchers; that is the separate `experimental.strictRouteMatching` behavior in the next PR. Named slots keep their existing default and soft navigation semantics. The preceding PR retains the slots owned by an interception host without treating `children` specially, so an undeclared child is no longer needed for that behavior. A real `children` branch still uses the retention marker when it is one of the host layout slots. ## Semantics For example, this layout declares only named slots: ```text app/dashboard/layout.tsx app/dashboard/@left/page.tsx app/dashboard/@right/page.tsx ``` With `explicitParallelRouteChildren` disabled, Next.js adds a synthetic `children` branch whose built-in default calls `notFound()`, even though the layout never declared or rendered it. With the default behavior enabled, the loader tree contains only `left` and `right`, so `/dashboard` is matched from the route targets that actually exist. This is different from an ordinary branch whose route targets are deeper in the tree: ```text app/nested/layout.tsx app/nested/@sidebar/[...slug]/page.tsx app/nested/content/layout.tsx app/nested/content/@left/[...slug]/page.tsx app/nested/content/@right/[...slug]/page.tsx ``` Here `content` really is the `children` branch of `nested`. The scan follows `content` through its layout and deeper named slots, so `children` remains required. `/nested/content/anything` can construct every declared slot, while `/nested/incomplete` only matches `sidebar` and is still incomplete. The distinction is whether the ordinary descendant eventually reaches a page or default, not whether a layout happens to exist along the way. The focused children detection coverage proves that a layout-only descendant does not synthesize `children`, while an ordinary branch whose route targets live inside deeper named slots still does. The limitation coverage also proves that named-only trees render pages, CSS, metadata, and regular error boundaries. It intentionally asserts the current broken behavior for HTTP access fallbacks and metadata or viewport failures so those expectations can be flipped when renderer ownership no longer depends on `children`. ## Verification - `pnpm build-all` - Turbopack and webpack development and production coverage for `explicit-parallel-route-children-detection` - Turbopack and webpack production coverage for `interception-dynamic-segment`, `parallel-routes-layouts`, and `explicit-parallel-route-children-legacy` - The same existing production coverage with Cache Components enabled - Turbopack and webpack production coverage for the documented named-only limitations <!-- NEXT_JS_LLM --> |
||
|
|
cce7bf2fc0 |
Retain interception route host slots (#97242)
## Summary
Interception routes represent a partial update to the layout that hosts
them. Today we model this for `children` by synthesizing a `__DEFAULT__`
route backed by `default-null`, but named siblings still use normal
default matching even though they should retain their active state too.
This makes retention relative to the interception host instead of the
`children` key. Every non-intercepting sibling gets the existing
`__DEFAULT__` marker backed by `default-null`, while slots inside the
newly selected interception subtree continue to use normal matching and
real defaults.
The coverage uses a named-only host to verify that named siblings retain
client state without evaluating a user default, that a sibling without a
default also retains, and that a hard refresh still loads the canonical
route.
## Example
Consider this route tree:
```text
app/
├── named-host/
│ ├── layout.tsx # renders canonical, content, secondary, and modal
│ ├── @canonical/
│ │ └── page.tsx
│ ├── @content/
│ │ ├── page.tsx # renders a stateful counter
│ │ └── default.tsx # throws if evaluated
│ ├── @secondary/
│ │ └── page.tsx # has no default.tsx
│ └── @modal/
│ ├── default.tsx
│ └── (..)named-target/
│ └── page.tsx # intercepted target
└── named-target/
└── page.tsx # canonical route
```
Before this change, a soft navigation from `/named-host` to
`/named-target` could replace `@content` with its throwing default and
treat `@secondary` as missing because only `children` had retain
semantics. After this change, only `@modal` switches to the intercepted
page; `@content` and `@secondary` keep their existing UI and the counter
keeps its state. A hard refresh still renders the canonical
`/named-target` route instead of the interception host.
## Verification
- Turbopack and webpack production coverage in
`interception-dynamic-segment`
- The same production coverage with Cache Components enabled
<!-- NEXT_JS_LLM -->
|
||
|
|
286fcc346f |
Turbopack: call loadActionManifest for app-route (#97921)
For #95233, I need to read server-reference-manifest.json even for App Routes, which previously didn't emit server action manifest entries at all (because thus far, it was unnecessary). |
||
|
|
ae622d6ce9 |
Migrate from box_patterns to deref_patterns (#97924)
TLDR: just enable `deref_patterns` and remove the `box` syntax. And it just works `box_patterns` will be removed entirely in the next Rust nightly: https://github.com/rust-lang/rust/pull/156749 |
||
|
|
43d81b9d55 |
feat(turbopack): resolve /-rooted imports from the project directory (#97799)
### What?
A request starting with `/` — `import '/content/where'`,
`require('/foo.js')` — now resolves from
the **project directory** (the one holding `next.config`), and cannot
reach outside it. Previously
it resolved from the root of the filesystem while reporting *"server
relative imports are not
implemented yet"*.
This is the base of a stack: the follow-up on top of it moves
`import.meta.glob`'s `/`-rooted
patterns onto the same root, so a pattern and a plain import agree on
what `/` means.
### Why?
The feature was half-present and mislabelled. The
`Request::ServerRelative` arm rewrote `/foo` to
`./foo` and resolved it against the filesystem root, then emitted a "not
implemented yet" issue
unless an import-map alias had already matched. Users got a diagnostic
telling them to give up on
something that in fact resolved.
Worse, resolving from the *filesystem* root means `/` escapes the
project. In a Turbopack execution
test — where the filesystem root is the repository root —
`require('/package.json')` resolved this
repository's own `package.json`. In a monorepo, where Turbopack's root
is the workspace root and the
app lives in `apps/web`, `/content/x.js` would reach a workspace-level
file rather than the app's.
The intended semantics are Vite's, and they were confirmed by
measurement rather than from memory.
In a workspace where `content/*.js` exists *both* at the workspace root
and under the Vite root, so
the two are distinguishable, Vite 5.4.21:
- resolves `/content/…` from the configured `root` — changing `root`
changes which file wins;
- fails outright for a file that exists only above `root`, rather than
walking up;
- behaves identically for a plain `import` and for `import.meta.glob`.
### How?
`ResolveOptions` gains `server_relative_root`, the directory a
`/`-rooted request resolves from, and
`ResolveOptionsContext` exposes it so embedders can set it. Next.js sets
it to the project directory
in the client, server and edge resolve contexts — all of which already
had it to hand, so nothing new
is threaded through.
Three deliberate choices:
- **The option is optional and defaults to the previous behaviour.**
`turbopack-core` is used well
beyond Next.js; an embedder that doesn't set it keeps resolving `/` from
the filesystem root. This
is also why the change is provably contained — the whole
`turbopack-tests` suite passes with no
snapshot churn.
- **It is named and documented in filesystem terms**, tied to the
existing `Request::ServerRelative`
vocabulary, rather than introducing a "project root" concept into
`turbopack-core`, which must stay
free of Next.js concepts.
- **There is no fallback.** Resolution happens in the configured root
and stops; a request that isn't
there is a normal module-not-found. A fallback to the filesystem root
would reintroduce exactly the
escape this fixes.
The "not implemented yet" issue is removed. The equivalent issue for
Windows-style absolute requests
is untouched, as is the separate tsconfig `extends` handling, which
treats a rooted path as absolute
per TypeScript's own rule.
### Testing
The Turbopack execution harness already has two distinct roots — the
repository root as the
filesystem root, and each test's own directory — so pointing the new
option at the test directory
makes "project directory" and "filesystem root" genuinely different, and
the distinction testable
without a Next.js app. Two fixtures: one where a `/`-rooted import and
require resolve from the
project directory, and one asserting that `/package.json`, which exists
only at the filesystem root,
does *not* resolve. The former emits no issues at all, which is what
pins the removal of the
diagnostic.
The end-to-end case uses the existing monorepo fixture, where the
project directory really is not the
filesystem root. A file of the same name exists in both
`apps/web/content` and the workspace root, so
the rendered value alone identifies which root was used — a test that
merely asserted "it renders"
would have passed under the old behaviour too.
### Known gap
TypeScript resolves a leading `/` as an absolute path on disk, so a
`/`-rooted import in a `.ts` file
still fails type checking (`TS2307`) even though it bundles and runs.
The e2e page is therefore
JavaScript. A tsconfig `paths` mapping works around it for users, but it
also makes the request match
the import map instead, so the two resolvers overlap; that deserves its
own decision and is tracked
separately rather than papered over here.
<!-- NEXT_JS_LLM -->
<!-- fleet 3acc7e63-52a8-4472-89cd-b1bbb8ad59fa -->
---------
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
|
||
|
|
d79b0d09cb |
fix(wasm): don't enable SWC's plugin host for wasm targets (#97859)
> Replaces #97788, which was **not merged**. Reordering this stack briefly left that PR > pointing at a base branch that had come to contain its own head commit, so GitHub closed it > as merged and deleted its branch. Nothing from it reached `canary`. It had been approved; > this PR is the same commit (`142f8882a2`), restored, and needs review again. Sorry for the churn. ### What? Stop enabling SWC's plugin-host Cargo features when building for wasm. ### Why? `turbopack-ecmascript-plugins` and `next-napi-bindings` both requested `swc_core`'s `__plugin_transform_host` unconditionally, and that feature includes **`swc/plugin`** directly. So every wasm build pulled in SWC's plugin runner and compiled SWC's `target_arch = "wasm32"` plugin path — code that cannot run there. SWC does not support executing wasm plugins from inside wasm (see swc-project/swc#3934; its own wasm binding stubs the equivalent feature out to `[]`), and hosting them needs the wasmtime backend, which was already gated to non-wasm. The plugin rule on the Next.js side is native-only too. Only the Cargo features were never gated to match the code boundary that already existed. This is what lets the rest of the stack build for wasm **without patching SWC**: the wasm32 plugin arm that does not compile is simply never built. It also removes a portability hazard beyond that compile error — `swc/plugin` brings in tokio's multi-thread runtime, which wasi accepts only on the threaded target; plain `wasm32-wasip1` rejects those tokio features outright. `plugin_transform_host_native_filesystem_cache` moves with them: it is a *native* filesystem cache and cannot apply on wasm. ### How? Both crates keep their shared AST/transform features and move the plugin-host features into their existing non-wasm dependency blocks. Verified on the resolved feature graph rather than by reading manifests, because feature unification can make a manifest look right while the graph is wrong: - `cargo tree -p next-napi-bindings --target wasm32-wasip1-threads -e features` — no `swc feature "plugin"`, no `swc_core feature "__plugin_transform_host"`; - the same query for the host target lists all six plugin-related features, byte-identical to before. <!-- fleet b6d0486f-97c7-42a7-bdaf-3490774cdec3 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
319cdf2bc1 |
fix(turbopack-node): make process_pool inert on wasm (#97858)
> Replaces #97584, which was **not merged**. Reordering this stack briefly left that PR > pointing at a base branch that had come to contain its own head commit, so GitHub closed it > as merged and deleted its branch. Nothing from it reached `canary`. It had been approved; > this PR is the same commit (`fb0b97fd84`), restored, and needs review again. Sorry for the churn. ### What? `turbopack-node`'s `process_pool` feature is inert on wasm, leaving `worker_pool` as the only Node backend there. ### Why? The child-process pool needs `tokio::process` and a TCP listener, neither of which exists on wasi: ``` error[E0432]: unresolved import `tokio::process` # gated #[cfg(not(target_os = "wasi"))] in tokio error[E0599]: no `TcpListener::bind` on wasi --> turbopack/crates/turbopack-node/src/process_pool/mod.rs:315 ``` Turning the feature off from the outside is not possible: `process_pool` is a **default** feature of both `turbopack-node` *and* `next-core`, so `--no-default-features` at the top level does not suppress it. `worker_pool` — Node worker threads over napi — is already a first-class alternative selected by `TurbopackPluginRuntimeStrategy`, so no new mechanism is needed. ### How? Gate the seven `process_pool` sites on `not(target_family = "wasm")`: the module, the sealed backend impl, the constructor, the config enum variant, the default-strategy selection, and the `next-api` import and match arm. Host feature semantics are unchanged. <!-- NEXT_JS_LLM --> <!-- fleet b6d0486f-97c7-42a7-bdaf-3490774cdec3 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
a3183c018b |
fix(turbopack): make the SWC wasm-plugin backend native-only (#97857)
> Replaces #97583, which was **not merged**. Reordering this stack briefly left that PR > pointing at a base branch that had come to contain its own head commit, so GitHub closed it > as merged and deleted its branch. Nothing from it reached `canary`. It had been approved; > this PR is the same commit (`7c99dff628`), restored, and needs review again. Sorry for the churn. ### What? Makes the SWC wasm-plugin backend native-only, and reports an **error** against `next.config` when `experimental.swcPlugins` is configured on a platform that cannot execute them. ### Why? `swc_plugin_backend_wasmtime` cannot be hosted inside wasm: there is no JIT, and `wasi-common` needs native filesystem APIs. SWC's own wasm32 path skips plugin transforms for the same reason, see swc-project/swc#3934. Silently dropping the transform is the worst outcome — the build succeeds and quietly produces different output than the config asked for. A warning is not much better: it still emits a bundle whose code was not transformed as configured, which is subtly wrong rather than obviously broken. So this reports an error instead. ### How? - `swc_plugin_backend_wasmtime` moves to a non-wasm dependency, and the plugin rule's implementation is split into native and wasm arms. - The wasm arm emits `UnsupportedSwcPluginsIssue` — `IssueStage::Unsupported`, `IssueSeverity::Error` — naming the configured plugins and pointing at the resolved `next.config` path (via `NextConfig::config_file_path`, as the babel and sass issues do), so the message points at the file the user has to edit. It fires once at config time and only when plugins are actually configured, so projects that don't use them are unaffected. Native behaviour is unchanged. <!-- fleet b6d0486f-97c7-42a7-bdaf-3490774cdec3 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
79b1300b80 |
fix(next-napi-bindings): detect the target, not the host, in build.rs (#97576)
### What? `crates/next-napi-bindings/build.rs` now decides whether to emit `-Wl,--warn-unresolved-symbols` from `CARGO_CFG_TARGET_OS` / `CARGO_CFG_TARGET_FAMILY` instead of from a `#[cfg(...)]` attribute. ### Why? `#[cfg(...)]` inside a build script is evaluated for the machine that **runs** the script — the host — not for the target being compiled. The existing `not(target_arch = "wasm32")` guard was therefore always true on a Linux host, so cross-compiling to wasm still passed the flag and the link died with: ``` rust-lld: error: unknown argument: -Wl,--warn-unresolved-symbols ``` This is a latent bug for any cross-compilation target, not only wasm. ### How? Cargo sets `CARGO_CFG_*` for the *target*, so the condition is read from the environment instead of from a compile-time attribute. Behaviour on native Linux is unchanged. <!-- NEXT_JS_LLM --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
ef85120648 |
Turbopack: Improve file existence error handling in realpath_with_links and in module resolution (#97717)
Fixes some regressions from
https://github.com/vercel/next.js/pull/97395.
Human summary of changes:
- Plainly return `NotFound` for `realpath_with_link` whenever any step
in the resolution algorithm fails, matching the behavior the libc uses.
- When probing in the module resolution algorithm, explicitly check for
`NotFound`.
- Fully remove the extremely non-standard and confusing behavior of
trying to return the last processed path from `realpath_with_links` if a
failure occurs.
- Modify `realpath` to return a `RealPathError` instead of just an
`anyhow::Error`, so that callers that care about the actual error kind
but not all of the intermediate links can still use it instead of
`realpath_with_links`.
- Rename `RealPathResultError` to `RealPathError`, and make it actually
implement `Display` so that it's easier to coerce to an `anyhow::Error`.
This requires including a tiny bit more data on this object, but in the
hot path it's just one extra clone of a `FileSystemPath`.
Human discussion:
https://vercel.slack.com/archives/C09R44U5HQW/p1787322213695299
AI slop summary of why #97395 caused a regression:
> The regression came from newly added missing-target detection in
|