This is a record of my experiences uplifiting CHToolbox from CommonJS to ES Modules. Some things are specific to TypeScript.
-
Updage esLint configuration. While on CJS, I was stuck on esLint
8.x
. So, I was still using the old.eslintrc.js
file format. To prepare for being able to upgrade to esLint9.x
(where support has been removed for the old config format), I preemptively updated my esLint config to the new flat format (in aneslint.config.mjs
file).- This was a headache of its own since the migration path between config formats was not super clear (and the automated tools did not work for me).
- The new flat config works with both esLint
8.x
and9.x
.
-
Update project configuration to target ESM:
-
Set
"type": "module",
inpackage.json
-
Update
compilerOptions
intsconfig.json
to have:"module": "nodenext", "moduleResolution": "nodenext",
-
-
Convert all imports to new format (automated using ts2esm)
- This process turned out to be super easy. No drama.
-
Fix unit tests. This, by far, was the worst part of the process.
- With ESM, you can no longer use
sinon
to directly modify the file dependencies. This article had the best breakdown I could find on the situation and the avaiable options. - Unfortunatly, as can been seen from reviewing the optinos in the article, basically all the options for doing stubbing in ESM unit tests are bad.
- Unless you are content doing dependency injection at the platform level (e.g. Angular or Effect), basically your only realistic option for stubbing imports is to hijack the the loading of the module you want to test (simliar vibes to how
rewire
could be used in CJS tests). - I settled on using esmock in my test files to load the module being tested (with my
sinon
stubs injected). This allowed me to keep most of my test code and assertions the same (since I was still usingsinon
stubs). However, I had to re-write pretty much all of my test setup code to get the mocks injected where they needed to be.
- With ESM, you can no longer use
-
Deal with all the broken tooling.
- Come to find out,
nyc
(istanbul code coverage) does not play very nicely with ESM. I ended up switching to c8 which worked with ESM out of the box (and was drop-in compatible with mynyc
config). - Also had a bunch of troubled with
ts-node
(used for mymocha
tests and direclty running the TS code locally). I ended up switching to tsx which also just worked out of the box with ESM
- Come to find out,
In the end, the only real "benefit" I have found so far with ESM (besides just remaining "compatible" with the trajectory of the JS ecosystem) is the support for top-level await
statements (without needing to be wrapped in an async IIFE). Even this is probably bad practice in production code (performance concerns...), but it can be super handy in test files!