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 type resolution when using modern TypeScript module resolution #373

Closed
wants to merge 1 commit into from

Conversation

jrolfs
Copy link

@jrolfs jrolfs commented Feb 7, 2023

I'm gradually working towards ECMAScript modules in our design system monorepo. One of the first steps to enabling this is switching to moduleResolution: nodenext or moduleResolution: node16 in our TypeScript configuration.

I've run into a couple snags related to Contentlayer because it only ships ESM, but the only true blocker I've come across so far is that the types don't resolve properly in the generated code. Specifying default conditions in the exports field fixes this without having to make any source changes.

Before

Import Inference
before-tip before-inference

After

Import Inference
after-tip after-infer

@changeset-bot
Copy link

changeset-bot bot commented Feb 7, 2023

🦋 Changeset detected

Latest commit: c22978b

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

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

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package or glob expression "examples-*" is specified in the `ignore` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch.

@schickling
Copy link
Collaborator

Thanks a lot for this PR. I'd like to better understand this situation. Can you point me to some reading resources that explain the requirement to have both default and import exports definitions?

@schickling schickling added this to the 0.3.1 milestone Feb 13, 2023
@jrolfs
Copy link
Author

jrolfs commented Feb 23, 2023

To be honest, I don't have a complete grasp of all the nuance involved here, but I'll try to corroborate my experience with Node + TypeScript documentation when I have a chance.

What I do know is that setting TypeScript's moduleResolution setting to node16/nodenext makes TypeScript read from the package exports field (as part of enabling support for Node-native ESM in TypeScript). I'm still using CommonJS in this Next.js application with "moduleResolution": "node16" because Next.js itself is built with CommonJS. I'm making the moduleResolution switch across the host monorepo as the first step to enabling ESM.

I think what's happening is that when I was using the old "moduleResolution": "node" that TypeScript is less strict about interop, which allows importing the ESM-only Contentlayer packages to work at runtime, and TypeScript finds the definition files without reading from the exports field. Switching to the new module resolution mode prevents interop from working the same way because it's actually using the Native Node ESM resolution. For example, I could no longer import the useMDXComponent hook, which is fine because it's such a simple little hook. The blocking issue was that it broke the type definitions because it must be missing the import condition in the conditional exports field.

I think the ideal solution would be to add "official" CommonJS support to all packages. I might be able to help with this if you're interested in supporting it. Short of that, this change at least makes using the new resolution mode possible.

@jrolfs
Copy link
Author

jrolfs commented Feb 24, 2023

I just serendipitously came across this by way of a dependency upgrade:

I haven't had a chance to go through it more closely, but in skimming the conversation, it looks like a very similar issue. Perhaps they do a better job of explaining things?

@schickling
Copy link
Collaborator

Thanks a lot for your thoughts and explanations. I'm wondering whether instead of adding the default condition, we should instead replace the import condition with the default condition or probably even better: drop the object notation like so:

./source-files/schema": "./dist/source-files/schema/index.js",

Regarding CJS support in Contentlayer:

I agree in principle that ideally every package (incl. Contentlayer) is available both as CJS and ESM module. However, given that Contentlayer is using a fair amount of ESM-specific code (e.g. import.meta.url), it wouldn't be feasible for me (especially given my currently limited time capacity) to maintain both CJS and ESM versions.

@schickling schickling modified the milestones: 0.3.1, 0.3.2 Mar 27, 2023
@jrolfs
Copy link
Author

jrolfs commented Mar 29, 2023

Can you point me to some reading resources that explain the requirement to have both default and import exports definitions?

Heh, sorry, I think I kinda missed your point here in asking about the both part. Most of that was me explaining why we need default and that import alone doesn't work in this specific case. Switching to default altogether totally makes sense. I guess I was really just trying to make the smallest/safest change possible.

I'll update the pull request, thank you!

@schickling
Copy link
Collaborator

Sorry for the delay. I've published a prerelease "0.3.2-dev.3" with your changes. Would you be able to give it a try?

@schickling
Copy link
Collaborator

This should be addressed with the 0.3.2 release. 🎉

@schickling schickling closed this Apr 24, 2023
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.

None yet

2 participants