Skip to content

Instantly share code, notes, and snippets.

@sfletche
Created May 19, 2026 20:21
Show Gist options
  • Select an option

  • Save sfletche/e26e0fbf70c6670c1ffcf929b4a2d4bf to your computer and use it in GitHub Desktop.

Select an option

Save sfletche/e26e0fbf70c6670c1ffcf929b4a2d4bf to your computer and use it in GitHub Desktop.
Repro: @swc/core compress.passes:2 drops module-scoped object literal used as default param to {} (rspack 1.7.11 / swc 1.15.33)

Repro: compress.passes: 2 drops module-scoped object literal to {}

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')).

Versions

  • @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)

Run

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.js

If 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 {}.

Why this repro looks "non-minimal"

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.

Workarounds

  1. Minifier config: set compress.passes: 1.
    new rspack.SwcJsMinimizerRspackPlugin({
      minimizerOptions: { compress: { passes: 1 } },
    })
  2. 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(),
    ) => { /* ... */ };

Bisection: minifier options

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.

import { min } from 'lodash';
import moment from 'moment';
type DateBucketSpec = {
maxBucketCount: number;
reverse: boolean;
granularities: {
maxDays: number;
unit: 'days' | 'weeks' | 'months' | 'quarters';
displayFormatString: string;
}[];
};
// Module-scoped const object literal -- this is what the minifier
// incorrectly reduces to `{}` under compress.passes: 2.
const defaultDateBucketSpec: DateBucketSpec = {
maxBucketCount: 7,
reverse: true,
granularities: [
{ maxDays: 7, unit: 'days', displayFormatString: 'MMM D' },
{ maxDays: 31, unit: 'weeks', displayFormatString: 'MMM D' },
{ maxDays: 120, unit: 'months', displayFormatString: "MMM 'YY" },
{ maxDays: Infinity, unit: 'quarters', displayFormatString: "[Q]Q 'YY" },
],
};
// Used as a default parameter value. After minification, `spec` will be
// `{}` (no `granularities` property), so the `.sort(...)` call throws
// "Cannot read properties of undefined (reading 'sort')" at runtime.
export const generateDateBuckets = (
inputStartDate: Date,
inputEndDate: Date,
spec: DateBucketSpec = defaultDateBucketSpec,
) => {
const startDate = moment.utc(inputStartDate);
const endDate = moment.utc(inputEndDate);
const dayCount = endDate.diff(startDate, 'days') + 1;
const granularityToUse = spec.granularities
.sort((a, b) => a.maxDays - b.maxDays)
.find((g) => dayCount <= g.maxDays);
return granularityToUse?.displayFormatString ?? '';
};
export const generateCumulativeBuckets = (a: Date, b: Date) => {
return generateDateBuckets(a, b) + String(min([1, 2]));
};
import { generateCumulativeBuckets } from './dateBuckets';
(window as any).__hsRepro = { generateCumulativeBuckets };
{
"name": "swc-compress-passes-default-param-repro",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "rspack build"
},
"devDependencies": {
"@rspack/cli": "1.7.11",
"@rspack/core": "1.7.11",
"@swc/core": "1.15.33",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"typescript": "^5.5.0"
}
}
const path = require('path');
const rspack = require('@rspack/core');
// Repro: SwcJsMinimizerRspackPlugin (which uses @swc/core under the hood)
// drops a module-scoped object literal to `{}` when it's used as a default
// parameter value. The bug requires compress.passes >= 2 (rspack's default
// is 2).
module.exports = {
mode: 'production',
context: __dirname,
entry: './index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.min.js',
clean: true,
},
resolve: {
extensions: ['.ts', '.js'],
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: { syntax: 'typescript' },
target: 'es2017',
},
},
},
},
],
},
optimization: {
minimizer: [
// Default options. Setting `compress: { passes: 1 }` here fixes the bug.
new rspack.SwcJsMinimizerRspackPlugin(),
],
},
stats: 'minimal',
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment