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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions