Skip to content
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

ADR: CommonJS and ESM decision #323

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

kjugi
Copy link
Member

@kjugi kjugi commented Jan 16, 2025

As discussed in our last meeting #320

Summarizing the decision around ESM builds.

@kjugi kjugi changed the title CommonJS and ESM decision ADR: CommonJS and ESM decision Jan 16, 2025
Copy link
Member

@UlisesGascon UlisesGascon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work doing the consolidation @kjugi! Thanks ❤️

I added some suggestions regarding tone and context, feel free to discus/accept/reject them ;)

docs/adr/isomorphism-support.md Outdated Show resolved Hide resolved
docs/adr/isomorphism-support.md Outdated Show resolved Hide resolved
docs/adr/isomorphism-support.md Outdated Show resolved Hide resolved
docs/adr/isomorphism-support.md Outdated Show resolved Hide resolved
docs/adr/isomorphism-support.md Outdated Show resolved Hide resolved
docs/adr/isomorphism-support.md Outdated Show resolved Hide resolved
docs/adr/isomorphism-support.md Outdated Show resolved Hide resolved
Copy link
Member

@bjohansebas bjohansebas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, With the changes proposed by Ulises

@bjohansebas bjohansebas requested a review from a team January 17, 2025 16:17
@kjugi
Copy link
Member Author

kjugi commented Jan 18, 2025

Thank you @UlisesGascon for your great suggestions! It elevates this document to a new level. It's more mature now ✌️
@ljharb Thank you too for your comments and attention to detail! 🥇

@kjugi kjugi requested a review from UlisesGascon January 18, 2025 22:06
Copy link
Member

@wesleytodd wesleytodd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am good with this as it is more of a "tracking document", but it may end up being something end-users look at and I am not sure it fully satisfies the questions they might have. We should keep an eye out for feedback on if we should make a user facing statement on this at some point, maybe as a blog post geared more toward end users.

@UlisesGascon
Copy link
Member

maybe as a blog post geared more toward end users

100% that is something that we can do. Who wants to take leadership? :)


- **Positive Impact**: It does not require to support another set of tools and one more major (or at least big) release.
- **Negative Impact**:
- Packages can't be used in deno projects and potentially in other future runtime engines for JavaScript that decide to not support commonjs. That can be a potential user miss
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not true though, right? deno can indeed use cjs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can it? i thought jsr packages could be consumed by cjs but deno still can only consume esm packages

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the premise of Deno was that it was only ESM, although I would have to verify it. Now you've made me doubt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, they do support it, so it’s probably fine to say that only Deno 1 doesn’t support it, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just remove this line. or rather, change it to reflect that packages may or may not work in browser environments

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 'may not work in browser environments.'

Copy link
Member Author

@kjugi kjugi Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! Please resolve if it's fine now ✌️


## Decision

During the [working session](https://github.com/expressjs/discussions/issues/320), we had an in-depth discussion about this topic. After careful consideration, we concluded that we will not make a dedicated effort to export our libraries in the ESM format. Instead, we will continue exporting the libraries as we have done historically.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to expose ESM and did so historically, I presume I should not since it’d be a dedicated effort?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which packages export ESM today?

Copy link
Member

@blakeembrey blakeembrey Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Historically I did it for path-to-regexp but removed it in the latest major to align with the TC, it wasn’t any additional overhead to maintain. It was more for people’s build tooling than node ESM though, as adding it predated it being formalized.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think I should include this fact from the past? I've been thinking about how to put this together now and I'm not sure.
The document should state the last known status. We can list exceptions now (if any) and explain this fact or update the document later when we agree on which one we want to change. There is a Changelog on the bottom so it should be easy to mark that out in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would need to change the phrase 'historically,' as it gives the impression that it was never done, but as Blake explains, it was done in some package.


During the [working session](https://github.com/expressjs/discussions/issues/320), we had an in-depth discussion about this topic. After careful consideration, we concluded that we will not make a dedicated effort to export our libraries in the ESM format. Instead, we will continue exporting the libraries as we have done historically.

This decision is motivated by the lack of resources to maintain such an effort in the long term. It is also worth noting that Express is specifically designed to run with Node.js. While some of our libraries can be considered "isomorphic," this was unintended and can currently be classified as an "unofficial but functional feature." Consequently, our CI systems do not include browsers or other runtimes as part of their testing workflows.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being isomorphic has been intentional for the packages I maintain, and I do make a dedicated effort to keep it isomorphic. This includes edge environments that are plain JavaScript. The lack of CI for these environments aren’t connected to their support.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with that last statement; something isn't actually supported unless it's actually tested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s a reasonable take, but doesn’t preclude that I do support them. Some things are unreasonable to have direct tests. There’s a reason we often only test each major node release instead of every single version, and we don’t claim those aren’t supported. It’s the same that I don’t test directly in CI for Cloudflare Workers (honestly not sure how I would tbh) but I do support them, by virtue of knowing it’s V8 and disallowing native node APIs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR is that I don’t think this assertion belongs in the doc, something being isomorphic may be related but is not connected to ESM support. I don’t want to give an impression that some of the packages in these organizations are not designed to support other JavaScript environments, regardless of ESM support, since it could be referred to as a way to drop existing supported environments without a major.

Copy link
Member

@ctcpip ctcpip Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, we should decouple isomorphism from module system compatibility. The root of this was that ESM exports would be justified for packages for which we wanted to support isomorphism. In other words, if the package was not intended to be isomorphic, there is perceived little value in exporting ESM.

I also agree that we cannot (and should not attempt) to test against every possible environment. However, isomorphic packages should get some level of testing outside of node, and this should hopefully not be prohibitively difficult.

We have already run into situations where we are not certain if a package is properly working in other environments, in part due to lack of isomorphism in the test suite. (Although admittedly router is not the best example of a package for which we would want to support isomorphism.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe but sadly we’re stuck with the tooling we have today, not the ideal world, so I thought it worth mentioning this isn’t just a browser concern.

Ref: https://github.com/evanw/esbuild/blob/main/docs/architecture.md#es6-linking

This is possible with ES6 modules but not with CommonJS because ES6 imports are bound at compile time while CommonJS imports are bound at run time.

Just to be clear, I’m not at all advocating for ESM here, I’m only providing context on why it shouldn’t be dismissed as “it’s a browser thing”.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ESM imports can be static or dynamic; so can CJS requires - since tooling has to account for both, there's literally no technical difference between them. Nothing is possible for ESM that's not possible for CJS if you're talking about a build tool.

Copy link
Member

@blakeembrey blakeembrey Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like something to take up with the tools, I’m just pointing out that common build tools (both Webpack and esbuild) require ESM for tree shaking, and these are common tools to bundle for server side environments that may have size limits (including ones that run node). Things haven’t split cleanly into “ship it onto a server” vs “ship it to a browser” for years now, as I often ship code to server less platforms.

Again, don’t take this as arguing for ESM support in this thread, but to argue against that it’s only relevant for browsers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree :-) i just think it's important to point out that it's a failing in the tools, not an inherent difference in the module format.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I have applied some changes. Please verify. Also, I think I'm parking the "isomorphic" topic completely. As mentioned in the comments, let's focus on export type here. I can start another doc about isomorphic and its status right after this one if needed.

- **Positive Impact**: It does not require to support another set of tools and one more major (or at least big) release.
- **Negative Impact**:
- Packages can't be used in deno projects and potentially in other future runtime engines for JavaScript that decide to not support commonjs. That can be a potential user miss
- OSS library authors that use our packages in ESM native libs might suffer from a lack of support
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a greater negative impact which is that other users fork the packages to ESM and those packages lack security updates over time which leaves users in a worse position.

Copy link
Member Author

@kjugi kjugi Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a valid point! Added, thanks! Please resolve if it's fine now ✌️


At present, our libraries function seamlessly in Node.js, supporting both CommonJS and ESM. Transitioning to support additional scenarios, such as direct ESM exports, would require significant changes to our CI systems and additional maintenance overhead.

**TL;DR**: Dedicated ESM exports will not be available for [expressjs](https://github.com/expressjs), [pillarjs](https://github.com/pillarjs), or [jshttp](https://github.com/jshttp) packages. PRs with such a change will not be accepted.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part needs polishing. Soften the language and consider exporting ESM when it requires little effort. Include libraries maintained by @blakeembrey, which now include ESM export. It should be clear that our CI tests do not cover this for today.
"CommonJS is a requirement, but ESM is not"

@samuelstroschein
Copy link

samuelstroschein commented Feb 3, 2025

Chipping in from an "I want to use express dependencies but need ESM" perspective, see pillarjs/path-to-regexp#347.

If the maintenance effort of libraries like path-to-regexp to distribute CJS and ESM does not lead to additional overhead (@blakeembrey in #323 (comment)), why should the desire for express js to consume dependencies as CJS block dependencies from emitting ESM at their will?

Adopting path-to-regexp is a tough choice for me now which leads to:

  • consistently pushing for ESM OR
  • fork/not adopt path-to-regexp

I assume that experience will be universal across express js dependencies. Again, all under the presumption that distributing ESM in dependencies yields no additional overhead.

@wesleytodd
Copy link
Member

wesleytodd commented Feb 4, 2025

I assume that experience will be universal across express js dependencies.

This was a main point of our discussion. It is not clear to me that it should be universal. The one thing we all agreed on is that we MUST ship CJS, but there seems to be no reason to limit packages which want to also ship ESM from doing so. That was the main feedback on this doc from our meeting yesterday IIUC.

@ljharb
Copy link
Contributor

ljharb commented Feb 4, 2025

There are inherent costs and dangers in shipping a dual-format package - it exacerbates the likelihood of duplicated (and thus out of sync) state; it's unlikely both formats are being tested (whether they're hand-written, increasing the likelihood of deviations, or transpiled, increasing the likelihood of transformation bugs); it increases the package size (which seems to be important to the same folks that want native ESM).

Also, node can import CJS, and every build tool can translate it to ESM just fine if needed, so I'm still not clear on the value.

@samuelstroschein
Copy link

@ljharb hope to share some light on the necessity of ESM.

Ofc you can make the decision to ignore the (minor) group that needs ESM. That's completely OK. Everything is a trade-off.

, so I'm still not clear on the value.

I can't adopt path-to-regexp if it's not ESM because tree-shaking doesn't work pillarjs/path-to-regexp#347 and import errors occur pillarjs/path-to-regexp#346.

it increases the package size (which seems to be important to the same folks that want native ESM).

ESM people care about tree-shaking, not the package size.

@wesleytodd
Copy link
Member

Yeah, I agree that I continue to be unclear on the value. That said, we did discuss on the call yesterday that we should expect them to be tested if we are shipping dual mode packages.

@ljharb
Copy link
Contributor

ljharb commented Feb 4, 2025

@samuelstroschein right - i'm saying that CJS treeshakes the same as ESM, and if there's a specific tool you're using that doesn't support it, i'd encourage you to advocate they do so.

@wesleytodd
Copy link
Member

wesleytodd commented Feb 4, 2025

If someone comes with proof that there is some technical barrier to efficiently using CJS in a real world use case, I would absolutely love to help, but that is not what has happened in any of these threads (see my question in the one linked about treeshaking).

EDIT: I realized this might be unclear in this comment so want to point out that I shared my opinion above that we should not stand against packages shipping dual mode if the folks maintaining those want to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants