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

Fix exports map to support loading in Node esm #425

Merged
merged 4 commits into from
May 27, 2022
Merged

Fix exports map to support loading in Node esm #425

merged 4 commits into from
May 27, 2022

Conversation

fgnass
Copy link
Contributor

@fgnass fgnass commented May 24, 2022

Currently, preact-router fails to load in Node.js when it is imported from an ES module. Node correctly picks up the .module.js version as specified in the exports map, but loads it as cjs module because of the file extension. Here is a simple StackBlitz example that shows the current behavior.

This PR fixes the problem by changing the file extension to .mjs.

Copy link
Member

@rschristian rschristian left a comment

Choose a reason for hiding this comment

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

Thanks for spotting this, really appreciate it!

package.json Outdated
Comment on lines 7 to 8
"module": "dist/preact-router.module.js",
"jsnext:main": "dist/preact-router.module.js",
"module": "dist/preact-router.mjs",
"jsnext:main": "dist/preact-router.mjs",
Copy link
Member

Choose a reason for hiding this comment

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

Could we revert this? It might be overly cautious, but there's no (functional) reason to subject these to Node's extension semantics, as Node never reads them. As unlikely as it is to be an issue, there are some older tools that will read "module" but will trip over .mjs, making this a breaking change.

A post-build cp to .mjs (if I'm remembering Microbundle's key preference right) would be sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good point! 👍 Another way to fix this without modifying the filenames at all would be to add "type": "module". Maybe this would be even safer? And we would not need to copy the files.

Copy link
Member

Choose a reason for hiding this comment

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

"type": "module" would necessitate that all CJS entries switch to .cjs, which has even less support.

.mjs got some popularity and use a few years before .cjs & the current Node extension semantics were specc'd and solidified.

@fgnass
Copy link
Contributor Author

fgnass commented May 25, 2022 via email

@rschristian
Copy link
Member

There's a few odd Node versions that have some issues around "type" and export maps, but they're not really the issue.

The issue is that bundlers have consumed "module" and "main" for many years now, long before Node's .[cm]js semantics have existed. They may throw at seeing either of those extensions, as they predate them existing. react-native's Metro bundler is an example of such a tool, and just last week caused an issue when immer released a change containing "module": "<path>.mjs". Turns out Metro still has 0 support for .mjs: immerjs/immer#937

So it's best to keep those newer extensions locked behind "exports", as everything that can consume exports can handle those extensions just fine (or it's a bug if they cannot). For legacy fields, we have no guarantees that they'll support those extensions so we should try to keep them as .js to avoid breaking changes.

In this case, we add .mjs to "exports", keep all other ESM outputs as .js as Node's semantics do not need to apply to fields Node will never consume. Older versions of Node (out-of-date, mind you) will not consume "exports" and could throw at seeing .cjs for "main", so that too should be avoided (hence why "type": "module" would be bad).

@fgnass
Copy link
Contributor Author

fgnass commented May 26, 2022

I updated the branch so that only the "module" exports use the .mjs extension. I used the copyfiles devDependency as it was already in place.

package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
@rschristian
Copy link
Member

Silly issue; somehow the test suite picks up and uses the new preact-router.mjs over the CJS version, even in a require() call. Just needed to specify the extension.

@rschristian rschristian merged commit 1e0b9ad into preactjs:main May 27, 2022
@rschristian
Copy link
Member

Thanks for putting this together!

@fgnass
Copy link
Contributor Author

fgnass commented May 31, 2022

It was a pleasure. Are there any plans for a new release on npm?

@lkmill
Copy link

lkmill commented Jul 11, 2022

@rschristian would be really nice if this could be published to npm... i currently cant upgrade my projects to "true" esm due to this.,..

@rschristian
Copy link
Member

@lohfu Would probably recommend preact-iso over this anyways which does offer ESM, but I'll see if we can get a publish here.

@rschristian
Copy link
Member

Jovi released v4.1.0 earlier today which includes this PR. Hope it helps!

@lkmill
Copy link

lkmill commented Jul 12, 2022

@rschristian cheers!

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

Successfully merging this pull request may close these issues.

3 participants