-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
NodeNext module resolver thinks we're importing a module, while Node requires a CommonJS file #60663
Comments
It is how it's designed though and for good reasons. Your |
Yeah, yjs isn't doing this correctly. See e.g. https://arethetypeswrong.github.io/?p=y-websocket%402.0.4 |
Thank you for the swift response! I am an application developer rather than a library author, and your response seems tailored to a library author. For what it's worth, I had already posted an issue to yjs's repo, and hopefully I represented your point of view well there. While I work on TS projects, I will help to push culture and build tools towards TypeScript's model of correctness. But I am simply working on an application which uses libraries. If I change my |
This is a "garbage in, garbage out" situation. TypeScript can only accurately represent how Node will run if the packages give TypeScript accurate information about how they're configured. |
Okay, if you don't find this compelling then I'll close this out. |
Hi, I'm the author of Yjs, and several other open source projects that expose This whole topic is news to me. I can't keep up with the typescript development. It becomes harder and harder to stay up-to-date, so that typescript users can use my libraries. I believe your decision is too restrictive. Please keep in mind that it breaks a huge part of the typescript ecosystem. CJS and MJS are different languages. I understand why they need to be declared Can you please clarify why it is necessary to provide duplicate typescript In my case, the declaration files for my mjs and cjs bundles are identical. Why does typescript need yet another declaration file? Is it just so they have a different extension name? It's not like typescript types supports cjs-only features. There is no
You have all the information you need to deduct that the declaration file refers to a commonjs module. No garbage on my side. I care about bundle size, so I would prefer not to duplicate the declaration files. |
https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#notes-on-dual-emit-solutions covers some of this. The problem is that, in a single compilation, you could have two inbound module resolutions (one CJS and one ESM) land at the same .d.ts file, at which point outbound resolutions from that file differ in their resolution mode too, which means that the same type at the same source location could refer to two completely separate unrelated entities. This isn't even architecturally supported, but if it were, it would lead to some extremely bad developer experiences: while it can be the case that your module presents identical shapes in CJS and ESM, this isn't necessarily true of its dependencies, and if anything ever goes wrong here (which it will) then you could end up with errors that basically say "Type Foo (from line 13 in bar.d.ts) is not assignable to Type Foo (from line 13 in bar.d.ts)". |
To expand on the Ryan's last note - the structure of your runtime code creates a so-called dual package hazard. It might not be apparent immediately but types are prone to it too. Even though TypeScript is a structural type system at heart it supports some nominal typing. If your library exposes some class then: const { Cls } = require('lib')
const foo = new Cls()
if (foo instanceof (await import('lib')).Cls) {
// this won't ever be true with your setup
} You need to consider the same situation at the type level. This is essentially what "Type Foo (from line 13 in bar.d.ts) is not assignable to Type Foo (from line 13 in bar.d.ts)" means once you start considering classes with private members (compared nominally). Or |
Thanks for both of your explanations. So it's mostly about the file-names in error descriptions. Personally, I find this acceptable. You could still publish your recommendations to avoid this scenario. But I understand your point. I imagine that almost all projects emit identical declaration files for cjs and mjs files. So I hoped there would be a better solution than duplicating declarations. Maybe I should remove the cjs bundle altogether. If typescript declarations support nominal typings, then it makes sense to have duplicative declaration files. |
🔎 Search Terms
module resolver nodenext node16 commonjs module dual package error require import
Here are some similar issues:
exports
resolution uses fallback conditions, unlike Node #50762module
for the present and the future #55221🕗 Version & Regression Information
⏯ Playground Link
https://github.com/jthemphill/ts-cjs-repro
💻 Code
The
"lib"
package in my repo is modeled after the popular project https://github.com/yjs/yjs. Like yjs,lib
uses Rollup to build CJS and ESM versions of the package, then uses TypeScript to emit a declaration file.Its
package.json
has a conditional export:This enables the package to be either imported or required, whether from ESM or CJS files.
The
app
package represents my project, which I am trying to move from{"module": "commonjs"}
to the more correct{"module": "node16"}
. Itsindex.ts
file is two lines:You can reproduce the problem with
🙁 Actual behavior
node app/dist/index.js
will run the code successfully. But TypeScript claims that the code will crash:🙂 Expected behavior
TypeScript should not report an error here. Node is correctly importing
index.cjs
, and this file really does contain the values described inindex.d.ts
.Additional information about the issue
I would like to migrate my team's project from the
commonjs
resolver to the more correctnode16
resolver, but it is hard to sell teammates on this migration if the "more correct" resolver leads to a bunch of false positives until we also complete an ESM migration😕I understand why the module resolver thinks that
index.d.ts
describes a module file, as the nearestpackage.json
contains a"type": "module"
field. This doesn't help me migrate my project, though.The text was updated successfully, but these errors were encountered: