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

feat: support "type": "module" #485

Closed
wants to merge 13 commits into from
Closed

feat: support "type": "module" #485

wants to merge 13 commits into from

Conversation

tmm
Copy link

@tmm tmm commented Jul 19, 2022

Adds support for ESM-only builds when "type": "module" is added to package.json.

  • Add support to init, validate, fix, dev, and build commands
  • Update and add tests
  • Update docs for "type": "module"
  • Add changeset for release

Closes #471

Happy to chat about this more or modify the approach. Some open questions:

  • Should this get added behind an experimental flag?
  • What version bump does this change receive?

@changeset-bot
Copy link

changeset-bot bot commented Jul 19, 2022

⚠️ No Changeset found

Latest commit: 04b8d32

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@codecov
Copy link

codecov bot commented Jul 19, 2022

Codecov Report

Merging #485 (04b8d32) into main (0c79455) will increase coverage by 0.10%.
The diff coverage is 97.91%.

@@            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              
Impacted Files Coverage Δ
packages/cli/src/entrypoint.ts 100.00% <ø> (ø)
packages/cli/src/messages.ts 83.33% <ø> (ø)
packages/cli/src/init.ts 80.00% <85.71%> (ø)
packages/cli/src/build/config.ts 83.78% <100.00%> (+1.43%) ⬆️
packages/cli/src/dev.ts 97.26% <100.00%> (+0.07%) ⬆️
packages/cli/src/package.ts 95.42% <100.00%> (+0.15%) ⬆️
packages/cli/src/utils.ts 100.00% <100.00%> (ø)
packages/cli/src/validate-package.ts 95.58% <100.00%> (+0.13%) ⬆️
packages/cli/src/validate.ts 91.76% <100.00%> (+0.30%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@emmatown
Copy link
Member

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

Comment on lines +1060 to +1061
"one/package.json": JSON.stringify({}),
"two/package.json": JSON.stringify({}),
Copy link
Member

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({
Copy link
Member

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

Copy link
Author

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?

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 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") {
Copy link
Member

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.
Copy link
Member

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 🤷

Copy link
Member

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
Copy link
Member

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?

Copy link
Member

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
Copy link
Member

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.
Copy link
Member

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

@Andarist
Copy link
Member

Yea, I think that "use it at your own risk" is a good strategy and a way for us to test things out.

@tmm
Copy link
Author

tmm commented Jul 29, 2022

@mitchellhamilton @Andarist thanks for the thoughts! Cool if I make some updates that better match the updated docs and your feedback?

@Andarist
Copy link
Member

I would say - go for it 😉

@emmatown
Copy link
Member

yep!

@emmatown
Copy link
Member

Just want to note that microsoft/TypeScript#50152 might affect what we want to do here

@Andarist
Copy link
Member

An explicit types condition for each package export should always work though, right? I don't imagine a world in which it wouldn't - that would be madness 🤣

@emmatown
Copy link
Member

emmatown commented Aug 10, 2022

Yeah, the design documented here would still work under "moduleResolution": "node16". One part of that issue though is essentially saying "unless people are actually using Node for module resolution (which a lot of the time, they are not) "moduleResolution": "node16" might be the wrong choice" so Preconstruct building packages that only work with "moduleResolution": "node16" might not make sense.

tmm and others added 3 commits August 13, 2022 18:59
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
@tmm tmm closed this by deleting the head repository Nov 12, 2022
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.

Preconstruct ignores "type": "module"
3 participants