Minimal reproduction for an SWC minifier bug where a module-scoped
const object literal used as a default parameter value is incorrectly
reduced to {} when compress.passes >= 2. This breaks the runtime
behavior of any code that reads properties from that default value
(e.g. spec.granularities.sort(...) throws
TypeError: Cannot read properties of undefined (reading 'sort')).
@swc/core@1.15.33(latest at filing time)@rspack/core@1.7.11(which bundles its own SWC; same bug)- Also confirmed on
@swc/core@1.13.3(the version Rspack 1.7.11 bundles)
npm install
npx rspack build
# Check for the unique string from the dropped literal.
# Expected: > 0. Actual: 0.
grep -c "\\[Q\\]Q" dist/bundle.min.js
grep -c "granularities:\\[" dist/bundle.min.jsIf you open dist/bundle.min.js and search for [Q]Q 'YY or
granularities:[, neither string is present — the entire
defaultDateBucketSpec object literal has been reduced to {}.
We were unable to reproduce the bug with handcrafted minimal snippets that
have the same shape (const literal + default param + single inlinable call
site). Empirically, the bug requires a non-trivial module graph. Specifically:
| Test variant | Reproduces? |
|---|---|
| Just the TS code, no imports | no |
TS code + only lodash import (used inside the function) |
no |
TS code + only moment import (used inside the function) |
no |
| TS code + only two local module imports | no |
TS code + one local module + lodash |
no |
TS code + one local module + moment |
no |
TS code + lodash + moment together (this repro) |
yes |
So the trigger appears to involve some interaction between the inliner / DCE on the 2nd compress pass and cross-module symbol references after module concatenation. The minimum bundled size we could shrink to (via delta-debugging the rspack-emitted bundle) is ~850 KB; we could not stub out any remaining module's body and keep the bug.
- Minifier config: set
compress.passes: 1.new rspack.SwcJsMinimizerRspackPlugin({ minimizerOptions: { compress: { passes: 1 } }, })
- Source-level: wrap the literal in a function factory so it is not
referenced only as a default-param value.
const getDefaultSpec = (): DateBucketSpec => ({ /* ... */ }); export const generateDateBuckets = ( a: Date, b: Date, spec: DateBucketSpec = getDefaultSpec(), ) => { /* ... */ };
Same input bundle, only varying compress.passes:
compress.passes |
mangle |
toplevel |
Result |
|---|---|---|---|
2 (rspack default) |
true |
true |
bug reproduces (literal dropped) |
1 |
true |
true |
fixed (literal preserved) |
The bug fingerprint is the absence of the unique [Q]Q 'YY string from
the minified output, which is only present inside the dropped literal.