Skip to content

Migrate from CommonJS to ES Modules #1430

@JamieMagee

Description

@JamieMagee

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:

  1. Replace blocking dependencies first
  2. Migrate source files (leaf modules first, working inward)
  3. 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:

  • requireimport
  • module.exportsexport
  • 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)?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions