Latest Results
fix(es/minifier): deconflict IIFE-hoisted bindings under mangle
IIFE inlining hoists parameter (and body-level `var`) bindings into the
enclosing scope while reusing their original nested syntax context. When
the hoist target is the top level and a same-symbol binding already lives
there, the two bindings collide.
The non-mangle pipeline is saved by `hygiene()`, which renames the hoisted
binding (e.g. to `x1`). But `hygiene()` is skipped once mangling is enabled,
and the mangler cannot rename the hoisted binding: its context is a
descendant of the top-level mark, so the preserver keeps its name, and with
the default `top_level: false` the top level is never renamed. The result
was two top-level `x` declarations:
SyntaxError: Identifier 'x' has already been declared
(SWC's parser does not enforce lexical redeclaration, so the invalid output
was emitted silently; Node rejects it at load time.)
Fix: when mangling is enabled, remap each hoisted binding's identifier to a
fresh synthesized syntax context (a mark whose parent is the root, like
`private_ident!`). Such a context is not a descendant of the top-level mark,
so the mangler's existing "force rename synthesized names" path deconflicts
it exactly as `hygiene()` would. The remap is applied to the hoisted
declarators and to every reference (assignment LHS and body uses), which all
share the same original id.
The react-pdf-renderer fixture output improves as a side effect: the fresh
context unblocks a UMD-wrapper simplification the optimizer previously could
not perform.
Closes: https://github.com/swc-project/swc/issues/11977
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>lukesandberg:fix/issue-11977-iife-mangle-collision fix(react-compiler): move index.ts/binding.js to package root
Reported by an automated reviewer: the previous layout (index.ts under
src/, binding.js/binding.d.ts also under src/, tsc's rootDir "./src" +
outDir "./") had `tsc -d` (running before `napi build` in build:dev,
same order as CI) generate its OWN weak, type-inferred root
binding.d.ts from src/binding.js (a plain JS file with no real type
annotations -- roughly `export = nativeBinding; declare let
nativeBinding: any`), since tsc treats already-existing .d.ts files as
ambient declarations it never copies to outDir, and only emits
declarations for .js/.ts source files it actually compiles. napi
build's own --dts flag only ever wrote the real, richly-typed
declarations to src/binding.d.ts, never to the root file that actually
ships. So `export type { Diagnostic, ... } from './binding'` and
`lint`/`lintSync`'s `binding.Diagnostic[]` return types resolved fine
while developing (tsc checks src/index.ts against its real,
correctly-located sibling src/binding.d.ts) but were broken for any
real downstream consumer of the published package, who only gets the
root-level files. Confirmed by simulating a consumer against only the
final built root/index.d.ts + root/binding.d.ts (no access to a src/
directory): `Cannot find module '../binding'` before this fix, clean
after.
@swc/core avoids this because its binding.js/binding.d.ts live at the
package root already, not under src/ -- an initial attempt to copy
that exact pattern here (keep index.ts under src/, use a type-only
`typeof import("../binding")` + separate runtime `require("./binding")`
split, matching @swc/core's own src/index.ts) turned out to have the
same latent bug: tsc emits the *literal* import-specifier text into
the compiled .d.ts without rewriting it for the new location, so
`../binding` ends up wrong in the emitted root index.d.ts too, once a
public signature (unlike @swc/core's internal-only use of that
pattern) actually needs to resolve it. Verified this by re-running the
same consumer simulation against that version -- still broken.
The actual fix: index.ts now lives at the package root too. It was the
only real source file under src/ (binding.js/binding.d.ts are napi
build's output, not authored TS/JS) once those moved out for the same
reason, so the src/ subdirectory no longer serves any purpose --
having rootDir/outDir differ was the entire source of the bug, and
with only one file, there's no reason to keep that split. `./binding`
is now unambiguously correct everywhere: same directory, before and
after compilation, for the source file, the compiled JS's require
call, and the emitted .d.ts's import specifier alike. Re-verified the
consumer simulation passes clean, and the runtime test suite
(tests/lint.test.cjs) still passes 4/4 against the new layout.
@swc/minifier has the identical structural bug (binding.js/d.ts under
src/, minifySync/minify return binding.TransformOutput) and is
already shipping it in production -- confirmed separately, left as a
distinct out-of-scope issue for this PR.imjordanxd:feat/react-compiler-lint-diagnostics Latest Branches
0%
Chastrlove:fix-react-compiler-spans -2%
lukesandberg:fix/issue-11977-iife-mangle-collision 0%
imjordanxd:feat/react-compiler-lint-diagnostics © 2026 CodSpeed Technology