require('package/package.json') throws for me with a "package exports do not define subpath" error.
Was this intended by the feature? It seems to me like package.json is/should be a special case, and should be implicitly available by default.
Why is it exceptional?
It defines, for example, type: module, and exports, and a number of other things that affect behavior observed from outside the package.
@ljharb isn't the privacy feature of exports exactly to prevent such snooping?
on the code, yes - not imo on package.json itself. That's certainly an argument that could be made, ofc, but I think the ecosystem generally assumes that a package's package.json is requireable.
Wasn't one of the strongest reasons to support dual mode that package
consumers should not have to know about the internals / implementation of a
package they are importing?
With that in mind exposing package.json seems like something that should be
opt in by module author.
On Mon, Nov 25, 2019, 2:05 PM Bradley Farias notifications@github.com
wrote:
@ljharb https://github.com/ljharb isn't the privacy feature of exports
exactly to prevent such snooping?—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/nodejs/modules/issues/445?email_source=notifications&email_token=AADZYV7D2LHNWBYENPWDHADQVQOWJA5CNFSM4JRNHY3KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFDOWOY#issuecomment-558295867,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AADZYV4HGH3YFBGNDJ7JOIDQVQOWJANCNFSM4JRNHY3A
.
I think the spec is clear. Only the defined exports are available. If the package author wants package.json to be available, an export needs to be defined for it. I don't see why it should be a special case.
You can always work around it by using a non bare specifier path.
Alright, that seems reasonable; it just seemed strange to me, and I wanted to confirm it's intentional.
Assuming you have exports like this:
{
"module": "lib/index.mjs",
"main": "lib",
"exports": {
"import": "./lib/index.mjs",
"require": "./lib/index.js"
}
}
So that consumers can do this:
require('extract-files/package.json')
And also if in the case of experimental JSON modules:
import pkg from './package.json'
What needs to be changed?
It would be good for this to be documented at https://nodejs.org/api/esm.html#esm_conditional_exports as this could become a major gotcha as the community adopts conditional exports; for example see https://github.com/jaydenseric/apollo-upload-client/issues/186 .
I tried to figure it out reading the docs, but it's not exactly intuitive and it's good to be sure of the approach before rolling it out across dozens of packages.
you’d need to add a “./package” or “./package.json” key, or both, to exports.
I already knew that, what was not obvious was exactly how it should look.
So like this?
{
"module": "lib/index.mjs",
"main": "lib",
"exports": {
"import": "./lib/index.mjs",
"require": "./lib/index.js",
"./package": "./package.json",
"./package.json": "./package.json"
}
}
Are there any edge-cases or tradeoffs?
If you have just the "./package.json" one, what happens if someone does:
require('extract-files/package')
Will it see there is no .js file to resolve, then try to check for .json before, or after the exports rules? Is the "./package" one really necessary?
When the exports field exists, it won’t do normal resolution, so yes, it’s necessary.
When specifying the main along with other subpaths, the main is referenced by ".":
{
"module": "lib/index.mjs",
"main": "lib",
"exports": {
".": {
"import": "./lib/index.mjs",
"require": "./lib/index.js"
},
"./package": "./package.json",
"./package.json": "./package.json"
}
}
The exports map is a "source of truth" for resolution. Any further checking of the file system is not necessary to determine the final path.
Wanted to bump this thread because we now have some real-world experience with packages causing unexpected breaking changes when they release an "exports" map upgrade. Here's the work flow that seems to repeat across all of them:
I'd like to argue three points that I haven't seen brought up in this thread:
resolveRoot API, but given that no such workaround exists today I don't believe that this is a feasible solution unless it can be backported to Node v12 & Node v14.In the interest of preventing constant ecosystem thrash over the next 1+ years as this feature is adopted, I would recommend either making the package.json an implicit member of the export map until a resolveRoot API lands OR allowing require('*/package.json') and/or require.resolve('*/package.json') to always succeed in CJS (can continue to fail in ESM imo, since this is mostly focused on backwards compat).
@FredKSchott adding "exports" should always be semver-major at this point; with the deprecation of slash exports, it's impossible to make it be non-breaking. Adding package.json to "exports" doesn't fix that.
I don't think that adding either a package.json exception or a new API will happen in time to really address ecosystem pain. My gut feeling is that an ecosystem package implementing "get package metadata relative to url" would be the more reliable strategy. That would also remove any dependency on exact node versions the user is running.
FYI we may have uncovered a case where a userland fix is impossible (if package does not have . or ./package.json defined in their export map): https://github.com/snowpackjs/snowpack/discussions/1954#discussioncomment-203114. Would love any ideas to solve if this group has any
@FredKSchott you're correct; there's no robust solution there that I'm aware of when package.json, or a main, isn't specified in package.json.
While a package that lacks a "main" (or a . in exports) is exceedingly rare, it's certainly a nonzero occurrence.
Most helpful comment
I don't think that adding either a package.json exception or a new API will happen in time to really address ecosystem pain. My gut feeling is that an ecosystem package implementing "get package metadata relative to url" would be the more reliable strategy. That would also remove any dependency on exact node versions the user is running.