TypeScript: Surprising (or incorrect) priorities with package.json exports fields?
I’m trying a scenario of module: nodenext
with Vue.js. I hit a few issues with resolution of declaration files.
Here’s the current Vue.js declarations package.json
:
{
"name": "vue",
"version": "3.2.20",
"description": "The progressive JavaScript framework for buiding modern web UI.",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",
"types": "dist/vue.d.ts",
// ...
"exports": {
".": {
"import": {
"node": "./index.mjs",
"default": "./dist/vue.runtime.esm-bundler.js"
},
"require": "./index.js"
},
// ...
"./package.json": "./package.json"
}
// ...
}
However, referencing this in a project results in the following error:
import * as Vue from "vue";
// ~~~~~
// error
// Could not find a declaration file for module 'vue'. 'USER_DIR/hackathon/vue-proj/node_modules/vue/index.js' implicitly has an 'any' type.
// Try `npm i --save-dev @types/vue` if it exists or add a new declaration (.d.ts) file containing `declare module 'vue';`
This kind of makes sense - I think you could argue that this isn’t configured right for moduleResolution: node12
or later.
I was able to get this working by adding
"exports": {
".": {
"import": {
"node": "./index.mjs",
"default": "./dist/vue.runtime.esm-bundler.js"
},
"require": "./index.js",
+ "types": "./dist/vue.d.ts"
},
}
But the following DID NOT work.
"exports": {
".": {
"import": {
"node": "./index.mjs",
"default": "./dist/vue.runtime.esm-bundler.js"
+ "types": "./dist/vue.d.ts"
},
"require": "./index.js",
},
}
That part seems like a bug, right?
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 5
- Comments: 26 (18 by maintainers)
Commits related to this issue
- fix: Resolve types with TypeScript 4.5 Our lovely TypeScript is finding every possible way to break things on every release. I cannot find any documentation on "export maps" behavior, except these is... — committed to adonisjs/core by thetutlage 3 years ago
- Add `"types"` to `"exports"` When trying to use `fast-check` with TypeScript where `moduleResolution` is set to `Node12` or `NodeNext` we get the following error: > Could not find a declaration f... — committed to makeeno/fast-check by makeeno 2 years ago
- 🐛 Add `"types"` to `"exports"` (#2803) When trying to use `fast-check` with TypeScript where `moduleResolution` is set to `Node12` or `NodeNext` we get the following error: > Could not find a de... — committed to dubzzz/fast-check by makeeno 2 years ago
- build: fix failure to reference type definitions - ref: https://github.com/mswjs/msw/pull/1399 - ref: https://github.com/microsoft/TypeScript/issues/46334 — committed to mizdra/happy-css-modules by mizdra 2 years ago
- Add types to package.json exports Otherwise projects might complain, here's Vue: > src/App.vue:5:23 - error TS7016: Could not find a declaration file for module '@axiomhq/js'. '/Users/arne/Developer... — committed to axiomhq/axiom-js by bahlo a year ago
Likely there should be a warning if anything comes after a “default” condition.
I think this is fair, but it highlights 3 things to me
We should really give a more accurate error message
Probably needs to be word-smithed, but I think it would be helpful.
We need to be cautious in our messaging - over-eager people will try to use this in regular projects, and existing packages aren’t ready to accommodate them.
We probably should help package authors get ready for
node12
+ resolution modes.First of all, note that if
main
, or any part ofexports
points to a JavaScript file, and there is no correspondingtypes
key when resolving through that package.json path, the compiler will look for a declaration file next to that JavaScript file and use that. So a foolproof way to write a valid project structure and package.json file is just to publish your declaration files in the same place as their partner JS files and literally never writetypes
anywhere in your package.json. The only time an author has to start writingtypes
keys is if they break that structure, e.g. by putting JS in one directory and types in another. (This seems to be one of package authors’ absolute favorite ways to make their own lives harder. Maybe it’s the default for some third party tool like rollup?)To answer your other questions:
In
node16
/nodenext
, it is expected for the top-leveltypes
to be ignored in the presence ofexports
, because Node ignores the top-levelmain
in the presence ofexports
. (The point of a targetedmoduleResolution
is to maintain a parallel logic like this as perfectly as we can, so there is parity between what works at compile time and what works at runtime.)Nothing specifically for this problem, but tsconfig
paths
will probably let you hack something together?Please 🙏
My take is that it’s basically a coincidence that TypeScript worked reasonably well with bundlers for years without complaints. Part of the promise of bundlers was that you can develop your frontend projects like Node, using dependencies from npm, and so bundlers copied Node’s module resolution strategy, so
--moduleResolution node
was appropriate for TS, and then enhanced it in ways that TypeScript could sometimes model withpaths
or pattern ambient modules and otherwise felt out of scope. But now, Node and bundlers have both diverged away from--moduleResolution node
in meaningful ways and different directions. We shipped support for the direction Node went, but we effectively now have no support for what bundlers are doing. This is 90% of what I have been thinking about and working on for the last month. Hoping to publish a proposal this week. So no, this is not the right issue to address that, but there isn’t really a canonical issue for it. Stay tuned.Also, I think this issue can be closed?
^ fixed by #47007