[pull] canary from vercel:canary - #1269
Merged
Merged
Conversation
### What? Preserve write failures from streaming SST construction instead of replacing them with debug-only writer lifecycle assertions. This adds explicit cancellation, immediate best-effort cleanup of incomplete SSTs, and focused coverage for failed writes and finalization. ### Why? `std::thread::panicking()` is false while an error is propagated through `Result`. On disk-full and similar failures, writers and their owning collectors are therefore dropped before a caller begins panic unwinding, allowing lifecycle assertions to obscure the original I/O error. Leaving partial files until startup recovery also delays reclaiming scarce disk space. ### How? `StreamingSstWriter::cancel()` consumes an incomplete writer, discards buffered bytes without flushing or finalizing, closes the raw handle, and best-effort removes the partial SST. Failed close attempts and unfinished Drop paths use the same cleanup, while deletion failures are ignored so they cannot replace the root error; existing startup recovery remains the fallback. Writer-owning scopes preserve the original error, explicitly cancel their writers, and then return that error. The compaction merge scope cancels both collectors on every error exit so a sibling collector cannot mask the failure. Successful close marks the finalized SST for preservation, and healthy writers still have to call `close()` or `cancel()` to satisfy the debug lifecycle invariant. The regression tests substitute an unbuffered read-only output handle to force deterministic add and close failures without requiring disk pressure. ### Verification - `cargo test -p turbo-persistence` (114 passed) - `cargo check --release -p turbo-persistence` - `cargo fmt --package turbo-persistence -- --check` <!-- NEXT_JS_LLM --> <!-- fleet da0c4e98-b86a-4c93-a519-8a63147e3f5c --> --------- 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>
> [!TIP] > Recommended to review commit by commit. ### Why? Support `canary` releases for agentic upgrade so the workflow is available for the dogfooders. Startup reminders also need to check target eligibility before offering an upgrade that the configured policy cannot perform. ### How? Enable latest and Future upgrades on the canary channel while keeping stable installations on stable releases. Security upgrades and advisory checks remain stable-only; canary version ordering is not used to infer security fixes. Share policy-aware advisory and target assessment between startup reminders and explicit upgrades, retaining security warnings when no eligible target is available and offering bare `--ai` only when the configured policy can proceed. Explicit stable `latest` upgrades also check target advisories, after treating strictly older targets as a no-op; equal affected targets remain blocked. Canary latest and Future upgrades skip advisory assessment. Version reminders keep their major/minor threshold, and Future Defaults retain their full-version availability checks. Assessment caching is left as a TODO. The agentic upgrade guide remains a bundled, agent-consumed workflow marked `version: draft`; public-facing documentation will be handled separately.
…3331) ### What? Adds support for `false` as a value in `experimental.turbopack.resolveAlias`, which resolves the aliased module to an empty stub — matching webpack's long-standing behavior. ```js // next.config.js module.exports = { experimental: { turbopack: { resolveAlias: { 'some-server-only-module': false, }, }, }, } ``` With this alias in place: - `import * as ns from 'some-server-only-module'` → `ns` is `{}` - `import { foo } from 'some-server-only-module'` → `foo` is `undefined` - `import def from 'some-server-only-module'` → `def` is `undefined` - `await import('some-server-only-module')` → resolves to `{}` - `require('some-server-only-module')` → returns `{}` ### Why? Webpack supports `false` as an alias target to stub out modules (e.g. to exclude Node-only packages from client/edge bundles). Turbopack did not, causing migration friction for projects that rely on this pattern. ### How? Threads the `false` sentinel through the full resolution pipeline as a new `Empty` variant: ``` false (config) → SubpathValue::Empty (next-core import map) → ImportMapping::Empty (turbopack-core remap) → ReplacedImportMapping::Empty → ResolveResultItem::Empty → ModuleResolveResultItem::Empty → ReferencedAsset::Empty (ESM static imports) → SinglePatternMapping::Ignored (CJS require / dynamic import) ``` For static ESM imports, `ReferencedAsset::Empty` is kept as a distinct variant so that `binding.rs` can distinguish a namespace import (yields `{}`) from a named/default import (yields `undefined`). For CJS `require()` and dynamic `import()`, `ModuleResolveResultItem::Empty` maps to the existing `SinglePatternMapping::Ignored`, which already generates the correct `{}` / `Promise.resolve({})` stubs — so no new code-generation path was needed there (the redundant `Empty` variant introduced during development was removed in a follow-up cleanup commit). **Files changed:** - `packages/next/src/server/config-shared.ts` — add `false` to `resolveAlias` type - `packages/next/src/server/config-schema.ts` — add `z.literal(false)` to schema - `crates/next-core/src/next_import_map.rs` — map `false` → `SubpathValue::Empty` - `turbopack/crates/turbopack-core/src/resolve/remap.rs` — handle `ImportMapping::Empty` - `turbopack/crates/turbopack-core/src/resolve/mod.rs` — propagate `ModuleResolveResultItem::Empty` - `turbopack/crates/turbopack-ecmascript/src/references/esm/base.rs` — `ReferencedAsset::Empty` - `turbopack/crates/turbopack-ecmascript/src/references/esm/binding.rs` — empty-module binding code-gen - `turbopack/crates/turbopack-ecmascript/src/references/esm/url.rs` — handle Empty in URL refs - `turbopack/crates/turbopack-ecmascript/src/references/async_module.rs` — skip Empty in async module analysis - `turbopack/crates/turbopack-ecmascript/src/references/pattern_mapping.rs` — map Empty → Ignored **Tests:** `test/e2e/turbopack-resolve-alias-false/` covers all five import styles (namespace, named, default, dynamic, CJS). --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
## Summary
Adds a compact Turbopack runtime path for modules whose exports are
entirely forwarded from other modules. It replaces one generated wrapper
getter per binding with a flat re-export description, while preserving
live-binding semantics and source evaluation order.
This revision also makes the optimization generic for synthetic
re-export facades, rather than leaving facade integration in the stacked
export-mangling PR:
- synthetic facades use `ExportRegistrationMode::Reexport`;
- their explicit reference list supplies ordering because they have no
source `ImportMap`;
- an evaluation-only locals reference can be represented as an empty
group;
- references whose import is performed by the compact registration do
not emit it twice;
- pruned references and merged evaluation-only references are omitted,
because they deliberately produce no namespace binding and may have no
module factory in the chunk;
- async modules and known export-cycle circuit breakers retain the
general registration path.
The runtime now inspects source property descriptors. Constant exports
reuse the source value, live exports reuse the exact
receiver-independent Turbopack namespace getter, and
dynamic/proxy/missing properties fall back to a wrapper getter.
Destination descriptors remain enumerable, non-configurable, read-only
ESM namespace properties; setters are never forwarded.
## Output examples
A re-export-only module can suppress its redundant import and use a
module-id group:
```js
// before
var ns = __turbopack_context__.i("a.js")
__turbopack_context__.s(["a", () => ns.a, "b", () => ns.b])
// after
__turbopack_context__.S(["a.js", "a,a,b,b"])
```
Two source modules become groups separated by `0`:
```js
__turbopack_context__.S([
"first.js", "a,a,b,b",
0,
"second.js", "c,c",
])
```
Encoding is selected independently per group. A comma-containing name
uses explicit pairs while another group can remain packed:
```js
__turbopack_context__.S([
"first.js", "b", "b", "has,comma", "has,comma",
0,
"second.js", "c,c",
])
```
A synthetic facade includes its evaluation-only locals part as an empty
group, then its forwarded bindings in reference order:
```js
__turbopack_context__.S([
"reexports.js <locals>",
0,
"x.js", "x,x",
0,
"y.js", "y,y",
])
```
Inside a scope-hoisted factory, imports are retained and existing
namespace objects are used as heads because another logical module in
the merged factory may read them:
```js
var locals = __turbopack_context__.i("reexports.js <locals>")
var x = __turbopack_context__.i("x.js")
__turbopack_context__.S([locals, 0, x, "x,x"], facadeId)
```
A merged evaluation-only locals reference is omitted from that list:
scope hoisting supplies only an in-factory ordering placeholder, not a
namespace variable that `S` could read.
## Runtime representation
For each forwarded property, `S` reads the source descriptor:
- data descriptor → capture the value with the runtime's value tag;
- getter descriptor → reuse that exact getter (Turbopack-generated
namespace getters are receiver-independent);
- no usable own descriptor → use `() => namespace[name]` as a defensive
fallback.
Tests pin constant primitives, frozen objects, function values, live
updates, exact getter identity, receiver independence, destination
descriptor flags, omitted setters, and the fallback path.
## Verification
- `cargo test -p turbopack-ecmascript -p turbopack-tests` — **590 unit,
302 execution, 3 auxiliary, 131 snapshot** tests passed on this layer
- Full stacked #97770 verification — **590 unit, 303 execution, 3
auxiliary, 131 snapshot** tests passed
- `cargo clippy -p turbopack-ecmascript -p turbopack-core -- -D
warnings`
- `cargo fmt --all -- --check`
- Runtime TypeScript check (`pnpm check` in
`turbopack-ecmascript-runtime/js`)
- `pnpm build-all`
- Dedicated coverage for ordinary import/re-export ordering, transformed
`DUMMY_SP` declarations, callable CommonJS namespaces, synthetic facade
ordering, scope-hoisted retained imports, pruned and merged evaluation
references, async fallback, and export-cycle circuit breakers
## Incremental size on top of #98932
Scratch comparison only, as requested: #98617 stayed based on `canary`.
Three apps were built with scope hoisting both disabled and enabled.
Client reports raw and deterministic gzip (`gzip -9 -n`, sorted JS
files); server reports raw only. Every row was repeated byte-for-byte.
| benchmark | scope hoisting | client raw | client gzip | server raw |
real `S` sites (client/server) |
| --- | --- | ---: | ---: | ---: | ---: |
| `basic-app` | off | +510 B | +214 B | **−6,114 B** | 0 / 56 |
| `basic-app` | on | +510 B | +164 B | +8,618 B | 0 / 0 |
| `heavy-npm-deps` *(lodash + Mantine + Mermaid)* | off | **−2,268 B** |
**−432 B** | +4,680 B | 3 / 8 |
| `heavy-npm-deps` *(lodash + Mantine + Mermaid)* | on | +522 B | +191 B
| +8,630 B | 2 / 2 |
| `module-cost` | off | +510 B | +263 B | +7,432 B | 0 / 6 |
| `module-cost` | on | +510 B | +265 B | +8,624 B | 0 / 1 |
Aggregate raw movement across the six rows is **+32,164 B**. The shared
helper is only amortized in configurations with many eligible facade
calls; scope hoisting commonly resolves forwarded bindings to locals,
leaving few calls while every runtime copy still carries the helper.
Descriptor reuse does **not** change generated module code or call-site
counts. Compared with the same facade integration using wrapper getters,
it adds **155 B client raw** and **527 B per runtime copy** (**1,054 B
server raw** in these apps). Its purpose is avoiding per-binding wrapper
allocation / access indirection where a source value or getter already
exists, not reducing bundle bytes. The size result was reported before
continuing, and descriptor work proceeded by explicit maintainer
direction.
<!-- NEXT_JS_LLM -->
<!-- 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>
We had 3 modes, now we just have 2 boolean for all possible 2*2 = 4 modes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )