Repository: e18e/e18e
Pull request #54: docs: add migration to esm post
Author: login: 43081j
Created: 2025-03-19T13:36:57Z | Merged: 2025-03-20T10:24:02Z



Diff:
diff --git a/docs/blog/migrating-the-ecosystem-to-esm.md b/docs/blog/migrating-the-ecosystem-to-esm.md
new file mode 100644
index 00000000..d6576b39
--- /dev/null
+++ b/docs/blog/migrating-the-ecosystem-to-esm.md
@@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
+
+Some maintainers may still want to use their choice of tool/bundler, so in those cases we can configure the tool to no longer output CommonJS.
+
+For example, in `tsup`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module",
+  "tsup": {
+    "format": ["esm"]
+  }
+}
+```
+
+## How the community is helping
+
+Within the e18e community, we have been tracking the migration from CJS to ESM in an [issue](https://github.com/e18e/ecosystem-issues/issues/129) for some time.
+
+It is a crazy amount of work for us to try migrate every possible package, so we have opted for the approach of helping migrate high impact tools, starter kits, frameworks and what not. This should hopefully help others to follow suit and give plenty of example migrations to work from.
+
+### Who has migrated so far?
+
+We've already seen a huge amount of packages make the jump. Here are just a few (some of which were assisted by e18e, and some not):
+
+- [chai](https://github.com/chaijs/chai) and all official plugins
+- [tinyspy](https://github.com/tinylibs/tinyspy)
+- [tinybench](https://github.com/tinylibs/tinybench)
+- [tinyexec](https://github.com/tinylibs/tinyexec)
+- [vueuse](https://github.com/vueuse/vueuse)
+- [@pinia/nuxt](https://github.com/vuejs/pinia) (_in progress_)
+- [clack](https://github.com/bombshell-dev/clack)
+- [eslint-plugin-lit](https://github.com/43081j/eslint-plugin-lit)
+- [eslint-plugin-wc](https://github.com/43081j/eslint-plugin-wc)
+- [eslint-plugin-svelte](https://github.com/sveltejs/eslint-plugin-svelte)
+- [eslint-plugin-github](https://github.com/github/eslint-plugin-github)
+- [picospinner](https://github.com/tinylibs/picospinner)
+
+This is just a subset of the packages we've seen migrate, many more PRs are still in progress by contributors and maintainers alike.
+
+### Migrating ESLint plugins
+
+One place we can easily contribute to this effort is the migration of ESLint plugins.
+
+ESLint already imports plugins under the hood, and so can support them in CommonJS _or_ ES module format.
+
+Migrating an ESLint plugin to ES modules will mean dropping support specifically for consumers who have flat configs written as CommonJS in a version of Node less than 20.x.
+
+This is because flat configs in `>=20` will have `require(esm)` available, and legacy configs (`.eslintrc`) will use `import`.
+
+### Migrating starter kits and frameworks
+
+Another high impact place to help with this migration is in starter kits and frameworks. Many projects are created from these templates, so migrating them to ES modules will mean all new consumers automatically have the right setup.
+
+We haven't yet started collaborating with these projects on this yet, but it is high priority in the pipeline.
+
+## Get involved
+
+If you maintain a package and want some help migrating, let us know! Many of the community would be happy to chip in.
+
+Similarly, if you want to help migrate packages with us, come say hi.
+
+[Join our discord](https://chat.e18e.dev) and let us know you want to help!
diff --git a/docs/public/og/migrating-the-ecosystem-to-esm.png b/docs/public/og/migrating-the-ecosystem-to-esm.png
new file mode 100644
index 00000000..0665772d
Binary files /dev/null and b/docs/public/og/migrating-the-ecosystem-to-esm.png differ


Reviews:
user: login: ljharb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T15:17:31Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:40:39Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:42:14Z
user: login: ljharb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T16:53:52Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T17:00:16Z
user: login: ljharb
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T17:17:16Z
user: login: patak-cat
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T18:31:30Z
user: login: joyeecheung
body: Sorry for the late reply, was swamped by meetings this week...
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T13:57:58Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T14:02:13Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T14:05:34Z
user: login: joyeecheung
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T15:38:50Z
user: login: joyeecheung
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T15:41:32Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T18:32:32Z
user: login: joyeecheung
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-22T23:30:50Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T00:19:17Z
user: login: joyeecheung
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-23T00:22:25Z
user: login: 43081j
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T00:35:02Z

Review comments:
diff_hunk: @@ -0,0 +1,140 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: ljharb
body: can this also mention the more performant approach of renaming all the files to ".mjs", and for a package, adding `exports` so that external consumers don't have to use file extensions?
created_at: 2025-03-19T15:16:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,140 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: ljharb
body: this isn't quite accurate; a dual package isn't necessary to support both CJS and ESM because you can `import` CJS. The reasons for a dual package are far more nuanced, and don't actually exist for most packages that have shipped dual.
created_at: 2025-03-19T15:17:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,140 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: that's true, i'll add an extra paragraph 👍 
created_at: 2025-03-19T15:40:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,140 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: most that have shipped dual did it to avoid requiring a dynamic import in CJS i think. so maybe we just need to mention that?

i.e.

```suggestion
Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
```
created_at: 2025-03-19T15:42:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,140 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: ljharb
body: this is true only if they insist on writing native ESM, though - that's the missing nuance here, i think. iow, shipping CJS-only is a trivial way to avoid any need for a dual package (prior to `require(esm)`)
created_at: 2025-03-19T16:53:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,140 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: that's also not quite true. various packages ship dual so they can support browsers natively without the need for a bundler. they had to use ES modules because browsers don't understand CJS, so they had to ship dual to also support older Node (or require a dynamic import).

i think this shows there's too many edge cases and situations here to mention without going off on a huge tangent
created_at: 2025-03-19T17:00:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,140 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: ljharb
body: that's true.

maybe just say "support both CJS and non-node ESM"?
created_at: 2025-03-19T17:17:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,170 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+---
+
+_March 19, 2025_
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
+
+Some maintainers may still want to use their choice of tool/bundler, so in those cases we can configure the tool to no longer output CommonJS.
+
+For example, in `tsup`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module",
+  "tsup": {
+    "format": ["esm"]
+  }
+}
+```
+
+## How the community is helping
+
+Within the e18e community, we have been tracking this in an [issue](https://github.com/e18e/ecosystem-issues/issues/129) for some time.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: patak-cat
body: Better to avoid "this" here to help the reader
```suggestion
Within the e18e community, we have been tracking the migration from CJS to ESM in an [issue](https://github.com/e18e/ecosystem-issues/issues/129) for some time.
```
created_at: 2025-03-19T18:31:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: joyeecheung
body: 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".
created_at: 2025-03-20T13:40:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: joyeecheung
body: Not sure how `tsup` actually manages the exports, but from my understanding, usually migrating a dual module means adding `module-sync` to the exports https://github.com/nodejs/node/pull/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):

```js
{
  "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)

```js
{
  "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):

```js
{
  "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. 
created_at: 2025-03-20T13:53:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: It is an es module

This snippet is assuming you already converted your syntax to esm

I'll try make that clearer
created_at: 2025-03-20T14:02:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: 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?

created_at: 2025-03-20T14:05:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: joyeecheung
body: 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.
created_at: 2025-03-20T15:38:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: joyeecheung
body: That makes sense now. Not sure if it's just me not reading thoroughly enough, it does feel that this assumption is missing somewhere...
created_at: 2025-03-20T15:41:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: 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
created_at: 2025-03-20T18:32:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: joyeecheung
body: > 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.
created_at: 2025-03-22T23:30:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: 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
created_at: 2025-03-23T00:19:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: joyeecheung
body: 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 👍
created_at: 2025-03-23T00:22:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,175 @@
+---
+title: Migrating the ecosystem to ES modules
+author:
+  - name: The e18e Contributors
+sidebar: false
+date: 2025-03-19
+head:
+  - - meta
+    - property: og:type
+      content: website
+  - - meta
+    - property: og:title
+      content: Migrating the ecosystem to ES modules
+  - - meta
+    - property: og:url
+      content: https://e18e.dev/blog/migrating-ecosystem-to-es-modules
+  - - meta
+    - property: og:description
+      content: An update on the progress we're making migrating packages to ES modules
+  - - meta
+    - property: og:image
+      content: https://e18e.dev/og/migrating-the-ecosystem-to-esm.png
+---
+
+_March 19, 2025_
+
+# ![Migrating the ecosystem to ES modules](/og/migrating-the-ecosystem-to-esm.png)
+
+Node made some huge steps forward recently by releasing `require(esm)` to each of the long-term support versions (other than the soon to fall out of LTS 18.x). Awesome work by [@joyeecheung](https://bsky.app/profile/joyeecheung.bsky.social) getting it over the line!
+
+This is awesome work and unblocks a huge amount of packages on the migration path from CommonJS to ES modules :tada:
+
+So let's have a look into what's next and how the e18e community is trying to help!
+
+## `require(esm)`
+
+One of the main blockers in the CJS vs ESM story has been interop. You could import CJS from within an ES module, but could not `require` an ES module from within a CJS module.
+
+This left us with two options at the time for a migration path:
+
+- Change much of the code to be `async`, such that we can use a dynamic `import` (which does work in CJS modules)
+- Change all CJS dependencies of our ES module package to be ES modules
+
+The ideal is the latter, that everyone uses ES modules. However, this obviously isn't feasible since not all packages are maintained and it'd be a crazy amount of work to replace them.
+
+So we were blocked...
+
+That is until `require(esm)` came along! This basically means we _can_ `require` an ES module inside a CJS module now. CJS packages can consume ES module packages and vice versa. A huge move forward and unblocks us all to get back on the migration path!
+
+For example:
+
+```ts
+// file: foo.cjs
+
+// chai is esm only, but this now works!
+const { expect } = require('chai')
+```
+
+## Types of package
+
+As part of this migration, we have three types of package to deal with:
+
+- ESM packages
+- CommonJS packages
+- Dual packages
+
+### Migrating a CommonJS package
+
+In most cases, this is as simple as setting `type` to `"module"` in `package.json`, then updating all imports to include file extensions.
+
+For example, in `package.json`:
+
+```json
+{
+  "name": "my-package",
+  "type": "module"
+}
+```
+
+And updating the imports:
+
+```ts
+// before
+import './foo'
+
+// after
+import './foo.js'
+```
+
+If you want to expose extensionless imports to your consumers, you can use an export map:
+
+```json
+{
+  "exports": {
+    "./foo": "./foo.js"
+  }
+}
+```
+
+### Migrating a dual package
+
+Dual packages are basically a necessary evil if you want to support both CJS and ESM _before `require(esm)` was available_ and don't want to force your CJS users to use an async dynamic import.
+
+These packages work by having **two copies** of the sources and the types.
+
+Fortunately, this doesn't increase the package size much since it will be compressed anyway. However, **it does increase the on-disk size** (once extracted by `npm`).
+
+Most people shipping TypeScript packages like this will be using a tool like [tsup](https://github.com/egoist/tsup) or [tshy](https://github.com/isaacs/tshy).
+
+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.
path: docs/blog/migrating-the-ecosystem-to-esm.md
user: login: 43081j
body: 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
created_at: 2025-03-23T00:35:02Z
author_association: CONTRIBUTOR

PR comments:
user: login: 43081j
created_at: 2025-03-19T13:37:38Z
body: @patak-dev could you help me with a cover image? 👀 

@joyeecheung could you double check that the `require(esm)` section isn't nonsense? and if you have a useful link of where most of it is explained, i could link to it
author_association: CONTRIBUTOR
user: login: netlify[bot]
created_at: 2025-03-19T13:37:42Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *shiny-salamander-b16dd2* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 9fa49569bbcda673f4eeaaf8a8c7f3c753c3856f |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/shiny-salamander-b16dd2/deploys/67dbec2fb95996000830691a |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-54--shiny-salamander-b16dd2.netlify.app](https://deploy-preview-54--shiny-salamander-b16dd2.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTU0LS1zaGlueS1zYWxhbWFuZGVyLWIxNmRkMi5uZXRsaWZ5LmFwcCJ9._Z5j0Z4_p0pNF1B4RbZQ5roEFVPK3Jrge2uZEupafhY)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [shiny-salamander-b16dd2 Preview](https://deploy-preview-54--shiny-salamander-b16dd2.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/shiny-salamander-b16dd2/configuration/notifications#deploy-webhooks)._
author_association: NONE