Skip to content

CJS → ESM #341

Open
2 of 7 issues completed
Open

Description

@JakobJingleheimer

Technical Design

Create a wrapper migration to orchestrate individual sub-migrations (eg inline require()s → await import() or import.sync() depending on parent context being sync or async).

This wrapper should handle:

  1. Running individual API migrations
  2. Update files using CJS-specific extension (.cjs → .mjs, etc).
  3. Update package.json ("type": "commonjs" → "type": "module", etc).

I think we can release a 0.1.0 version with a subset:


Important

Each sub-task should have its own PR

Activity

  1. added this to the CJS → ESM milestone on Jan 22, 2026
  2. AugustinMauroy commented on Jan 22, 2026

    @AugustinMauroy
    Member

    Update files using CJS-specific extension (.cjs → .mjs, .cts → .mts, .d.cts → .d.mts).

    IMO we shouldn't touch to typescript file for the first iteration.

    FYI: i have opened a pr as draft to make some research about the good way to handle that #340

    Also If we change the extension we will need to update some other part of the codebase which mean we need a way to track/store that.

    In term of technical part we:

    • want to have step by step thing not one jssg workflow
    • doesn't want to have one big pr with +50K

    🚨IMPORTANT 🚨
    I'm going to have a call today with codemod.com team to discuss about technical part. And with theses information I'm going to make an action plan.

  3. JakobJingleheimer commented on Jan 22, 2026

    @JakobJingleheimer
    MemberAuthor

    Also If we change the extension we will need to update some other part of the codebase which mean we need a way to track/store that.

    I accounted for that in the relevant sub-tickets 🙂 correct-ts-specifiers can handle that (just run it after the file extensions are changed—it should work for vanilla js extensions too; if it doesn't, it probably easily could).

    I just realised correct-ts-specifiers needs an update to also handle node:test's mock.module.

  4. AugustinMauroy commented on Jan 22, 2026

    @AugustinMauroy
    Member

    Okay what we will do :

    1. having a pr that scaffold the codemod
    2. having mutilple pr that setup the tests suite without running it. (yeah test driving development here)
    3. Adding one codemod by pr + running there test
    4. publish the codemod

    Technical constraint, if we modify file ext we will need to store data (from/to) somewhere. It's will be stored in state which is not available on steps that are Codemod Registry Step we will only use jssg step. So what it's implied for the user it's mean the user will just be able to run @nodejs/cjs-to-esm and not each step as independent thing.
    We can still block the user with manual step

  5. JakobJingleheimer commented on Jan 22, 2026

    @JakobJingleheimer
    MemberAuthor

    it's mean the user will just be able to run @nodejs/cjs-to-esm and not each step as independent thing

    I think that's not necessarily true: they can run the individual steps, but they can't automatically update file extensions (which they may not even need to do). No?

  6. AugustinMauroy commented on Jan 23, 2026

    @AugustinMauroy
    Member

    it's mean the user will just be able to run @nodejs/cjs-to-esm and not each step as independent thing

    I think that's not necessarily true: they can run the individual steps, but they can't automatically update file extensions (which they may not even need to do). No?

    Saddly if we want to harmonize everything on use codebase we need to update ext to js + update package.json type key. To not have to do some intensive fs call to verify all import specifier we can just store a from/to array. But that imply to be different JSSG step in the same codemod

  7. JakobJingleheimer commented on Jan 23, 2026

    @JakobJingleheimer
    MemberAuthor

    No, what I mean is something like this:

    ./src
    ├  foo/
    ┆  └  …
    ├  bar/
    ┆  └  …
    ├  qux/
    ┆  └  …
    ├  zed/
    ┆  └  …
    ├  index.js
    ├  util.js
    └  …
    

    Let's say this project is a very simple case: everything is CJS and the only CJS parts are top-level require()s and straightforward module.exportss.

    No file extension updates are needed; the only "outside" update needed is setting pjson.type to "module", which doesn't depend on file extension updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions