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

Create doc on how to support ESM and CommonJS when publishing modules #542

Open
Tracked by #1 ...
mhdawson opened this issue Oct 12, 2022 · 41 comments
Open
Tracked by #1 ...
Labels
package-maintenance-agenda Agenda items for package-maintenance team

Comments

@mhdawson
Copy link
Member

From discussion at the last collaborator summit.

This would help as the suggestion was that there were some easy ways to do this.

@mhdawson mhdawson added the package-maintenance-agenda Agenda items for package-maintenance team label Oct 12, 2022
@ljharb
Copy link
Member

ljharb commented Oct 12, 2022

Given that by far the easiest way is “only publish CJS”, and if you want named exports, “publish CJS transpiled from ESM” - what more would go in this document?

@lemanschik

This comment was marked as outdated.

@ljharb
Copy link
Member

ljharb commented Nov 27, 2022

@lemanschik i strongly disagree with that; a default export is what a module is; named exports are what module has, and most modules should be something. That’s a stylistic choice and not something node should be weighing in on. I, and the Airbnb style guide ftr, say that you should almost always do module.exports = function.

@lemanschik

This comment was marked as off-topic.

@ljharb
Copy link
Member

ljharb commented Nov 27, 2022

I have a number of projects that work perfectly compatibly with a build process, so I’m not sure about those reports.

@lemanschik

This comment was marked as outdated.

@lemanschik

This comment was marked as off-topic.

@lemanschik

This comment was marked as off-topic.

@lemanschik

This comment was marked as off-topic.

@lemanschik

This comment was marked as off-topic.

@lemanschik

This comment was marked as off-topic.

@lemanschik

This comment was marked as off-topic.

@ljharb
Copy link
Member

ljharb commented Nov 29, 2022

What is the benefit of doing all this when “just CJS” works everywhere?

@lemanschik
Copy link
Contributor

lemanschik commented Nov 29, 2022

@ljharb i am happy that you ask for example it makes total new forms of builds more enjoyable and compose able at present we have overall good results with opening component based ipc specifiers inside chromium and v8 it self. this leads to stuff like running the monaco editor as chrome extension directly then using workspaces and overrides and then directly use unmodified directly loaded modules out of the npm ecosystem in a way that i can patch and replace them or use single functions of them to compose something total diffrent

It is simply expressiv when i can write

const reused = import('anything').then(any=> Object.assign(any, { addOrpatch }))

and get all original source references without parsing generating and all that of tons of import maps in facts i am also working mean while on building the whole chromium platform in a browser in a virtual x86 vm running on wasm and other stuff also directly exposing remote filesystems for distributed builds via webrtc and such stuff it helps a lot if we do not need to add incremental build steps and can directly cache distributed incremental build results

note also collabing on MESON Build system written in ECMAScript that opens up building NodeJS inside the browser distributed.

we talk about projects with a lot of dynamic references and far more then some million well documented symbols.

@ljharb
Copy link
Member

ljharb commented Nov 29, 2022

@lemanschik @frank-dspeed i'm not sure that answers the question. CJS is equally composable; enjoyable is subjective. I agree that a build process remains required, but I haven't seen an advisable "no build process" setup anywhere yet, so i'm not sure why that's important.

@lemanschik
Copy link
Contributor

lemanschik commented Nov 29, 2022

@ljharb you will see it in action at present we use that all over simply via userland loaders but we would benefit when npm users would create packages in a way that we do not need to handle nodejs logic so we can depend on a nodejs indidpendent logic for the resolve. that allows us to do content based hashing and cascasing builds

so we can hot replace modules in large builds see it as fast distributed hot module reload helper in general we at present worked a lot on tooling to create indipendent specifier resolvers all over the ecosystem.

When NodeJS and the NPM System would agree on such thing we simply can save a lot of build time energie and frustrations for millions of developers.

For example if some one patches a single module in a stack of millions where some do depend of it that helps a lot.

and i mean with module real modules not this npm modules that contain 22 mb of redundant objects renamed. refatored

@lemanschik
Copy link
Contributor

maybe the best description what all that does solve is it reduces the need to implement own resolver implementations inside every package manager and stuff like typescript and vscode electron can share the resolve logic cross context.

and also our chromium ipc can share the resolve logic via remote pipe stdio imagin tesing your builds directly in the browser no need for input output directly fix bugs and hit run again in the same view.

@ljharb
Copy link
Member

ljharb commented Nov 29, 2022

Hot reloading all modules generically isn't a thing that's possible without engine cooperation, due to the nature of JavaScript itself, and node would require support from v8 to do it. Either way, this issue's document should be written to fit actual reality, not aspirational reality.

@lemanschik
Copy link
Contributor

@ljharb ok then your correct lets start with a doc that points out your observations always bundle to CJS and in some months we will revisit that so no one gets hurt by writing that up for no need.

@lemanschik

This comment was marked as resolved.

@ljharb
Copy link
Member

ljharb commented Nov 29, 2022

That would be a harmful recommendation, because then you'd be exporting a Promise for a Module object. ESM wrappers don't add value.

@lemanschik
Copy link
Contributor

hmm ok i guess as you sayed transpil everything there will be no edgecase where something needs to stay esm?

like scripts that use the import.meta.url on dynamic load and parse url parms to return a conditional module?

@ljharb
Copy link
Member

ljharb commented Nov 29, 2022

Correct, that's my understanding. The only thing is top-level await, and i've seen no reasonable use cases for that besides an application entrypoint.

@lemanschik

This comment was marked as off-topic.

@lholmquist
Copy link
Contributor

Found this on the node.js docs: https://nodejs.org/api/packages.html#dual-commonjses-module-packages

Does this cover what we wanted?

@frank-dspeed
Copy link
Contributor

@lholmquist it was not what we search for but it is today maybe the thing we search for let me explain:

Out of Historical Reason there where 1x Export per Package JSON then later with es2015 the package json got the import / export field which can store multiple exports.

This exports have also support for nested conditional exports thats the part that your posting.

So the Field is only supported by current tooling every tooling today supports them as far as i know. If not support can be added in less then a day.

the --condition flag lets you choose one set of imports/exports.

Also i can announce the landing of require esm inside NodeJS 22+

require('es-module.js')

works under a flag if the ESM does not contain top level await at last.

@lemanschik
Copy link
Contributor

lemanschik commented Jul 2, 2024

I would propose the following path forward now:

  • Create ESM-only packages and only use the module field. Call it a day.
  • Use the -r esm flag to require the ESM NPM module to activate user-land support for require(esm-module.js) with additional support for top-level await as a migration path.
  • From Node.js 22+ onward, migrate to --experimental-require-module and -r esm until the other flag works.
  • From Node.js 22.x+ when the experimental flag gets an opt-out flag, call it a day. We will have resolved the ambiguity. Top-level import can import ESM and CJS code, and require can do the same. While everything should get transpiled to ESM for analyzability and tooling.
  • should annoy people to produce only exports["string"] = assignments and do avoid module.exports as migration path also exports.name = any is total ok simple let them avoid module.exports = and we wre good to go

Special edge case electron and other runtimes consuming npm

There are cases where .cjs is needed they will simple need to create a cjs entrypoint inside that single application where they use the modules they should not publish that application but if they wish to publish it then they simple publish it with a post install transpil to cjs hook or again use the -r esm flag

ESM -r esm as migration path is green for

  • nwjs
  • electron
  • node-ts
  • tsc / typescript

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package-maintenance-agenda Agenda items for package-maintenance team
Projects
None yet
Development

No branches or pull requests

6 participants