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

attempt fix typescript modules error for runno runtime #324

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jonathanpv
Copy link

@jonathanpv jonathanpv commented Mar 5, 2025

@taybenlor
Copy link
Owner

This seems right, but I can't find good documentation on what the correct thing to do is.

This stackoverflow link suggests this approach is not enough, and the import and require need to be split: https://stackoverflow.com/questions/58990498/package-json-exports-field-not-working-with-typescript

Can you find some documentation on this?

We should also update all package.json files in the repo if this change is necessary, not just this one.

@agilgur5
Copy link
Contributor

agilgur5 commented Mar 7, 2025

Can you find some documentation on this?

I can help answer this 🙂

TS's docs on publishing types are here: https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html. They seem to not have been updated with regard to package.json#exports (aka Node.js "export conditions"), but the TS 4.7 release notes detail it: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#packagejson-exports-imports-and-self-referencing. The compilerOptions documentation also has some details on the consuming side: https://www.typescriptlang.org/docs/handbook/modules/reference.html#node16-node18-nodenext.

Node's own docs on this are here: https://nodejs.org/api/packages.html#exports. That links to more detailed docs that involve all the different permutations. Given the current usage, "conditional exports" and "subpath exports" are particularly relevant.

This stackoverflow link suggests this approach is not enough, and the import and require need to be split: https://stackoverflow.com/questions/58990498/package-json-exports-field-not-working-with-typescript

Normally this is specifically in the case where you have different, "conditional" exports for CJS (require) vs MJS (import) formats.
The TS 4.7 release notes appear to add additional stipulations here:

It’s important to note that the CommonJS entrypoint and the ES module entrypoint each needs its own declaration file, even if the contents are the same between them. Every declaration file is interpreted either as a CommonJS module or as an ES module, based on its file extension and the "type" field of the package.json, and this detected module kind must match the module kind that Node will detect for the corresponding JavaScript file for type checking to be correct. Attempting to use a single .d.ts file to type both an ES module entrypoint and a CommonJS entrypoint will cause TypeScript to think only one of those entrypoints exists, causing compiler errors for users of the package.

So this answer appears to be most correct, where they are split.
Why adding a single subpath types export worked may be for a few reasons:

  1. usage of "moduleResolution": "bundler" (per Recommended way to get type declarations in a modern vite / typescript project #323 (comment)) and differences between Vite / esbuild and tsc
  2. It might only cause an issue if you try to simultaneously do a CJS import; i.e. the error condition might not have been hit and this might be sufficient in most cases
  3. other wackiness, non-spec approaches, graceful fallbacks, etc; I'm honestly not sure

Also, in my experience, if you don't have subpath exports, then plain top-level types seems to work fine. So the subpath "." may be contributing to this.


If you're confused by this, well, I feel like I get confused every time I look at this and I help maintain a TS compiler integration (and maintained and contributed to lots of other TS tooling in the past, including the TS docs) 😅
That being said, it is easier to grok the less features you use; conditional exports, subpath exports, and TS support all add complexity to the scope, especially when used together, and all the fallbacks for backward-compat add some more too.

To add to the confusion, Node's support for ESM in the module VM API was still experimental last I checked and so was blocking test runners like Jest jestjs/jest#9430 from natively supporting it too (there are workarounds)

Tbh, it's a lot easier to only support newer, LTS versions of Node that all support ESM as a standard spec (see also https://deno.com/blog/commonjs-is-hurting-javascript, https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). Backward-compat is hard and confusing, so big respect to all those who attempt it, and I extensively try to do so too, but even I'm planning to move my supported libraries to pure ESM in breaking changes eventually to simplify builds and reduce the maintenance burden, especially as older Node is now out-of-support.

@taybenlor
Copy link
Owner

Thanks so much @agilgur5 - those links were very useful.

Okay so it seems like the right fixes are either:

  • add deeper settings inside the "exports" field to provide the types and the default for each of require and import

Or

  • delete the "exports" field

Is that right? If that's the case deleting seems to be the right move.

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