cli icon indicating copy to clipboard operation
cli copied to clipboard

[BUG] npm forces download of peer dependencies even if these are present in NODE_PATH

Open mykola-mokhnach opened this issue 3 years ago • 13 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

This issue exists in the latest npm version

  • [X] I am using the latest npm

Current Behavior

I can observe moduleA in node_modules of moduleB

Expected Behavior

npm must not download a peer dependency if it is resolvable within NODE_PATH or any other method described in https://nodejs.org/api/modules.html#loading-from-the-global-folders

Steps To Reproduce

  1. Install moduleA globally (npm i -g moduleA)
  2. Install moduleB, where moduleA is set as a peer dependency locally: NODE_PATH=<node_modules_root_where_moduleA_installled> npm i moduleB
  3. Check node_modules folder of moduleB

Environment

  • npm: 8.16.0
  • Node.js: v18.5.0
  • OS Name: macOS
  • System Model Name: Darwin SL-1204 21.5.0 Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:xnu-8020.121.3~4/RELEASE_X86_64 x86_64
  • npm config:
; copy and paste output from `npm config ls` here

mykola-mokhnach avatar Aug 09 '22 13:08 mykola-mokhnach

NODE_PATH is highly discouraged and won't ever work in ESM. npm deals with the node_modules folder, and I wouldn't even expect a peer dep higher up the directory structure to satisfy the requirement.

ljharb avatar Aug 09 '22 18:08 ljharb

Thanks for your response @ljharb

What would be then a recommended/acceptable way for moduleB to load/see moduleA without duplicating it into moduleB's node_modules?

mykola-mokhnach avatar Aug 09 '22 18:08 mykola-mokhnach

npm link them in. Duplication isn't a problem, but an atypical resolution path is.

ljharb avatar Aug 09 '22 18:08 ljharb

I would be happy to link the stuff, but unfortunately such trick does not work if moduleB has moduleC dependency, whose postinstall scripts have peer dependencies on moduleA. As far as I can see such scripts are being executed in a temporary folder and cannot detect the link.

This is our original PR reverted where we tried the approach with a link: https://github.com/appium/appium/pull/17297/files

mykola-mokhnach avatar Aug 09 '22 18:08 mykola-mokhnach

Yes, every peer dep has to all be linked in everywhere, so they're shared.

ljharb avatar Aug 09 '22 19:08 ljharb

There's some sort of weirdness where npm link <thing> causes prepare to be run, regardless of how thing was installed. I suppose this is abuse, but:

  • cd foo
  • npm install thing
  • cd ../bar
  • npm link ../foo/node_modules/thing

prepare script of thing gets run. whoops, no dev deps!

boneskull avatar Aug 09 '22 23:08 boneskull

tbf, our use case is cursed

boneskull avatar Aug 09 '22 23:08 boneskull

Yes, that's the point of "prepare" - it'd be "prepublishOnly" if it was only for publishing and not for linking/installing from git. Dev deps should be present tho when linking, i'd expect?

ljharb avatar Aug 09 '22 23:08 ljharb

@ljharb in this case, they aren't, because the thing I'm trying to dedupe (via npm link) was installed via npm install thing. it was surprising behavior to see that something installed this way would try to run prepare. and yes, it should be prepare, because if you clone the repo and run npm install--with all the dev deps--prepare should run.

boneskull avatar Aug 10 '22 17:08 boneskull

I can trivially produce this behavior by doing

  1. cd tmpdir
  2. npm install lodash
  3. <edit lodash's package.json to have script {"prepare": "echo PREPARE"}>
  4. mkdir ../tmpdir2 && cd ../tmpdir2
  5. npm install ../tmpdir/node_modules/lodash --foreground-scripts

PREPARE will be echo'd

I guess I would not expect that to happen. either my reading comprehension is poor or maybe that should be noted in the lifecycle scripts docs. I would expect a postinstall to run there, but not a prepare

boneskull avatar Aug 10 '22 17:08 boneskull

I would expect anything that's installed from a local file path to have all dev deps present - iow, it's meant for installing from a project, not from a node_modules folder.

ljharb avatar Aug 10 '22 17:08 ljharb

right, which is what I meant by "abuse" above. 😜

boneskull avatar Aug 10 '22 17:08 boneskull

(though, that being said, I don't think this behavior is particularly obvious)

boneskull avatar Aug 10 '22 17:08 boneskull

Even though node will look in NODE_PATH to resolve require statements, npm itself doesn't take that into consideration when building up the local package tree. All it knows is the local node_modules folder.

NODE_PATH is supported by node.js for historic reasons, and is not taken into consideration by npm. There are no plans to start supporting it either.

wraithgar avatar Aug 15 '22 21:08 wraithgar