clearlydefined / clearlydefined/service

Migrate from CommonJS to ES Modules

Open
#1,430 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
51
Forks
46
PR merge metrics
No merged PRs in 30d

Description

### 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`**
```javascript
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)?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.