Minimal reproduction for a bug where rolldown emits incorrect code for a const enum with string-valued alias member.
export const enum Theme {
Light = "Light",
Dark = "Dark",
Default = Theme.Light, // alias member
}
console.log(Theme.Light); // Expected: "Light", Actual: "Default"
console.log(Theme.Default); // Expected: "Light", Actual: "Light"# Vite 8 (rolldown) — broken
npm install
npm run build
node dist/main.js
# Theme.Light = Default ← WRONG
# Theme.Dark = Dark
# Theme.Default = Light
# Vite 7 (esbuild) — correct
npm install vite@7
npm run build
node dist/main.js
# Theme.Light = Light ← correct
# Theme.Dark = Dark
# Theme.Default = LightRolldown emits a numeric-style reverse mapping for the alias member:
var e = function(e) {
return e.Light = "Light",
e.Dark = "Dark",
e[e.Default = e.Light] = "Default", // reverse mapping!
e
}({});Step by step:
e.Light = "Light"→{ Light: "Light" }e.Dark = "Dark"→{ Light: "Light", Dark: "Dark" }e.Default = e.Light→e.Default = "Light"e["Light"] = "Default"→ overwritese.Light!
Result: { Light: "Default", Dark: "Dark", Default: "Light" }
Rolldown treats the alias member Default = Theme.Light as a numeric enum member and emits a reverse mapping (e[value] = key). TypeScript never generates reverse mappings for string enum members — only for numeric ones.
esbuild (Vite 7) handles this correctly by resolving the alias to the literal value:
var Theme = (O => (
O.Light = "Light",
O.Dark = "Dark",
O.Default = "Light", // resolved to literal
O
))(Theme || {});Use the literal value instead of an alias member:
export const enum Theme {
Light = "Light",
Dark = "Dark",
Default = "Light", // literal instead of Theme.Light
}- vite: 8.0.1
- rolldown: bundled with vite 8
- typescript: 5.9.3
- node: v22.x