Skip to content

fix: Preserve module.exports assignments when converting to ESM - #210

Merged
bennycode merged 1 commit into
mainfrom
fix/module-exports-object-literal
Sep 29, 2026
Merged

bennycode merged 1 commit into
mainfrom
fix/module-exports-object-literal

Conversation

@bennycode

Copy link
Copy Markdown
Owner

Fixes #125

module.exports = <expr> only produced an ESM export when the value was an identifier or a function expression. For anything else the statement was removed without a replacement, so exports disappeared silently.

  • Object literals that only reference identifiers become named exports: module.exports = { c0, one: c1 } becomes export { c0, c1 as one };
  • Every other expression (object literals with values, arrow functions, classes, require calls) becomes export default <expr>, so no code is dropped.

While fixing this I found a related bug in the module.exports.foo = … path, which always emitted export { <right side> }:

  • module.exports.foo = bar exported bar instead of foo. It now emits export { bar as foo }.
  • Non-identifier values produced invalid code like export { 42 }. They now emit export const answer = 42;
  • When the name is a reserved word (class, default, …) or already used in the file, the value goes through a local alias: const _answer = …; export { _answer as answer };
  • Deeper paths like module.exports.a.b = 1 used to be flattened into export { 1 }. They are now left untouched.

The "already used" check is conservative. It treats any identifier with the same text in the file as taken, so some exports get an alias they don't strictly need. The output is valid either way.

Supersedes #166, which only handled identifier-only object literals.

Assigning anything other than an identifier or a function expression to
module.exports removed the statement without replacement, so exports
like `module.exports = { c0, c1 }` disappeared. Object literals that only
reference identifiers now become named exports, and every other
expression becomes a default export.

Named exports had a related bug: `module.exports.foo = bar` produced
`export { bar }`, dropping the name foo, and non-identifier values
produced invalid code like `export { 42 }`. Properties are now exported
under their own name, through a local const alias when the name is a
reserved word or already used in the file. Deeper paths like
`module.exports.a.b` are left untouched instead of being flattened.
@bennycode
bennycode merged commit 54191d9 into main Sep 29, 2026
2 checks passed
@bennycode
bennycode deleted the fix/module-exports-object-literal branch September 29, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

module.exports = { ... } are stripped instead of being transformed

1 participant