Background
The codebase currently uses CommonJS throughout. Since we're on Node.js 24, we have access to mature ESM support including require(esm) interop, import.meta.dirname, and automatic module syntax detection.
Scope
~184 JavaScript files need changes:
- Convert
require() to import
- Convert
module.exports to export
- Add
.js extensions to all relative imports
- Update JSON imports to use
with { type: 'json' } syntax
Migration approach
Node 22+ allows CJS to require() synchronous ESM files, so we can migrate incrementally rather than all at once. Files with ES module syntax are auto-detected without needing "type": "module" in package.json.
Suggested order:
- Replace blocking dependencies first
- Migrate source files (leaf modules first, working inward)
- Migrate test files last
Blocking dependencies
These packages are CJS-only and need replacement:
| Package |
Replacement |
proxyquire |
esmock (8 test files affected) |
azure-storage |
@azure/storage-blob |
painless-config |
dotenv or custom solution |
express-routes-versioning |
inline or find alternative |
geit |
find alternative (appears unmaintained) |
proxyquire is the biggest issue. It fundamentally can't work with ESM because it hijacks require(). All 8 test files using it need rewriting.
Files needing manual attention
Dynamic requires in bin/config.js:32
if (!target) target = require(requirePath) // dynamic path from env vars
This needs redesign using import() or a static import map.
__dirname usage (6 test files)
These can use import.meta.dirname directly—no workaround needed on Node 21.2+.
What can be automated
A tool like cjs-to-esm can handle most of the mechanical conversion:
require → import
module.exports → export
- Adding file extensions
The JSON import syntax and dynamic requires need manual work.
Open questions
- Do we add
"type": "module" to package.json, or rely on auto-detection?
- Should we convert to TypeScript source files at the same time, or keep that separate?
- Any preference on
proxyquire replacement (esmock vs dependency injection refactor)?
Background
The codebase currently uses CommonJS throughout. Since we're on Node.js 24, we have access to mature ESM support including
require(esm)interop,import.meta.dirname, and automatic module syntax detection.Scope
~184 JavaScript files need changes:
require()toimportmodule.exportstoexport.jsextensions to all relative importswith { type: 'json' }syntaxMigration approach
Node 22+ allows CJS to
require()synchronous ESM files, so we can migrate incrementally rather than all at once. Files with ES module syntax are auto-detected without needing"type": "module"in package.json.Suggested order:
Blocking dependencies
These packages are CJS-only and need replacement:
proxyquireesmock(8 test files affected)azure-storage@azure/storage-blobpainless-configdotenvor custom solutionexpress-routes-versioninggeitproxyquireis the biggest issue. It fundamentally can't work with ESM because it hijacksrequire(). All 8 test files using it need rewriting.Files needing manual attention
Dynamic requires in
bin/config.js:32This needs redesign using
import()or a static import map.__dirnameusage (6 test files)These can use
import.meta.dirnamedirectly—no workaround needed on Node 21.2+.What can be automated
A tool like
cjs-to-esmcan handle most of the mechanical conversion:require→importmodule.exports→exportThe JSON import syntax and dynamic requires need manual work.
Open questions
"type": "module"to package.json, or rely on auto-detection?proxyquirereplacement (esmockvs dependency injection refactor)?