Convert your CommonJS codebase to ES Modules with confidence.
Add "type": "module" to package.json
This makes all .js files ES Modules by default
Replace require() with import
Convert all CommonJS imports to ES Module syntax
Replace module.exports with export
Use named or default exports
Add .js extensions to imports
ES Modules require explicit file extensions
Replace __dirname and __filename
Use import.meta.url instead
Test thoroughly
Run your test suite and check for errors
❌ CommonJS (Before)
// utils.js
const helper = require('./helper');
function add(a, b) {
return a + b;
}
module.exports = { add };
✅ ES Modules (After)
// utils.js
import helper from './helper.js';
export function add(a, b) {
return a + b;
}
❌ Problem: Missing file extensions
import utils from './utils'; // Error!
✅ Solution: Add .js extension
import utils from './utils.js';
❌ Problem: __dirname not defined
const path = __dirname + '/file.txt'; // Error!
✅ Solution: Use import.meta.url
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
Simpler in Node.js 22+:
// Node.js 21.2+ provides these directly
const __dirname = import.meta.dirname;
const __filename = import.meta.filename;
// No imports needed!
❌ Problem: Dynamic require()
const mod = require(`./${name}.js`); // Error!
✅ Solution: Use dynamic import()
const mod = await import(`./${name}.js`);
You can migrate gradually using the dual package approach:
package.json
{
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
This allows your package to work with both CommonJS and ES Module consumers.
The CJS-to-ESM migration story has improved significantly:
--rewriteRelativeImportExtensions auto-rewrites .ts to .js in importsShould You Still Migrate?
Yes. While interop has improved, ESM is the standard going forward. New packages increasingly ship ESM-only, and ESM enables better tree-shaking, static analysis, and tooling support.
Several tools can automate the mechanical parts of CJS-to-ESM conversion:
cjs-to-esm
Converts require() to import and module.exports to export. Handles most common patterns automatically.
npx cjs-to-esm ./src
jscodeshift / codemods
AST-based transform toolkit for large-scale codebase migrations. Write custom transforms for project-specific patterns.
Tip: Automated tools handle ~80% of conversions. Always review the output and test thoroughly - edge cases like conditional require() and dynamic paths need manual attention.