-
Notifications
You must be signed in to change notification settings - Fork 737
Description
Reproduction link or steps
// state.js
export const app = { user: null };
// main.js
import * as h from './state.js';
console.log(h.app?.user?.name ?? 'ok');Bundle with: rolldown main.js --dir dist
What is expected?
The optional chaining should be preserved:
console.log({ user: null }?.user?.name ?? "ok");
// or after inlining the object:
// the ?.user access should still be guardedThe expression should safely evaluate to "ok".
What is actually happening?
The output is:
console.log({ user: null }.user.name ?? "ok");All ?. operators are stripped, causing TypeError: Cannot read properties of null (reading 'name') at runtime.
Root cause analysis
The bug is in the module finalizer's handling of ChainExpression during namespace member expression rewriting:
1. crates/rolldown/src/module_finalizers/impl_visit_mut.rs (lines ~485-492)
ast::Expression::ChainExpression(chain_expr) => {
if let Some(new_expr) = chain_expr
.expression
.as_member_expression_mut()
.and_then(|expr| self.try_rewrite_member_expr(expr))
{
*expr = new_expr; // replaces entire ChainExpression, dropping ?. semantics
}
}When a ChainExpression wraps a member expression that gets rewritten (namespace import resolution), the result replaces the entire ChainExpression — dropping the optional chaining wrapper.
2. crates/rolldown_ecmascript_utils/src/ast_snippet.rs (member_expr_or_ident_ref)
ast::Expression::from(self.builder.member_expression_static(
SPAN, cur, self.id_name(name, *related_span),
false, // always non-optional
))member_expr_or_ident_ref always creates member expressions with optional: false, never preserving the original optional flags.
Impact
This is a correctness bug that silently changes runtime behavior. It affects any code using optional chaining on namespace imports or their resolved bindings. Reported in vitejs/vite#21862 as a Vite 8 production build issue.
Any additional comments?
Tested with rolldown v1.0.0-rc.9. The oxc parser, minifier, transformer, and codegen all handle optional chaining correctly in isolation — this is purely a rolldown module finalizer issue.
Metadata
Metadata
Assignees
Labels
Type
Fields
Give feedbackPriority