-
-
Notifications
You must be signed in to change notification settings - Fork 16
docs: add migration to esm post #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@patak-dev could you help me with a cover image? 👀 @joyeecheung could you double check that the |
✅ Deploy Preview for shiny-salamander-b16dd2 ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Co-authored-by: patak <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late reply, was swamped by meetings this week...
import './foo' | ||
|
||
// after | ||
import './foo.js' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the title says
Migrating a CommonJS package
Then I assume foo.js
is a CJS module? I think in that case, it must be either renamed to foo.cjs
if "type": "module"
, or the content of foo.js
itself must be migrated to be authored in ESM.
If foo.js
still contains CJS code, I am not sure if this really counts as migration - it just adds an extra ESM wrapper around the code authored in CJS, which isn't necessarily in the first place because import cjs
already works, and this is just import esm-wrapper
where esm-wrapper
imports cjs
again?
If it means foo.js
must also be rewritten to be in ESM format, then from my reading this is missing a sentence to clarify that "first, rewrite foo.js
to be in ESM format".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is an es module
This snippet is assuming you already converted your syntax to esm
I'll try make that clearer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense now. Not sure if it's just me not reading thoroughly enough, it does feel that this assumption is missing somewhere...
|
||
Those shipping JavaScript often just use a bundler like [esbuild](https://github.com/evanw/esbuild) to create two bundles (or one and the sources). | ||
|
||
To migrate from these setups, we mostly need to do the same steps as migrating a CommonJS package from above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how tsup
actually manages the exports, but from my understanding, usually migrating a dual module means adding module-sync
to the exports nodejs/node#54648 if it's not yet ready to drop support of older Node.js versions.
So for example, if the exports map originally looks like this (it uses node
to provide additional fallback for non-node environments, like what babel is doing):
{
"type": "module",
"exports": {
// On Node.js, provide a CJS version of the package transpiled from the original
// ESM version, so that both the ESM and the CJS consumers in the same graph get
// the same version to avoid the having two versions of the same package
// conflicting with each other a.k.a. package hazard.
"node": "./dist/index.cjs",
// On any other environment, use the ESM version.
"default": "./index.js"
}
}
To prepare to drop dual shipping (say v1 is still dual-shipping because it is supporting older versions of Node.js, but it still wishes to tell Node.js > 20 to always pick the ESM version, no matter it's required or imported)
{
"version": "1.4.0",
"type": "module",
"exports": {
"node": { // Packages can drop this special case as they drop support for older Node.js
// On new version of Node.js, both require() and import get the ESM version
"module-sync": "./index.js",
// Supply ESM to bundlers for better generated code
"module": "./index.js",
// On older version of Node.js, where "module" and require(esm) are not supported,
// use the transpiled CJS version to avoid dual-module hazard.
"default": "./dist/index.cjs"
},
// On any other environment, use the ESM version.
"default": "./index.js"
}
}
To actually drop dual shipping (say on v2 which only supports Node.js v20.x and above):
{
"version": "2.0.0",
"type": "module",
"exports": {
".": "./index.js"
}
}
There are more complex cases where people define both require
and import
exports, which needs extra wrapping to avoid the dual package hazard. But in those cases the migration (without bumping major to avoid changing Node.js support matrix) would still boil down to: add a module-sync
exports (which must preceed require
, if there is one) that provides the ESM exports, so that both require and import picks the ESM version. And when it bumps major to drop support for Node.js <20, simply remove all these mess and just point the exports to the ESM, without caring how it's loaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is written under the assumption you just go all in on esm. Which presumably means you have one very simple export (your last example), which works in most node versions as esm but only 20.x and above as cjs
We don't need to care about "module sync" then, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think module-sync
primarily concerns whether the package is still supporting Node.js < 20. If they already decide to bump major and drop support for Node.js < 20 then yes they do not need to care about "module-sync"
(though they might still want "module-sync"
to be the last patch they add to the release that still supports < v20, so that when the older release is loaded by Node.js > 20, it's actually the ESM that will be required) . Maybe a note about Node.js version support would help clarifying that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node < 20 would still support it without module-sync
would it not? as an ES module
it just means you can't require(esm)
it. which is fine in this all-or-nothing strategy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node < 20 would still support it without module-sync would it not? as an ES module
Yes, it can be imported, just not required. Though for packages that adhere to the "when dropping support for older Node.js versions, bump major" principle, it affects what they could do with the last major release before the migration. For example:
- The package is dual-shipped in v1, which supports Node.js 18 and above
- The package wants to become ESM-only, so it just discards the CJS distribution, ditch the transpilation step, and ship in ESM form as-is, this is now v2 of the package, since it will no longer be require-able on Node.js 18.
The article has covered the two points above, though there is a remaining question:
- Is the package still maintaining v1 for Node.js > 20 for some time, or are they completely abandoning it and moving on to v2?
If they are still maintaining v1 (e.g. if v2 is not just about shipping ESM - which is likely not observable for users - but also has some other significant and observable major changes, which means users can still cling on to v1 for a while), then "module-sync"
allows them to specify that Node.js > 20 is always loading the ESM version to reduce dual-package hazard on v1. Or, even if they are abandoning v1, this addition of "module-sync"
might be the last thing they'll release to v1 to reduce the issues that may come from the dual-package hazard when v1 is loaded in Node.js > 20.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. In the packages that migrated so far as part of this coordinated effort, they did all just release new majors which contained nothing else. Just a switch to esm only
So none needed module-sync since it was generally understood that anyone who wants to require it from now on needs node 20 and above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, is this article just about "what have been done", instead of "what should be done if you are going to migrate to ESM"? If it's the former, then it makes sense 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's about what the community is doing to help while also briefly explaining how you can also migrate your own packages
You would only use module-sync
if you're continuing to ship a dual package, right? This is mostly pushing people to ship only esm. So what use does it have then? If you're shipping esm only, you're accepting that anything below 20 can't require it from what I understood
No description provided.