Skip to content
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

module: expose getPackageJSON utility #55229

Conversation

JakobJingleheimer
Copy link
Member

@JakobJingleheimer JakobJingleheimer commented Oct 1, 2024

Finally got around to exposing one of the utilities we've long-discussed providing to users.

Currently, users (particularly library authors like yarn, who I believe originally requested this) have to re-implement this functionality.

I ran into this issue when building a codemod that needs to consume pjson.types / pjson.exports[…].types

JakobJingleheimer/correct-ts-specifiers#6

References:

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/loaders

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Oct 1, 2024
@JakobJingleheimer JakobJingleheimer added c++ Issues and PRs that require attention from people who are familiar with C++. semver-minor PRs that contain new features and should be released in the next minor version. labels Oct 1, 2024
doc/api/module.md Outdated Show resolved Hide resolved
@RedYetiDev RedYetiDev added module Issues and PRs related to the module subsystem. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. and removed lib / src Issues and PRs related to general changes in the lib or src directory. labels Oct 1, 2024
Copy link

codecov bot commented Oct 2, 2024

Codecov Report

Attention: Patch coverage is 97.36842% with 4 lines in your changes missing coverage. Please review.

Project coverage is 88.04%. Comparing base (103b843) to head (864b98a).
Report is 190 commits behind head on main.

Files with missing lines Patch % Lines
src/node_modules.cc 84.21% 0 Missing and 3 partials ⚠️
lib/internal/modules/cjs/loader.js 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #55229      +/-   ##
==========================================
- Coverage   88.23%   88.04%   -0.19%     
==========================================
  Files         651      652       +1     
  Lines      183863   186830    +2967     
  Branches    35824    35999     +175     
==========================================
+ Hits       162235   164501    +2266     
- Misses      14932    15562     +630     
- Partials     6696     6767      +71     
Files with missing lines Coverage Δ
lib/internal/modules/esm/resolve.js 96.35% <100.00%> (-0.19%) ⬇️
lib/internal/modules/package_json_reader.js 100.00% <100.00%> (ø)
lib/module.js 100.00% <100.00%> (ø)
src/node_modules.h 100.00% <ø> (ø)
lib/internal/modules/cjs/loader.js 96.26% <90.90%> (-1.16%) ⬇️
src/node_modules.cc 78.65% <84.21%> (+0.55%) ⬆️

... and 144 files with indirect coverage changes

@JakobJingleheimer

This comment was marked as resolved.

@panva

This comment was marked as resolved.

@richardlau
Copy link
Member

richardlau commented Oct 6, 2024

a require('../') in node-gyp where there is no index.js in the parent dir, so I'm not sure how this was ever working? does require() read from package.json (ex its "main" field)?

Yes, if neither ../index.js, ../index.json nor ../index.node was found. See https://nodejs.org/docs/latest/api/modules.html#all-together.


* `startLocation` {string} A fully resolved FS path or file URL to start looking
* `everything` {boolean} Whether to return the full contents of the found package.json
* Returns: {Object | undefined}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Returns: {Object | undefined}
* Returns: {Object|undefined}

Ditto to the rest

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the way the docs are formatted. There is no space between the |.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it actually matter or is it a style/consistency issue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the linter will throw an error, but it's documented in the Style Guide (https://github.com/nodejs/node/blob/main/doc/README.md#function-arguments-and-returns)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah let's keep them consistent, i.e. without spaces

* data: {Object}
* name: {string | undefined}
* type: {string | undefined}
* exports: string | string\[] | Record\<string, unknown> | undefined
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a valid type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno if the validator considers it one, but it absolutely valid 🙂

Copy link
Contributor

@aduh95 aduh95 Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we are not using TS nor JSDoc syntax, so it doesn't really make sense to claim it's "valid syntax" if the validator disagrees

Suggested change
* exports: string | string\[] | Record\<string, unknown> | undefined
* `exports`: {string|string\[]|Object|undefined}

lib/internal/modules/package_json_reader.js Show resolved Hide resolved
lib/internal/modules/package_json_reader.js Show resolved Hide resolved
lib/internal/modules/package_json_reader.js Outdated Show resolved Hide resolved
lib/internal/modules/package_json_reader.js Outdated Show resolved Hide resolved
lib/internal/modules/package_json_reader.js Show resolved Hide resolved
if (everything) {
const result = modulesBinding.getNearestRawParentPackageJSON(startPath);

return {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also need to be null prototyped

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aduh95 please advise 🙏

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it's user-facing

Comment on lines +231 to +233
* data: {Object}
* name: {string | undefined}
* type: {string | undefined}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto with the rest

Suggested change
* data: {Object}
* name: {string | undefined}
* type: {string | undefined}
* `data`: {Object}
* `name`: {string|undefined}
* `type`: {string|undefined}

* …
* path: {string}

Retreives the contents and location of the package.json closest to the supplied `startLocation`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Retreives the contents and location of the package.json closest to the supplied `startLocation`;
Retreives the contents and location of the `package.json` closest to the supplied `startLocation`;

* path: {string}

Retreives the contents and location of the package.json closest to the supplied `startLocation`;
this behaves identically to node's own lookup and consumption of package.json for a given module.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this behaves identically to node's own lookup and consumption of package.json for a given module.
this behaves identically to Node.js own lookup and consumption of `package.json` for a given module.

Comment on lines +63 to +65
...(name != null && { __proto__: null, name }),
...(main != null && { __proto__: null, main }),
...(type != null && { __proto__: null, type }),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null prototype is not useful here, spread only copies own properties

Suggested change
...(name != null && { __proto__: null, name }),
...(main != null && { __proto__: null, main }),
...(type != null && { __proto__: null, type }),
...(name != null && { name }),
...(main != null && { main }),
...(type != null && { type }),

@arcanis
Copy link
Contributor

arcanis commented Oct 10, 2024

Currently, users (particularly library authors like yarn, who I believe originally requested this) have to re-implement this functionality.

Locating the closest package.json isn't a concern for us since the PnP map is already aware of the location of all packages on disk. We're interested by the various resolution utilities private to the Node implementation. Basically things from here1:

https://github.com/yarnpkg/berry/tree/master/packages/yarnpkg-pnp/sources/node

As for this function specifically, a C++ implementation means it doesn't go through fs, so it doesn't go through any virtual filesystem layer that have been setup, so we'll have to patch it as well. I'd really prefer if Node.js could have one and only one interface through which filesystem calls are performed 😕

Footnotes

  1. and while some of them are loading files from the FS, we care more about what they do with it - it'd be perfectly ok if the exposed utilities required the user to provide the package.json content, for example

@JakobJingleheimer
Copy link
Member Author

As for this function specifically, a C++ implementation means it doesn't go through fs

This is already the case for node's package.json reading etc

@arcanis
Copy link
Contributor

arcanis commented Oct 10, 2024

This is already the case for node's package.json reading etc

Which is private logic. And that's exactly why we have the loader hooks: to rewrite parts of the resolution in a composable way (so that hook X can apply its changes on top of those performed by hook Y). I guess we don't want utility hooks, so I think the design of said utilities should avoid relying on "hidden inputs" (the filesystem).

@JakobJingleheimer
Copy link
Member Author

JakobJingleheimer commented Oct 10, 2024

@arcanis I'm not sure what you're getting at. It sounds like you're suggesting a drastic overhaul of node's pjson find behaviour. I'm not necessarily opposed to that, but that's super out of scope for this, and those sorts of under-the-hood changes can happen transparently for this utility.


I was totally fine merely exposing a "find" utility. That was my original plan (and then to perhaps subsequently expose a get which consumed the find), but was strongly dissuade by @anonrig due to the C++ implications. If those are not valid (or we just don't care about them), I can switch back to just the find.

That would be extremely frustrating given how much time this current one took (especially due to the "can you also tidy up …" requests, which actually took the vast majority of time for this PR).

@arcanis
Copy link
Contributor

arcanis commented Oct 10, 2024

To be clear, my primary point is: how does this work with hooks which don't work on the filesystem? Let's say I have --loader zip --loader ts and ts happens to call getPackageJSON, what will happen? It's a valid and preexisting use case, and it should be taken into consideration in the design.

It sounds like you're suggesting a drastic overhaul of node's pjson find behaviour.

I don't suggest a "drastic" overhaul: I don't mind that the native core resolution leverages native utilities. If you prefer that for performances, that's perfectly fine by me. That's why we have the hooks: to abstract all this logic in a pipeline that userland JS code can transparently integrate with (through nextResolve / nextFetch / etc).

The problem is when such native utilities are exposed through other access points. Then it becomes impossible to make them work with custom logic that hooks are supposed to allow, and they'll poison any other hook using them by making them incompatible as well.

What I'd tend to suggest would be to:

  • Keep the native implementation of the package crawl for the default resolver
  • But implement the same logic in JS just for the purpose of those published utilities, and use fs

It will lead to code duplication, but it'll ensure that the utilities will always have the exact same behavior as the default resolver on the Node.js version they run on (which wouldn't be the case if they were an independant package, for instance).

Alternatively, we need a reassurance that it'll always be possible to monkey-patch such utilities. But we all hate monkey-patching, and the hooks exist in part to make them a thing of the past.

@JakobJingleheimer
Copy link
Member Author

I think all of those points, whilst valid, are not strictly relevant to this PR: those are under-the-hood details that can happen before or after, transparently.

This PR (or a "find" util) is only to expose the result of what node is doing. What and how node is doing it can change, and this would just continue to surface that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. module Issues and PRs related to the module subsystem. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants