-
-
Notifications
You must be signed in to change notification settings - Fork 338
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
Support for nodenext/node16 module resolution in TypeScript #1305
Comments
Can you show a minimal example project that'd lead to that error? |
After some more digging, I think the error is even more niche. In hindsight, I bumped into this building for ESM, not CommonJS. Here's a reproduction: https://github.com/benasher44/prosemirror-nodenext It seems to have something to do with adding the paths config to tsconfig.json. You can reproduce in that project by running |
One more notes as I'm narrowing in on this: in my main project, I can reproduce this with the |
Hey I think this might be a TS issue, which I have filed here. I'll keep you posted! |
I'm still confused about this scenario, but I found this comment: microsoft/TypeScript#46334 (comment) Specifically this part:
I think this is maybe where things are falling apart here. In this case, all packages only have a top-level types entry in the package.json, but it appears one needs to be added in exports as well. |
In other words, applying ProseMirror/prosemirror-view#131 to all prosemirror-* packages should fix it 🤞. There's no response from TypeScript folks yet on the issue I filed, but I'm not optimistic that the fix won't be either of these things:
|
@marijnh TS maintainers confirmed that the root issue is the lack of a types entry. A types entry needs to be added under exports to work properly.
|
Would it be possible to publish updates to prosemirror packages to fix? |
I still have not seen a minimal example producing this error, and if I try to set one up TypeScript doesn't complain and just uses the types from the top-level package field. |
What about the project I provided above? |
@marijnh I updated the sample project to be even more minimal. It now reproduces just importing prosemirror-view (doesn't go through tiptap). Issue seems to be when Latest comment in the TS issue covers the "why" about
|
Which one? prosemirror-nodenext? |
Yep! That one |
Ah, what you appear to be doing differently from my tests is that you don't have |
So that's actually an important difference. With nodenext and no "type": "module", this allows us to build in this intermediate mode where we can use .js suffixes in imports (for local imports) and generally update imports to prepare for ESM, but it's still technically building CJS output. The final migration step is adding "type":"module" to signal to TS that we're building ESM now. Hope that makes sense |
We're using prosemirror in a shared library that needs to exist in this intermediate mode for a brief period, while we update/prepare downstream consumers of the library |
Would it help if I opened PRs to the prosemirror packages to add the missing types entries (aside from the handful of cases where PRs already exist)? |
I really don't want to update all my packages to address a weird limitation in another tool. And, since many people are publishing CJS+ES module packages without using this annoying syntax, I'm still not convinced breaking all those is intentional behavior on the part of TypeScript. |
This issue has been raised many times with TS, and they've made it clear that despite some fallback behaviors that can make it work, you need to have a types entry in exports. I'm at a loss I suppose. If you won't even accept PRs if I agree to open all of them, I suppose we'll have to locally patch these packages |
👋 TypeScript maintainer here, just popping in to clarify a few things.
There are no fallbacks we can implement. The mitigation we might be able to consider is providing a more descriptive error message saying that the library’s package.json is probably underspecified or misconfigured.
When "exports": {
".": {
"import": "./dist/index.js", // Hit with "type": "module"
"require": "./dist/index.cjs" // Hit without "type": "module"
},
"./style/prosemirror.css": "./style/prosemirror.css"
}, When we see that the
So now we should be able to understand too that this isn’t the only solution—in fact, it’s not even the best solution. The best solution is to have both an
Things are very much working as intended on our end. We didn’t “break” anything here; typings for your CJS You’re right that many libraries needed updates after TypeScript rolled out support for these newer Node features. Most of the very popular ones are already updated thanks to users like @benasher44 who took the time to track down the issue and report problems and open PRs. And as I said, the best option is to add a colocated |
Thanks Andrew for the detailed walkthrough! Also happy to open PRs that add collocated types files. |
@marijnh any more thoughts here? I'd be happy to contribute the PRs that add the necessary support. |
Would having the build tool emit a symlink from the |
I'm not sure about symlinks, but if you're open to that direction, I confirmed that copying index.d.ts to index.d.cts does work! I think the effect would be the same (i.e. would create the file transparently in build tooling). |
"exports": {
".": {
"import": "./dist/index.js",
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.cjs"
}
},
"./style/prosemirror.css": "./style/prosemirror.css"
}, |
Yep that works! In my trial, I also tried with and without the top level |
Small update: if you remove the top-level "types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.cjs"
}
},
"./style/prosemirror.css": "./style/prosemirror.css"
}, |
This doesn’t look right, because there’s still only one .d.ts file. A single .d.ts file cannot represent two different modules (much less modules of different formats) at the same time. Assuming that this package.json has |
Yes. And that seems to work fine in my tests. |
It will result in false-positive TypeScript errors, because a Node |
Could you be more specific about this issue? TypeScript will emit the original |
A |
Which version of TS in which configuration does this? Because I've been sharing ESM-syntax declaration files between CJS and ESM in TS for a long time without trouble. |
All versions of TypeScript that support
None of them can resolve types from CJS files under // main.cts
import {} from "prosemirror-view";
|
This issue, as well as the arethetypeswrong links you posted, is about TS not being able to resolve a declaration file when using ProseMirror in a CommonJS project with nodenext module resolution. If I use If I use |
I just gave a one-line repro for a problem with
This is an unsupported combination. However, I explained earlier why the proposed structure is a problem for // main.cts
import {} from "prosemirror-view";
Now edit
|
Sure, but you didn't mention that reproducing involved compiling a CommonJS file with an So it seems adding a |
Imports/exports in CommonJS files get compiled to
It doesn’t work fine without it. Trying to represent a CJS and ESM module with a single
An ESM types file cannot correctly represent a CJS module. The ESM-ness of them is a property of the file extension and package.json
The same thing my repro showed, |
The thing is, it doesn't. With the current package.json structure, your test case compiles fine. I've been providing a single |
I believe I’ve provided you with a repro for issues with both the existing structure and the proposed structure. I would be happy to provide it again. For what it’s worth—and I apologize in advance because I always struggle to find a way to say this that doesn’t sound arrogant—I can promise that I can be trusted on this topic, as it’s been my primary focus working on the TypeScript team for the last year. But, I would rather package authors understand this for themselves than take my word for it, so please let me know what issues you would like to see demonstrated. However, do skim through my past messages, because I’m pretty sure I’ve covered the problems with the current package structure. |
That's what I'm trying to do, but the tool's behavior keeps throwing off any mental model I'm building on this. As I said, just compiling stuff with --module commonjs works with the existing package.json (which has declarations for ESM but not for CJS in its exports), but starts failing when I add a CJS export pointing to an ESM declaration. I realize that's not intended to work, but then why does it work without the export? |
Okay, that explains that — if it doesn't find an export it defaults to treating the dependency as untyped, if it does find an export it insists on it being valid. Now, from further experiments, it seems that I can provide a |
That’s correct. It’s usually safe to reuse ESM-generated declaration files for CJS declaration files, with a few caveats, from most likely to cause problems to least likely:
Checking for these things is why you’d have to do compilation work twice if you want to produce a safe dual build. (I’ve pretty much never seen any dual format library do this safely. Every build tool that exists for dual publishing leaves you in this tough situation with missing TypeScript types.) One thing you could do is copy the ESM declaration files and check that they’re at least error-free with |
Great, that should provide a workable solution then. |
Since TypeScript will complain if we try to use a single file for both ESM and CJS Issue ProseMirror/prosemirror#1305
I have published new versions of the prosemirror-* modules and the other dependencies under my control. I still feel, given the fact that ESM declarations work fine in most CJS contexts, that forcing all packages to include a separate .d.cjs file is a needlessly burdensome decision on the part of the TS team, but I guess it's what they decided on, so I went ahead and updated the build system to split out the duplicated file under the prescribed filename. |
Amazing, thank you! |
It looks like someone has started opening the PRs to support this in prosemirror-view and others. As folks with TypeScript repos migrate to ESM, switching to nodenext/node16 module resolution is the first step to migrating from CommonJS to ESM.
PRs (might not be exhaustive):
Without the necessary package.json pieces in place, you get errors like this when importing prosemirror packages in TypeScript repos using nodenext/node16 module resolution (and module still set to CommonJS):
The text was updated successfully, but these errors were encountered: