-
Notifications
You must be signed in to change notification settings - Fork 61
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
feat: support "type": "module"
#485
Conversation
|
Codecov Report
@@ Coverage Diff @@
## main #485 +/- ##
==========================================
+ Coverage 92.14% 92.25% +0.10%
==========================================
Files 32 32
Lines 1452 1484 +32
Branches 395 413 +18
==========================================
+ Hits 1338 1369 +31
- Misses 108 109 +1
Partials 6 6
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
Hey, thanks for opening this PR I’ve been thinking about what exactly Node ESM support should like in Preconstruct for a few years now and I’m still not sure what exactly the right answer is but I think it’d be worth trying something and to start with, I’m thinking “let’s only support all the new things and do things “correctly” and see how painful it is” would be an interesting starting place so I’ve pushed some changes to the documentation with the behavior I’m imagining. Let me know what you think. Apologies that this isn’t exactly a quick fix. cc @Andarist curious if you have any thoughts on this |
"one/package.json": JSON.stringify({}), | ||
"two/package.json": JSON.stringify({}), |
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.
It's probably a safe bet to assume that if a tool understands type: 'module'
then it also understands the package.json#exports
field. So assuming that, we could stop generating nested package.json
directories for entrypoints and rely on package.json#exports
entirely for entrypoints.
], | ||
}); | ||
if (!hasModuleType) | ||
configs.push({ |
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.
Those files were also acting as optimizations for node since reading from process.env
is slow. In ESM-only world, we should probably leverage development/production conditions in package.json#exports
to apply the same optimization. I know that @mitchellhamilton had some concerns related to that though
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.
Good with adding this in. @mitchellhamilton any thoughts?
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 it should be done as a separate change to this. The big thing IMO is that the syntax for it shouldn't be process.env.NODE_ENV
since you could have process.env.NODE_ENV === 'production'
but not have the production
condition set.
), | ||
]; | ||
if (entrypoint.json.module) { | ||
if (entrypoint.json.module || entrypoint.json.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.
general question - should we even bother supporting "mixed" packages where some entrypoints are of type module and some are of type commonjs? IMHO the added complexity isn't quite worth it
"module" | "commonjs"; | ||
``` | ||
|
||
> If you're just thinking "I want to write code with the native ECMAScript import and export syntax and have my code work for most people", you likely should not use this feature. This feature allows you to build packages that import Node.js ESM only dependencies and therefore only support being imported from ESM in newer versions of Node.js and some bundlers. |
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.
OTOH, it's not that uncommong to have self-contained repositories without external dependencies. In such it should be possible to use this feature without any "fear" and without deeper understanding of things. It's a fine line though and once you opt into this then you might end up with problems in the future, once you start adding external deps 🤷
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.
It's also not completely true that all deps have to be "esm only". It's very nuanced though and I'm not sure how to touch on that subject here, maybe we should link to a further explanation that could be put somewhere below? (you already touch the subject here)
|
||
- The [`exports` field feature](#exports) must also be enabled. | ||
- There are no entrypoint `package.json`s, the entrypoints are only specified in the `exports` field | ||
- The dist files for all entrypoints are in the `dist` directory in the root of the package |
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 could be true regardless of this new option. Could we align the behavior for regular builds?
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.
Yeah, I was thinking that as well
- The [`exports` field feature](#exports) must also be enabled. | ||
- There are no entrypoint `package.json`s, the entrypoints are only specified in the `exports` field | ||
- The dist files for all entrypoints are in the `dist` directory in the root of the package | ||
- No `main`, `module`, `browser` or `umd:main` fields are used |
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.
might be worth mentioning the available conditions here
- The dist files for all entrypoints are in the `dist` directory in the root of the package | ||
- No `main`, `module`, `browser` or `umd:main` fields are used | ||
- Module resolution internally follows [Node.js ESM module resolution](https://nodejs.org/api/esm.html#import-specifiers) which requires explicitly writing extensions in imports. This includes following `"moduleResolution": "nodenext"` in TypeScript which means writing `.js` to import a TypeScript file. | ||
- The default export of a CommonJS dependency(even if it provides ESM intended for bundlers) will be the whole exports object, not `exports.default` if `exports.__esModule` is set and otherwise the whole exports object which is the default behaviour in Preconstruct. |
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 could be rephrased somehow as it's hard to read and grok. I understand how it works but not sure if I would be able to fully understand it after reading just this point
Yea, I think that "use it at your own risk" is a good strategy and a way for us to test things out. |
@mitchellhamilton @Andarist thanks for the thoughts! Cool if I make some updates that better match the updated docs and your feedback? |
I would say - go for it 😉 |
yep! |
Just want to note that microsoft/TypeScript#50152 might affect what we want to do here |
An explicit |
Yeah, the design documented here would still work under |
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
Adds support for ESM-only builds when
"type": "module"
is added topackage.json
."type": "module"
Closes #471
Happy to chat about this more or modify the approach. Some open questions: