es/minifier: toplevel false fails to preserve global var inside a block
Describe the bug
Preserver clears in_top_level for every block and uses that syntactic flag when preserving var declarators. A var in a script-level block belongs to the global function scope, so renaming it violates mangle.toplevel:false and removes its externally visible name.
Input code
if(true){var globallyVisible=1; console.log("inside")} console.log(globallyVisible)Config
Options used by the exact-source reproduction:
{
"compress": {
"defaults": false
},
"mangle": {
"toplevel": false
},
"module": false
}Link to the code that reproduces this issue
Portable input and settings in SWC Playground
SWC Info output
Source build of swc_ecma_minifier 61.0.5, commit 246e6156ca14eaa07243ac8d9aa994c91c95cf3d, macOS arm64, Rust 1.96.0-nightly (f5eca4fcf 2026-04-09), Node v20.20.2.
The observed checkout and freshly fetched authoritative origin/main are the same commit. Fetch: git fetch --no-tags origin main, 2026-09-09T03:15:52Z, repository swc-project/swc. Reproduction was performed in a separate checkout of that fetched commit. Thus the cause remains on the default branch, not only in an older release.
For minify cases, the source harness follows the repository example: parse, resolve bindings, convert Terser compressor options, call optimize, apply fixer, emit, and execute original/output separately. Script-only behavior uses Script parsing. The playground carries portable source/settings; its published version is not evidence that the release matches the pinned source build. No implementation change was applied.
Expected behavior
External observer prints 1; normal program stdout is inside then 1.
Actual behavior
if(true){var o=1;console.log("inside")}console.log(o);External observer prints undefined; the output declares o rather than the requested preserved global name.
Version
swc_ecma_minifier 61.0.5, 246e6156ca14eaa07243ac8d9aa994c91c95cf3d (also freshly fetched main).
Additional context
Responsible code: pass/mangle_names/preserver.rs:87.
Observe the externally visible binding using:
const vm = require("node:vm");
const context = {console: {log(){}}};
vm.runInNewContext(code, context);
console.log(context.globallyVisible);The original prints 1; the minified output prints undefined. Ordinary in-program stdout matches and therefore is insufficient to detect this option-contract failure.
Proposed Scope
Track whether a var binding belongs to script-global function scope independently of lexical block nesting. Preserve such names with toplevel:false; retain function-local mangling and explicit toplevel:true behavior.
Limit the change to this semantic boundary and its regression coverage. No API expansion, dependency update, migration, deployment, or feature-flag rollout is required. Preserve the documented minifier assumptions; the reproduction does not require overriding builtin implementations.
Acceptance Criteria and Test Scenarios
- Evaluate the primary original and output in separate Node vm contexts and require context.globallyVisible === 1 for both.
- A direct top-level var remains preserved.
- An equivalent var inside a function remains eligible for local mangling.
- With toplevel:true global names remain eligible.
- Retain at least two statements in the block so prior single-statement flattening cannot hide the regression.
- Add regression coverage in the existing SWC-owned fixture/execution suites under
crates/swc_ecma_minifier/tests/fixture/issues, or the existingtests/eval.rsAPI harness for the Evaluator case. Check exact execution/return values and valid generated syntax, not only snapshots. - Initialize submodules, update fixture expectations only as needed, and rerun
cargo test -p swc_ecma_minifierwithout UPDATE. Preserve passing controls above. Keep regression comments in English.
Duplicate search
No same-root open duplicate was found in bounded searches including "mangle" "toplevel" "block" and related minifier terms. Similar issues were evaluated by root cause and transformation boundary.
Out of Scope
Unrelated rewrites, new user options, implementing other audit findings, and changing semantic assumptions.
This is a message for readers, not the author of this issue.
Please read no +1 before leaving a comment.
Source: swc-project/swc