-
Notifications
You must be signed in to change notification settings - Fork 7
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
improving esm support #102
base: master
Are you sure you want to change the base?
Conversation
size-limit report 📦
|
"compilerOptions": { | ||
"outDir": "dist", | ||
"target": "es5", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shifting from es5 to es2022 (likely will be a major
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is likely broken. You mention in the description of the PR that it introduces exports
but it has not been done. Even if it would, you are specifying type: "module"
in this package.json
and yet your "main"
points to a CJS file with .js
extension. That can't work without an additional package.json
"in-between".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Not adding the "exports"
was an oversight. How is it looking now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better, question @Andarist: what changes do you think I need to make? I find it's hard to know what is the right way to make sure things will work for folks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No pressure for you to reply though. I'll keep researching :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my reading of the node module documentation is that "type": "module"
should mean that I can use esm everywhere.
But I don't know if that is correct, or if that is only correct from a particular node version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I don't know if that is correct, or if that is only correct from a particular node version
it's correct since node 12.
if that is correct, I should be able to drop the cjs build and just have an esm build, and have the "main" field point to that
yes, if you don't want to maintain compatibility with the CJS consumers. It might still be good to use exports
in such a case as that limits what can be imported from your package (exports
is a strict allowlist for accessible files/specifiers). In fact, with that you don't even need main
as exports
can substitute it just fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say "cjs" consumers, do you mean folks on node using "cjs"? (require('bind-event-listener')
Would this package going esm prevent require('bind-event-listener')
usage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To take a step back: the intention of this PR is to give ESM consumers a better time. Right now esm consumers were relying on the bundler to wrap the cjs outputs with some lame code to get it working on esm.
Ideally folks using node or bundlers would just be able to have a good time 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like I have some reading to do: https://nodejs.org/api/packages.html#dual-commonjses-module-packages
Seems like I might be able to get away with just making this package esm only
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this package going esm prevent require('bind-event-listener') usage?
Yes. If you are going ESM-only then you make it impossible to require
this package. At least until this experimental feature lands: https://nodejs.org/en/blog/announcements/v22-release-announce#support-requireing-synchronous-esm-graphs
"sideEffects": false, | ||
"type": "module", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Andarist I have gone for the approach outlined in the Node conditional exports documentation.
So the package is listed as a "type: "module"
, but the when "require"
is being used we provide CommonJS files by using the .cjs
file extension
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am doing some testing now, but this seems really promising
package.json
Outdated
"exports": { | ||
"import": "./dist/esm/index.js", | ||
"require": "./dist/cjs/index.cjs", | ||
"types": "./dist/esm/index.d.ts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are two problems here:
- conditions are
exports
are matched from the top to the bottom. It doesn't make sense to havetypes
last here. This condition should usually be first. TS will still try to matchimport
andrequire
conditions when appropriate and it might never reachtypes
in a manifest like this (it would reach it here as a sibling.d.ts
file of yourimport
condition but that's coincidental) - TS will still think that this package can't be required. If you are shipping
.cjs
file here then you should also consider shipping.d.cts
file and create conditions structure like{ import: { types, default }, require: { types, default } }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome thanks. Do you know how I can get tsc to generate .d.cts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't 😂 Or rather, it generates its outputs based on your inputs. Your input is a .ts
file (not an .cts
file) so it won't guess that you want some extra .d.cts
file here. The thing to understand here is that TS won't really understand that you are trying to ship a dual package from a single set of sources so it won't help you anyhow to generate multiple outputs based on a single input.
You can either cp && mv
your .d.ts
or use some tool that handles this for you (tsup or tshy)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think i've done it! I am now using tsup
. It looks like the declaration files for esm and tsc are the same (which is what I was getting caught on before - i thought the cjs ones needed to be formatted differently).
Changes:
- now using
tsup
to builddist
(and declaration files) - "exports" now pointing to the correct types for given bundles (might not be strictly needed, but seems helpful)
- added
@arethetypeswrong/cli
to validate that the package is setup correctly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keen to know what you think @Andarist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attached the npm pack
for reference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now with tsup workaround: egoist/tsup#953 (comment)
package.json
Outdated
"check": "yarn check:typescript && yarn check:prettier && yarn check:dist", | ||
"check:prettier": "prettier --debug-check $npm_package_config_prettier_target", | ||
"check:typescript": "tsc --noEmit && tsc --noEmit --project ./test/tsconfig.json", | ||
"check:dist": "attw --pack . --ignore-rules no-resolution", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to add --ignore-rules no-resolution
as I haven't added a node10
mechanism. I could fix that by adding a "main" field that points to the CJS output - but I am not sure that I should
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added "main"
for now - but not sure if I should keep it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry this PR has changed so much 🤭
// https://github.com/egoist/tsup/issues/953#issuecomment-2132576167 | ||
// ensuring that all local requires in `.cjs` files import from `.cjs` files. | ||
// require('./path') → require('./path.cjs') in `.cjs` files | ||
name: 'fix-cjs-require', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised that u need any kind of fixup here. What would break if this was removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are using bind-event-listener
in a plugin for eslint@8
which was failing without this fix 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. I'd probably stay away from type: module
here since you are creating a dual package anyway. That would fix your issue but it would introduce the mirror issue in .mjs
files (it just perhaps would be less surprising there that you need to add extensions in your imports)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dang. I was keen to make cjs more of an after thought which I could drop. Maybe easiest to go the other way 😭
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, since u already have those workarounds in place - it's probably fine the way it is. I only have a vague recollection of some React Native versions having problems with non .js
files in main
but perhaps u can ignore it. Similarly, IIRC, old CRAs were loading .cjs
as files and not javascript sources (as in, they were replacing them with URLs to the generated assets, like they do with .png
files and more)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am thinking of leaving it is as "it is an esm module, but it has some cjs fallbacks when we need it". If it doesn't work out, we can go full esm.
Aside: publishing packages that "just work" is a nightmare right now 😭
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any objections @Andarist to merging in it's current state? My open thought is whether I should update "target"
to be "ES6" 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’d bump it even higher although this lib is so small that it probably doesnt use any es2016+ features anyway ;p
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I have target set at es2022
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't test it at all but the package.json configuration looks fairly OK to me now
"esModuleInterop": true, | ||
"allowSyntheticDefaultImports": true | ||
"skipLibCheck": true, | ||
"target": "es2022", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think i'll update this to be "target": "es6"
esm
outputs (through"module"
field and"exports"
)cjs
support through"main"
fieldes5
target toes2022
target (will likely need amajor
for this)esbuild
for building"engines"
topackage.json