Yesterday, I got bit by an interesting caveat of
npm link due to how the module resolution algorithm works in
node and (with plugins) in your favourite bundler. This post documents what I learnt by debugging the issue.
We have a library
libA at path
~/libA and a project
myApp that consumes
libA at path
~/myApp. Now say that you want to test
myApp with a local version of
libA to see if things work fine before cutting a new release of
How do we achieve this, we think? 🤔
Well, one way to do so is by copying
libA to somewhere in the directory structure of
~/myApp. Well, hmmm, no, that doesn't sounds right. Hmm 🤔
Wait a second. Why not use the wonderful
We choose to do so by
~/libA and running
npm link. Following this we
~/myApp and run
npm link libA to (sym)link
~/myApp/node_modules/libA to your local
~/libA. You run
myApp, it all works fine and you're a happy cat. 😼
libA has a peer dependency on
graphql (or any package). This means that the consumer of our library must install
graphql in their application. So we go into
~/myApp and run
npm install graphql. Now you run the application as always but, oh no!
myApp fails to run! The logs on the screen say that
libA was looking for
graphql but didn't find it! What gives?! 🙀
We sit, then stand, then just lie there, staring at the terminal. I mean, we did everything right, no? We installed the
graphql peer dependency in
~/myApp as instructed. So, really, what in the actual fruit basket?! 😾
Well, turns out, we got bit by a small lack of understanding on our part combined with the module resolution algorithm of your favourite bundler/
node/what have you.
When we run
npm link libA in
~/myApp/node_modules/libA is (indirectly) symlinked to our local
~/libA. Now, when we run
~/myApp and the
require statement is encountered, the file given by the
module key (depending on your 🤓 bundler) in
~/libA/package.json is loaded. Let's assume that the key points to
~/libA/dist/libA.cjs.js. This file, in turn, tries to
graphql. To do so,
node or your bundler, does the look up roughly as follows:
graphqlis a core module. Nope, it isn't. 🤷🏻♀️ Go to next step.
~/libA/dist/node_modules. Found it? No? Ok, next step then. 👷🏼♀️
~/libA/node_modules. It isn't there either! That's because its a peer dependency and isn't automatically installed when we run
semiiii enlightenment 👼
This process continues, one directory higher at a time in the hierarchy, at every step till it gives up and ends.
But, Mudit, why not just install the peer dependency in
npm install graphql --no-save and move on with our lives? Well, that defeats the purpose Jimmy, that's why!
In anycase, the biggest reason this all happens is right there in front of us — the module resolution starts off in the wrong directory! Well, not really wrong but wrong for us! Bad computer!
We notice that all the look ups are happening relative to
~/myApp where we already did install
graphql! This is due to the fact that
npm link creates symbolic links! And that's the root cause, Jimmy! Due to the fact that
npm link libA creates a symlink inside
~/myApp/node_modules (indirectly) to
~/libA, the module resolution algorithm (correctly) uses the real location of
libA for the look up of
totally 100% moksha level enlightenment 👼🏼++
Well that was quite a journey. At least for me I'd say heh. It had me scratching my head for a bit and hopefully it helps someone else out there.