You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
npm v7 does not install linked packages dependencies #2339
npm v7 does not install linked local packages dependencies.
Expected Behavior:
In npm v6 the dependencies of a local linked package are installed. This means that if app has my-local-pkg as a dependency, running npm install generates app/node_modules folder and also my-local-pkg/node_modules (with its dependencies).
In npm v7, however, only app/node_modules is generated.
Steps To Reproduce:
I created the following repository to illustrate it. The README.md has the instructions on how to reproduce it.
Simplified, the steps are:
Add the following folder structure: ./app and ./my-local-pkg, each with their own package.json
Compare the two outputs, npm v7 does not install my-local-pkg dependencies, there is no ./my-local-pkg/node_modules folder generated.
Environment:
npm v7.1.2
Reacted by Raphael von der Grün, Rafael Pedicini, Alexander Schranz, nat, TommyRooryck, Daniel Mathis, Luca Rath-Heel, Johannes Wachter, Thomas Schedler, manuxi and 83 more
I'd also add that for me the expected behavior for local installs npm install ../some-package is to result in the same package-lock.json as installs from the registry with the only difference being the "resolved" field of local install pointing to ../some-package instead of to the registry (and other related metadata like "link": true).
This means that the dependencies of the locally installed package should end up in the top level of node_modules unless there is a version conflict. I believe that v6 puts all of the local install package's dependencies in its node_modules and not in the top level node_modules (hopefully v7 will change this).
Having the install behavior be consistent will make it easier to develop npm packages by being able to install the package locally in development to test it out and know that it will act the same as installing it from the registry when it's published.
git clone git@github.com:rafgraph/rollpkg.git
mkdir test&&cdtest
npm init --yes
# compare package-lock.json after running the two commands
npm i --save-dev ../rollpkg
# vs
npm i --save-dev rollpkg
Note that Rollpkg, a zero config build tool to create packages with Rollup and TypeScript, has Jest and ESLint as dependencies (and provides default configs for these), but let's the user call jest and eslint instead of wrapping those commands, which relies on those bins being in the top level of node_modules. The advantage of not wrapping the jest and eslint commands is that if the user needs to use a specific version of Jest or ESlint all they have to do is install that version and it will override the version provided by Rollpkg.
Reacted by NetherGranite, Chad Faragher, Glen and Rezgar Cadro
Not exactly peer deps, it's a default with optional override. I know this isn't a standard use case, but regardless a consistent package-lock.json would be nice to have.
@garciaalvaro if the path is set within the root directory we will resolve the dependencies in v7 but, if as you've shown, the path is outside the fs root then we treat it like a linked dep and won't resolve those deps. We understand that this is a change from v6.
@darcyclarke Thank you for the information.
If I understood correctly, the behaviour is intended. Is there some way to keep/emulate the previous behaviour and install those dependencies too?
@garciaalvaro I'm going to bring this up at our next Open RFC call & see what the consensus is in terms of supporting the old behaivour; I forgot the exact reasoning behind the change but I can imagine that it would have reduced some complexity & potentially cleaned up a number of issues/edgecases. I'll circle back here after the call on Wednesday.
All that said, we will consider making --install-links the default behavior in the next major version (v9) which you should start to see release candidates for in the coming weeks/months.
@darcyclarke--install-links sounds cool, if it wasn't for this bug 🙈: #4863
Reacted by Lucas Fischer, Disorder AA and Alexander R
@nlf To me it seems like this bug is not fixed. I've demonstrated my failed attempt to take advantage of this fix. Can you please tell me what I did wrong or why I cannot install? It's a nice self-contained repro, so hopefully someone can show me how to make this --install-links work correctly.
@nlf The most upsetting feature here is actually that npm link now REMOVES linked pkg deps that are already installed in the parent's node_modules, forcing me to explicitly add those peerDeps to the parent's package.json if i need to use them in my parent project.
Can this be somehow fixed/reverted/added as a feature?
Reacted by William C. Smith, Alexander R, nexsodev, Till Voß, Felix Treede, Shamik Bera, Neal Benner, Amit Beckenstein, brianbraunstein and Tyler Carson
Still facing issues with --install-link it seems to make my current locally linked package work but it then messes up with my other locally referenced ( not linked ) components/packages which are present in my parent's project directory. And also npm link is really a pain to work with as mentioned by @jasminen we need to install all local's peerDeps in parent.
Maybe a solution to it could be somehow making my linked package refer to its own node_modules for its deps rather than searching for those in parent's.
For what it's worth, I had to switch to using yarn to achieve what I want. The package.json remains the same, but I use yarn install in place of npm install because it handles the linked dependencies of local dependencies according to my expectations.
Reacted by caramboleyo, Patrick Sturm and Poweranimal
I still run into issues with npm >= 7, pnpm and yarn.
But today I tried bun from @oven-sh where example @Jarred-Sumner works on. They released 1.0 and seems correclty handle also dependencies of local dependencies and install them. In my case i was able to build our complex setup with it, which was currently only possible with npm@6. While we still use some kind of symlinks which simulates child paths so not sure if it will work in all cases. In my case bun run preinstall && bun install && bun run build did work.
yarn 1.22.21 makes a full copy (not a symlink) of the local package into the node_modules folder and installs all deps properly into the projects node_modules folder.
This was the last straw that made me switch to Deno two years ago, still not fixed.
Well done.
A locally linked package should NOT be treated any different than a package installed from a remote source. That means its deps need to be installed into the projects node_modules folder and nowhere else.
Reacted by Trang Le, DamagedCode, brianbraunstein, Chris Whong, Kristaps, Amit Beckenstein, Patrick Sturm, nickbrowne, Ivo Janssen, Anton Melser and 4 more
I thought this issue was causing problems in my scenario, but then I realized the npm behavior makes sense, given the nodejs module resolution behavior.
I think my scenario is pretty common so I'll elaborate in case it helps others.
Scenario
my_repo
├── a
├── b
├── c
a depends on b
b depends on c
a and b are not published externally, they're only used within the context of my_repo.
c could be published externally (or it could also be a remote package, I just made it local here because it was easy for testing, this is irrelevant though).
Key requirements: fast refresh/hot reload/etc features (like nextjs fast refresh), must work for both packages (even if running within a local docker container, which requires some volume mounts/etc).
"Set resolved to the real path of resolved, maintaining the same URL querystring and fragment components".
So this means that symlinks are resolved before looking for node_modules directories.
What this means is the following works:
my_repo
├── a
│ └── node_modules
│ └── b -> ../../b
├── b
│ └── node_modules
│ └── c -> ../../c/
└── c
but the following does NOT work:
my_repo
├── a
│ ├── a.mjs
│ └── node_modules
│ ├── b -> ../../b
│ └── c -> ../../c/
├── b
│ └── b.mjs
└── c
└── c.mjs
Note that this has nothing to do with npm, just nodejs.
That means if you want to symlink from a to b to support "fast refresh", you're also stuck making sure that b has its node_modules setup correctly (i.e. npm install has been run within b).
Thoughts
One could argue that cd a; npm install should setup a/node_modules and a/package-lock.json to look the same as if b was a remote package, I thought this at first too. However, if you say --install-links=false, you're specifically requesting a symlink, which means npm knows nodejs will use b/node_modules when doing lookups for b dependencies.
Setting up a/package-lock.json to include b dependencies would then be odd and problematic. Imagine a2 is just like a, it also depends on b.
You then have 3 different package-lock.json files that all cover b dependencies, and they could potentially conflict, meanwhile you only have 1 node_modules folder containing the dependencies that will actually get used (the one inside of b).
You're working on a, you tweak b, all is good, "fast refresh" is working. Then 4 days later you go to work on a2 and you forgot you tweaked b and changed its dependencies. The symlink a2/node_modules/b -> ../../b is still there, so the code works fine, utilizing the forgotten tweak and dep change, but your a2/package-lock.json is now incorrect. Because a2 is working via the symlink, you may never remember to update the package-lock.json, you might even check it into your repo.
Given this, I can see why npm behaves like it does when requesting symlinking.
encountered this old chestnut again today. the following workaround seems to be working for now:
so my top-level project has installed my own dependency called @benev/toolbox which has its own dependencies (like lit and @dimforge/rapier3d)
first, i install my dependency's latest published version with npm i @benev/toolbox@latest -- thus, now npm properly puts the subdependencies in my node_modules
❌ but if i run npm link ../toolbox to use my local copy which i'm actively working on, npm is deleting the subdependencies -- so i do NOT use npm link
understand, that this works because i still have the subdependencies from the ordinary npm i
if you put this in a package.json script, don't forget to escape the double quotes like \"
🤔 this works well, up until the point you need to test changes to the subdependencies of the symlinked package, in which case, you'll need to dive down a rabbit hole of doing your own funky linking
🧙♂️ you may consider at this point doing all your manual funky local symlinking in an npm script so it's reproducible, ie, i do npm i && npm run devlinks -- maybe even the "prepare" script if you're zany
Activity
raphinesse commentedon Dec 16, 2020
Same behavior with
npm@7.2.0rafgraph commentedon Dec 20, 2020
I'd also add that for me the expected behavior for local installs
npm install ../some-packageis to result in the samepackage-lock.jsonas installs from the registry with the only difference being the"resolved"field of local install pointing to../some-packageinstead of to the registry (and other related metadata like"link": true).This means that the dependencies of the locally installed package should end up in the top level of
node_modulesunless there is a version conflict. I believe that v6 puts all of the local install package's dependencies in itsnode_modulesand not in the top levelnode_modules(hopefully v7 will change this).Having the install behavior be consistent will make it easier to develop npm packages by being able to install the package locally in development to test it out and know that it will act the same as installing it from the registry when it's published.
For example, with Rollpkg:
Note that Rollpkg, a zero config build tool to create packages with Rollup and TypeScript, has Jest and ESLint as dependencies (and provides default configs for these), but let's the user call
jestandeslintinstead of wrapping those commands, which relies on thosebins being in the top level ofnode_modules. The advantage of not wrapping thejestandeslintcommands is that if the user needs to use a specific version of Jest or ESlint all they have to do is install that version and it will override the version provided by Rollpkg.ljharb commentedon Dec 20, 2020
@rafgraph for that, jest and eslint should be peer deps - and linking with peer deps means you have to link the peer deps also.
rafgraph commentedon Dec 20, 2020
Not exactly peer deps, it's a default with optional override. I know this isn't a standard use case, but regardless a consistent
package-lock.jsonwould be nice to have.darcyclarke commentedon Jan 15, 2021
@garciaalvaro if the path is set within the root directory we will resolve the dependencies in v7 but, if as you've shown, the path is outside the fs root then we treat it like a linked dep and won't resolve those deps. We understand that this is a change from v6.
garciaalvaro commentedon Jan 22, 2021
@darcyclarke Thank you for the information.
If I understood correctly, the behaviour is intended. Is there some way to keep/emulate the previous behaviour and install those dependencies too?
darcyclarke commentedon Jan 26, 2021
@garciaalvaro I'm going to bring this up at our next Open RFC call & see what the consensus is in terms of supporting the old behaivour; I forgot the exact reasoning behind the change but I can imagine that it would have reduced some complexity & potentially cleaned up a number of issues/edgecases. I'll circle back here after the call on Wednesday.
83 remaining items
fabb commentedon Sep 10, 2022
@darcyclarke
--install-linkssounds cool, if it wasn't for this bug 🙈: #4863wyckster commentedon Sep 23, 2022
@nlf To me it seems like this bug is not fixed. I've demonstrated my failed attempt to take advantage of this fix. Can you please tell me what I did wrong or why I cannot install? It's a nice self-contained repro, so hopefully someone can show me how to make this
--install-linkswork correctly.jasminen commentedon Dec 27, 2022
@nlf The most upsetting feature here is actually that
npm linknow REMOVES linked pkg deps that are already installed in the parent's node_modules, forcing me to explicitly add those peerDeps to the parent's package.json if i need to use them in my parent project.Can this be somehow fixed/reverted/added as a feature?
drik98 commentedon Dec 27, 2022
Facing the same issues as @jasminen
mryellow commentedon Apr 19, 2023
I'm still finding
npm linkto be an absolute pain to work with. Hours spent chasing non-issues.shamik-fw commentedon Jun 13, 2023
Still facing issues with
--install-linkit seems to make my current locally linked package work but it then messes up with my other locally referenced ( not linked ) components/packages which are present in my parent's project directory. And alsonpm linkis really a pain to work with as mentioned by @jasminen we need to install all local's peerDeps in parent.Maybe a solution to it could be somehow making my linked package refer to its own node_modules for its deps rather than searching for those in parent's.
wyckster commentedon Jul 6, 2023
npm is still broken as far as I'm concerned.
For what it's worth, I had to switch to using
yarnto achieve what I want. The package.json remains the same, but I useyarn installin place ofnpm installbecause it handles the linked dependencies of local dependencies according to my expectations.alexander-schranz commentedon Jul 6, 2023
@wyckster interesting I failed with yarn also what version are you using of yarn?
wyckster commentedon Jul 12, 2023
@alexander-schranz
Yarn will correctly install the example in my repro. I just confirmed this to be true with the node:19 docker image: node v19.9.0 and yarn 1.22.19.
Whereas npm v9.6.3 (which comes with that same docker image) still fails to install local dependencies of local dependencies correctly.
alexander-schranz commentedon Sep 11, 2023
I still run into issues with
npm >= 7,pnpmandyarn.But today I tried
bunfrom @oven-sh where example @Jarred-Sumner works on. They released 1.0 and seems correclty handle also dependencies of local dependencies and install them. In my case i was able to build our complex setup with it, which was currently only possible withnpm@6. While we still use some kind of symlinks which simulates child paths so not sure if it will work in all cases. In my casebun run preinstall && bun install && bun run builddid work.Fix npm install command (#4)
caramboleyo commentedon Dec 23, 2023
yarn 1.22.21 makes a full copy (not a symlink) of the local package into the node_modules folder and installs all deps properly into the projects node_modules folder.
This was the last straw that made me switch to Deno two years ago, still not fixed.
Well done.
A locally linked package should NOT be treated any different than a package installed from a remote source. That means its deps need to be installed into the projects node_modules folder and nowhere else.
brianbraunstein commentedon Jan 11, 2024
I thought this issue was causing problems in my scenario, but then I realized the npm behavior makes sense, given the nodejs module resolution behavior.
I think my scenario is pretty common so I'll elaborate in case it helps others.
Scenario
adepends onbbdepends oncaandbare not published externally, they're only used within the context ofmy_repo.ccould be published externally (or it could also be a remote package, I just made it local here because it was easy for testing, this is irrelevant though).Relevant Nodejs module resolution behavior.
The key is the phrase "real path" in the following: https://nodejs.org/api/esm.html#resolution-algorithm-specification section
ESM_RESOLVE.7.4which says:So this means that symlinks are resolved before looking for
node_modulesdirectories.What this means is the following works:
but the following does NOT work:
Note that this has nothing to do with npm, just nodejs.
That means if you want to symlink from
atobto support "fast refresh", you're also stuck making sure thatbhas itsnode_modulessetup correctly (i.e.npm installhas been run withinb).Thoughts
One could argue that
cd a; npm installshould setupa/node_modulesanda/package-lock.jsonto look the same as ifbwas a remote package, I thought this at first too. However, if you say--install-links=false, you're specifically requesting a symlink, which means npm knows nodejs will useb/node_moduleswhen doing lookups forbdependencies.Setting up
a/package-lock.jsonto includebdependencies would then be odd and problematic. Imaginea2is just likea, it also depends onb.package-lock.jsonfiles that all coverbdependencies, and they could potentially conflict, meanwhile you only have 1node_modulesfolder containing the dependencies that will actually get used (the one inside ofb).a, you tweakb, all is good, "fast refresh" is working. Then 4 days later you go to work ona2and you forgot you tweakedband changed its dependencies. The symlinka2/node_modules/b -> ../../bis still there, so the code works fine, utilizing the forgotten tweak and dep change, but youra2/package-lock.jsonis now incorrect. Becausea2is working via the symlink, you may never remember to update thepackage-lock.json, you might even check it into your repo.Given this, I can see why npm behaves like it does when requesting symlinking.
chase-moskal commentedon Jul 4, 2024
encountered this old chestnut again today. the following workaround seems to be working for now:
@benev/toolboxwhich has its own dependencies (likelitand@dimforge/rapier3d)npm i @benev/toolbox@latest-- thus, now npm properly puts the subdependencies in mynode_modulesnpm link ../toolboxto use my local copy which i'm actively working on, npm is deleting the subdependencies -- so i do NOT usenpm linkrm -rf node_modules/@benev/toolbox && ln -s "$(realpath ../toolbox)" node_modules/@benev/toolboxnpm i\"npm i && npm run devlinks-- maybe even the "prepare" script if you're zanyalesmenzel commentedon Sep 20, 2024
@ljharb Hi, this is still broken on latest NodeJs version 22.9 / npm 10.8.3.