Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.refactron.dev/llms.txt

Use this file to discover all available pages before exploring further.

Transform ID: commonjs_to_esm Language: TypeScript

What it does

Rewrites the CommonJS module surface into ES modules:
  • const x = require('m')import x from 'm' (with node: prefix for Node builtins).
  • const { a, b } = require('m')import { a, b } from 'm'.
  • module.exports = X (identifier) → export default X.
  • module.exports = { a, b } (object literal) → named export { a, b }.

Detector pattern

The detector at src/analyze/detectors/typescript/commonjs.ts finds ts-morph CallExpression nodes with the callee require and BinaryExpression nodes with module.exports on the left.

Preconditions

  1. The file does not use __dirname or __filename — these are CommonJS-only globals; ESM requires import.meta.url translation which is out of scope.
  2. No dynamic require (require(name) where name is not a string literal) — ESM imports must be static.
  3. The file is not a .cjs extension (explicitly opted into CommonJS by the user).
  4. No require.resolve(...), require.cache, or other Node CJS-specific APIs.

Before / after

// Legacy CommonJS interop.
const path = require('path');

module.exports = {
  join: (a, b) => path.join(a, b),
};

Edge cases handled

  • Node builtins (path, fs, os, crypto, etc.) get the modern node: specifier prefix.
  • Package specifiers (lodash, express) are kept unprefixed.
  • Relative specifiers ('./utils') are kept unprefixed.
  • Destructured require (const { join } = require('path')) becomes named import.
  • module.exports = identifier becomes export default identifier.
  • module.exports = { a, b } becomes export { a, b }.

Edge cases NOT handled (skip via precondition)

  • File uses __dirname / __filename (precondition node-globals).
  • Dynamic require(name) where name is not a literal (precondition dynamic-require).
  • .cjs files (precondition cjs-extension).
  • module.exports.x = ... style (top-level mutation of the exports object — defer to a manual migration).
The actual reordering of declarations between input and output may differ — module.exports = { join } references a join defined inline; the rewrite hoists the join const ahead of the named export. Run refactron run --dry-run for the exact diff in your code.