Thursday, December 8, 2022
ESM-migration
After some tests with Deno land development and some import with esm.sh, I think it's worth migrating my commonJS project to ESM.
But replacing commonJS with ESM, like sindresorhus did, will bring breaking changes and devastation around my existing code base.
p-map case
Take p-map and look at the version usage; Most people use the old version. p-map is an ESM-only package, so most users are currently stuck with older versions.
That is mostly the case for all sindresorhus's packages.
Dual stack
The best way is to publish dual staked NodeJS package. For that, you need some build chain to refactor and package.json upgrade.
tsconfig changes
By having 100% of my project written in typescript, I can apply the same change on all of them:
Create a new tsconfig-cjs.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"declarationMap": false,
"outDir": "dist/cjs"
}
}
then update the main tsconfig.json
to generate ESM modules, so in tsconfig.json
change:
{
"compilerOptions": {
"outDir": "dist/esm",
"moduleResolution": "node",
"module": "ES2020",
"declaration": true
}
}
I also recommend using an include
statement in your tsconfig
instead of exclude
, you only need to specify the entry point in the include, not all your
source files.
Note: we only want to generate the declaration once in the ESM dest.
package.json changes
Do no define any "type": "module"
or you cjs code will not be acceissible.
Replace your main entries:
from
"main": "dist/index.js",
"typings": "dist/index.d.ts",
to
"main": "dist/cjs/index.js",
"module": "./dist/esm/index.js",
"typings": "dist/esm/index.d.ts",
"exports": {
".": {
"types": "./dist/esm/index.d.ts", // "types" - can be used by typing systems to resolve the typing file for the given export. This condition should always be included first. see https://nodejs.org/api/packages.html#community-conditions-definitions
"require": "./dist/cjs/index.js",
"import": "./dist/esm/index.js",
"default": "./dist/cjs/index.js"
}
},
"files": [ "dist" ]
Update the build script:
{
"scripts": {
"prepublishOnly": "rimraf dist",
"build": "tsc --pretty --project . && tsc --pretty --project tsconfig-cjs.json",
"build": "tsc --build tsconfig.json --pretty", // new Version
"prepare": "npm run build"
}
}
Now your package is compatible with most nodeJS projects.
extra verifications step
Before any commit or publish, double check:
- your clean script
- your package.json
files
entry must include newly generated files
First, publish using --dry-run
and --tag
to avoid changing the latest
tag turning test.
ESM migration changes
In ESM all inport must include an file extention, import dep from './file'
will become import dep from './file.js'
to fix vs code auto import add in your
.vscode/settings.json
:
{
"javascript.preferences.importModuleSpecifierEnding": "js",
"typescript.preferences.importModuleSpecifierEnding": "js"
}
External migration blog post
- Anthony Fu Post
- Yonatan Kra Post
- Axel Rauschmayer Post
- sindre-sorhus Post about modules
- dual package hazard
- Readonly Pure ESM package discussion
Note for myself
- @u4/opencv-build
- @u4/tiny-glob
- js-promise-readable is converted
- api-ovh-node is converted
- dev-input-reader is converted
- proxmox-api is converted