next.js
4bb6fc4b - [turbopack] Forward usage information across re-exports (#96282)

Commit
57 days ago
[turbopack] Forward usage information across re-exports (#96282) ## What? This PR improves tree-shaking across whole-namespace re-exports. It is the middle PR in the stack: #98680 provides the independently reviewable `EsmAssetReferenceOptions` constructor refactor requested during review, and #96396 builds on this PR to narrow static member reads on namespace-valued named exports. A representative case is a CommonJS re-export followed by an ESM star barrel: ```js // index.js const { m } = require('./cjs') // cjs.js module.exports = require('./barrel') // CJS re-export // barrel.js export * from './base' // ESM star barrel // base.js export const m = 1 export const n = 2 // nobody ever uses this ``` Only `m` is observed by the entrypoint, so that usage can now flow through both re-export layers to `base.js` and allow `n` to be removed. ## Why? Turbopack now has a generic `ExportUsage::Passthrough` mechanism, introduced on `canary` for client-component and Next.js module proxies. It carries the referencing module's accumulated used-export set into a transparent target. However, CommonJS whole-module forwarding and ordinary ESM star barrels do not opt into that mechanism. Applying this PR's tests to plain `canary` showed that all runtime values are already correct, but unused exports remain retained in four important shapes: - an ESM named consumer through `module.exports = require(ESM)`; - a deep CommonJS forwarding chain; - CommonJS forwarding into an ESM `export *` barrel; - a CommonJS diamond with disjoint used exports. This PR therefore remains a tree-shaking precision change rather than introducing a second forwarding model. There is also an independent name-observability concern. A CommonJS forwarder exposes a raw namespace object's properties under their original names, while named consumers of an ESM `export *` can be rewritten if an export is mangled. Namespace provenance must survive multiple passthrough hops without unnecessarily disabling mangling for ordinary ESM named forwarding. ## How? ### Configure forwarding at construction time #98680 replaces `EsmAssetReference`'s positional behavior arguments with `EsmAssetReferenceOptions`. This PR extends that options object with an optional passthrough mode. ESM star references receive `Some(false)` directly when constructed; free-variable references receive `None`; and the existing annotation-driven client-proxy path still contributes `Some(true)`. The two sources merge conservatively: an annotation that exposes the namespace's original names cannot be weakened by a syntax-driven `Some(false)` on the same reference. The former mutating `mark_namespace_reexport()` API is removed. ### Reuse the generic passthrough fixed point The implementation uses canary's existing model: ```rust ExportUsage::Passthrough { namespace_object_may_escape: bool, } ``` The boolean describes whether the edge itself exposes the target namespace's original property names: - `module.exports = require('…')` records `Passthrough { namespace_object_may_escape: true }` directly in the existing CommonJS import-usage map; - `export * from '…'` records `Passthrough { namespace_object_may_escape: false }`, because statically known ESM consumers can be rewritten when target exports are mangled. No parallel `TargetExportUsage` type, graph field, call parameter, or second fixed-point implementation is added. Detection is limited to a simple assignment using the unresolved/global `module` and `require` bindings. Shadowed bindings, member requires, and compound assignments retain their existing behavior. ### Propagate keys and namespace provenance independently A passthrough edge merges the parent module's resolved `Evaluation`, `Exports`, or `All` state into its target. The merge remains monotonic and idempotent, so chains, cycles, and diamonds converge. Namespace provenance is carried independently of the used key set: - an edge with `namespace_object_may_escape: true` makes the target's original names observable; - every passthrough also carries namespace provenance that already reached its parent; - a newly propagated provenance bit retriggers traversal even when the used key set did not change. This preserves original names through multi-hop namespace reads while keeping unrelated ordinary named exports mangleable. Passthrough references are also exposed correctly to scope-hoisted merged modules. ## Testing Enabled execution coverage includes: - deep CommonJS chains, CommonJS → ESM star barrels, cycles, diamonds, and overwrite behavior; - ESM named/default/namespace import interop through `module.exports = require(ESM)`; - opaque whole-namespace and evaluation-only consumption, including single evaluation; - native `export *`, default/local-shadow filtering, and the optimization-disabled mode; - namespace provenance across forwarding edges and a multi-hop diamond; - both mangling safety and non-pessimization for ordinary named forwarding; - shadowed `module`/`require`, member-require, and non-simple assignment boundaries; - runtime counterparts for every nested precision case that remains skipped. Known optimization-only limitations remain under `__skipped__/deep-reexports`: nested `export * as` through CJS, namespace-property unwrapping in a CJS assignment, depth-two nested namespaces, and unwrapping after a forwarding hop. Their runtime semantics are covered by the enabled `reexport-forwarding-nested-runtime` fixture. Verification on the rebased stack: - plain-canary reassessment: 7/9 fixtures, with only four expected precision failures; - integrated bottom layer: 9/9 forwarding fixtures; - passthrough unit tests: 2/2, plus the six-case construction-option merge lattice; - combined stack: 14/14 focused execution/snapshot tests and 60/60 analyzer graph tests; - affected Cargo checks, `next-core` check, `build-all`, and the forwarding/namespace HMR test pass. <!-- NEXT_JS_LLM --> <!-- fleet 993ef5d6-cc80-4082-8e14-ff9ac871ce33 --> 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>
Author
Parents
Loading