Skip to content

[pull] canary from vercel:canary - #1269

Merged
pull[bot] merged 7 commits into
javascript-for-kids:canaryfrom
vercel:canary
Sep 22, 2026
Merged

pull[bot] merged 7 commits into
javascript-for-kids:canaryfrom
vercel:canary

Conversation

@pull

@pull pull Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

See Commits and Changes for more details.


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

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

sokra and others added 7 commits September 22, 2026 09:41
### 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>
Assert the shape of those across ESM and CJS to prevent regressions in
the future
## 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
@pull pull Bot locked and limited conversation to collaborators Sep 22, 2026
@pull pull Bot added the ⤵️ pull label Sep 22, 2026
@pull
pull Bot merged commit 2f3e77f into javascript-for-kids:canary Sep 22, 2026
7 of 11 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants