Repository navigation
Surprising (or incorrect) priorities with package.json exports fields? #46334
Description
Activity
- changed the title
[-]Surprising (or incorrect) priorities with export maps?[/-][+]Surprising (or incorrect) priorities with package.json export fields?[/+]on Oct 13, 2021 andrewbranch commented
on Oct 13, 2021 MemberMore actionsThis kind of makes sense - I think you could argue that this isn't configured right for
moduleResolution: node12or later.My assumption was that a top-level
typesshould merge with the.of an export map in order to continue offering typing support for the top-level import of all existing libraries that are already using export maps. I logged this at #46281.That part seems like a bug, right?
Nope. Is the referencing file cjs mode or is esm mode? An
importcondition is only going to be applicable for an esm mode import - so a cjs mode import (eg, an import in a js file in a package without type:module) isn't going to use theimportcondition for its imports - it uses therequirecondition instead.My assumption was that a top-level types should merge with the . of an export map in order to continue offering typing support for the top-level import of all existing libraries that are already using export maps. I logged this at #46281.
typesis a TS-specificmain, whichexportsblocks - I don't see why it shouldn't also blocktypes.Reacted by Anton Gilgurandrewbranch commented
on Oct 13, 2021 MemberMore actionsIs the referencing file cjs mode or is esm mode?
I missed that the
package.jsondidn’t have"type": "module". Daniel Rosenwasser (@DanielRosenwasser) what was the file extension of the importing file?DanielRosenwasser commented
on Oct 14, 2021 MemberAuthorMore actionsI was importing from a module (
.tsfile with"type": "module").DanielRosenwasser commented
on Oct 14, 2021 MemberAuthorMore actionstypesis a TS-specificmain, whichexportsblocks - I don't see why it shouldn't also blocktypes.I think this is fair, but it highlights 3 things to me
-
We should really give a more accurate error message
"A 'types' field was found in this package's 'package.json', but was not used because an 'exports' field was found and took priority over the top-level 'main' and 'types' field."
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.
Reacted by Andrew Branch and chocolateboyReacted by Anton Gilgur-
DanielRosenwasser commented
on Oct 14, 2021 MemberAuthorMore actionsIn any case, it's a bug that this one didn't work, right?
"exports": { ".": { "import": { "node": "./index.mjs", "default": "./dist/vue.runtime.esm-bundler.js" + "types": "./dist/vue.d.ts" }, "require": "./index.js", }, }defaultis going to be matched beforetypes, because thedefaultcondition is always set, and the object is ordered. (So you probably wanna list the type condition first, since it's not usually set except by TS)- changed the title
[-]Surprising (or incorrect) priorities with package.json export fields?[/-][+]Surprising (or incorrect) priorities with package.json exports fields?[/+]on Oct 15, 2021 DanielRosenwasser commented
on Oct 15, 2021 MemberAuthorMore actionsOof that's really confusing. Shouldn't
importtake priority overtypestoo though?The mistake is thinking they're prioritized - they're not. They're either on or off, and the first condition (in object insertion order) that is on is selected.
Reacted by Anton Gilgur11 remaining items
Should this impact the apps not using
node12and no explicittypeis defined inpackage.jsonfile?Coz, I have an application that breaks after upgrading to 4.5. Here is a sample repo to reproduce the issue. https://github.com/thetutlage/Typescript-4-5-regression
Happy to provide more info if required :)
- added a commit that references this issue
on Nov 21, 2021 andrewbranch commented
on Nov 29, 2021 MemberMore actionsHarminder Virk (@thetutlage) that’s #46770, which I believe is a bug—didn’t realize it was affecting non-
node{12,next}resolution modes. Thanks for the repro!andrewbranch commented
on Nov 29, 2021 MemberMore actionsActually #46770 has a couple different things going on so it’s hard to say what’s what. The issue you reproduced is caused by this:
TypeScript/src/compiler/moduleNameResolver.ts
Line 343 in 68bf5a5
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features: NodeResolutionFeatures.AllFeatures, conditions: ["node", "require", "types"] }; Type reference directives are always being resolved with
NodeResolutionFeatures.AllFeatures, which prevents us from looking attypesandmainhere:TypeScript/src/compiler/moduleNameResolver.ts
Line 1993 in 68bf5a5
if (!(state.features & NodeResolutionFeatures.Exports)) { ^ fixed by #47007
Reacted by Michael KrieseAlso I see
nodenextmode do not have a default types resolution, whenpackage.jsoncontains neithertypesnorexports, but aindex.d.tsis placed in package root, no types is resolved, is this intended?Yes, node12/nodenext has no special handling of index files.
- added a commit that references this issue
on Mar 17, 2022 - added a commit that references this issue
on Mar 17, 2022 It sounds like the end result of the conversation abve is that if a package has a top level
types, but also has a matchingexportsvalue with notypes, TS will not load any typings (though it could in theory check the matched JS specifier directly whenallowJSis set). Is that correct?If so, what advice will you give to those who consume packages like this? Declaring
typesbut notexports.{something}.typesis basically always an error, right? Is there some mechanism for me to depend on packagefoobut tell TS to either ignore itsexportsfield, or override it in some way? If not, does that just mean I have to get the library authors to fix their package or make a fork of it myself?Also: I don't know if it was brought up previously, but this behavior has a sort of impedance-mismatch with existing frontend bundlers --
webpackrespects theexportsfield today, but bunding TS for browser consumption through webpack means types are pulled from top leveltypes, not the matchingexportsmapping. Is this issue the right place to address that?Reacted by Anton GilgurDeclaring
typesbut notexports.{something}.typesis basically always an error, right?First of all, note that if
main, or any part ofexportspoints to a JavaScript file, and there is no correspondingtypeskey 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 writetypesanywhere in your package.json. The only time an author has to start writingtypeskeys 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-leveltypesto be ignored in the presence ofexports, because Node ignores the top-levelmainin the presence ofexports. (The point of a targetedmoduleResolutionis 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.)Is there some mechanism for me to depend on package
foobut tell TS to either ignore itsexportsfield, or override it in some way?Nothing specifically for this problem, but tsconfig
pathswill probably let you hack something together?does that just mean I have to get the library authors to fix their package
Please 🙏
but bunding TS for browser consumption through webpack
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 nodewas appropriate for TS, and then enhanced it in ways that TypeScript could sometimes model withpathsor pattern ambient modules and otherwise felt out of scope. But now, Node and bundlers have both diverged away from--moduleResolution nodein 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?
Reacted by Victorien ElvingerReacted by Anton GilgurExcellent answer as always, thanks Andrew. One or two follow-ups though:
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 write
typesanywhere in your package.jsonThis sounds like a sentence that should appear somewhere on a "for library authors" page in the TS handbook. Does it?
tsconfig
pathswill probably let you hack something together? / {"please" get authors to fix their package}This isn't a hypothetical. When Webpack started respecting
exports,import { ... } from "somelib/assets/file.css"started to throw an "is not exported from package" error. I have an open issue with a pretty popular library that is coming up on its second anniversary (!) while they deliberate how to fix it. At least for the asset, I can work around it by transforming the module specifier into an absolute path using Webpack'sresolve.alias. I think you're going to see a lot of new bug reports here in the next couple of months as library authors struggle to migrate toexportsand mess uptypesresolution in the process, if there's not an easy path for consumers to work around the issue.- added a commit that references this issue
on Sep 18, 2022 - added a commit that references this issue
on Jun 26, 2023
I'm trying a scenario of
module: nodenextwith Vue.js. I hit a few issues with resolution of declaration files.Here's the current Vue.js declarations
package.json:However, referencing this in a project results in the following error:
This kind of makes sense - I think you could argue that this isn't configured right for
moduleResolution: node12or 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?