DEV Community

Cover image for How to handle peer dependencies when developing modules
🦁 Yvonnick FRIN
🦁 Yvonnick FRIN

Posted on • Edited on • Originally published at yvonnickfrin.dev

How to handle peer dependencies when developing modules

What is a peer dependency and what is the problem with it?

Peer dependencies are a specific kind of dependencies really useful for reusable modules:

  • Ask user to install a dependency your module needs to work without specifying a version in particular
  • Prevents having multiple version of a same module in user's app node_modules
  • Reduce javascript files size to load on browser side particularly useful for mobile users

The problem with peer dependencies is npm and yarn don't install them at all. This is the right behavior for production purpose but when you are developing you might need to test your module in a host app. npm and yarn provide a command to achieve it called link that basically creates a symlink into the host app node_modules to your module source folder. It works fine but you also need to execute tasks in your module that needs these dependencies. For example, you might want to execute tests. Since they aren't present in your module's node_modules you will experience errors like this:

Cannot find module 'react' from 'index.js'
Enter fullscreen mode Exit fullscreen mode

yarn and npm don't provide tools to install peer dependencies for your development environment. There is an opened issue on yarn repository since 27 October 2016 but yarn has a special script called prepare that is executed after dependencies installation only on development mode maybe we could do something with it 🤔. Let's find a way to set up a work around!

Solve the problem

I made a repository with all the sources to reproduce the problem.

We have the following folder structure:

.
├── LICENSE
├── README.md
├── app
│   ├── node_modules
│   ├── package.json
└── lib
    ├── node_modules
    └── package.json
Enter fullscreen mode Exit fullscreen mode

First of all we will set up a link so the app's node_modules point to the lib folder.

You must install dependencies and you might also need to build your module first

cd lib
yarn link
cd ../app
yarn link lib
Enter fullscreen mode Exit fullscreen mode

At this point if you start your application you could use your module without any trouble. The problem appears when you try to execute things into your module folder as I said before like tests. To solve this we will use prepare script from yarn and the package install-peers-cli.

install-peers-cli is a cli that install peer dependencies of a package. It should be called after dependencies installation. Fortunately prepare is called after dependencies installation and only when you are developing so it won't install the peer dependencies when a user install your module.

First install install-peers-cli package:

yarn add -D install-peers-cli
Enter fullscreen mode Exit fullscreen mode

Then add the prepare script in your module's package.json and call install-peers-cli in it:

// package.json
{
  "scripts": {
    "prepare": "install-peers"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you could start anything that use your peer dependencies without experiencing errors. Unless your module uses React and his new addition Hooks...

For React using hooks

If you follow previous steps you should have an error with the following message "hooks can only be called inside the body of a function component". This error occurs when you have multiple copies of React in your node_modules folder. In this comment Dan Abramov explain the solution to this well known issue. Let's put it into pratice.

You should go in the react folder that is present in your module's node_modules and create a link. Then go back to your host app folder and use this link.

cd lib/node_module/react
yarn link
cd ../../../app
yarn link react
Enter fullscreen mode Exit fullscreen mode

Now the error should be fixed and you could use your module into your host app!

You're all set 🙌!

See a typo? Don't hesitate to open an issue or make a pull request on the article's repository

Top comments (24)

Collapse
 
arcanis profile image
Maël Nison • Edited

The yarn link implementation will, from the v2 onward, be able to properly follow peer dependencies. So if you're developing react-foo which has a peer dependency on react, you'll just have to go into your application, run yarn add react, yarn link ~/react-foo, and you'll be ready to go.

Collapse
 
yvonnickfrin profile image
🦁 Yvonnick FRIN • Edited

Really nice to hear Maël! I can't wait to test yarn v2. Is there an alpha version available or something similar?

Collapse
 
arcanis profile image
Maël Nison

We're developing it over at github.com/yarnpkg/berry - it's usable, but still developer preview for now 🙂

Collapse
 
dspacejs profile image
Daniel Spajic

Is there a way to get this to work with other peer dependencies? It works great when it's just react, but I have several peer dependencies (such as react-redux) which is causing conflicts. I've been messing around with this a lot and I'm having trouble getting it to work.

Collapse
 
yvonnickfrin profile image
🦁 Yvonnick FRIN

You tried to do the same trick described in the article for react (linked module dependency directly into your host app) ? I agree this is a bit messy. You can use yarn v2 to fix this problem (I guess). See Maël Nison's comment on this article. He explains how it works in yarn v2 and where you can install this new version 👍

Collapse
 
dspacejs profile image
Daniel Spajic

Oh that's a better solution, thanks. I'd use that but my team uses NPM. I ended up just installing the peer dependencies for our library into peer_deps/node_modules so it's not picked up by the app but can still be used by the library during development.

Collapse
 
sevenzark profile image
Dave M

Unfortunately, the linking solution for the React library did not work for me. The link seemed to set up just fine, but when I launched the consumer app I got the dreaded "Invalid hook call," same as before setting up the link to react.

Collapse
 
jusufa profile image
Angela J

It didn't work for me too in the beginning, but after I deleted the node_modules in the library, reinstall and did the linking again between the library and parent App, it works. Maybe something you could try.

Collapse
 
sevenzark profile image
Dave M

I do know to try this, but unfortunately it does not resolve the problem. I have run into this again while trying to develop a library. Very frustrating and link simply doesn't work to resolve this at all.

Collapse
 
jdnichollsc profile image
J.D Nicholls

Do you have experience with this error?

Only run install-peer-deps after install command. Skipping.

Thanks in advance!

Collapse
 
yvonnickfrin profile image
🦁 Yvonnick FRIN • Edited

Hi Juan David Nicholls Cardona. Did you have this message using install-peer-deps in the prepare script ?

Collapse
 
jdnichollsc profile image
J.D Nicholls

yes, but only using npm, it was fixed by using yarn instead, thanks!

Thread Thread
 
sevenzark profile image
Dave M

It's unfortunate that someone would make a package that only works with yarn! We can't just change our standards to use yarn just for one library. :(

Thread Thread
 
sevenzark profile image
Dave M

Okay, I got it to work with npm by appending the force flag (-f), so my script command is now "install-peers -f". Seems to be a bug in the library and there is an open github issue already about this.

Thread Thread
 
urrutiamartin profile image
Urrutia Martin

You can use "postinstall": "install-peers", this command run after the package is installed.
This work for me.

Collapse
 
enbonnet profile image
Ender Bonnet

Thanks you, i found that comment from Dan but i really don't understood how to do it

Collapse
 
yvonnickfrin profile image
🦁 Yvonnick FRIN

So it works fine for you now?

Collapse
 
enbonnet profile image
Ender Bonnet

Yes! It works!

Collapse
 
cristianmorenoruiz profile image
cristian-moreno-ruiz

I think a simpler solution is to add the peerDeps also as devDeps. I don't know if it is the best one, but is way simpler than what you propose here.

Collapse
 
sevenzark profile image
Dave M

That is one way to do it. One disadvantage, though, is having to manually keep dev and peer in sync. The advantage is not needing another library to install peers. So it's a matter of which trade-off you prefer. Myself, I'm more worried about the error-prone nature of manually duplicating two lists in my package.json so I'm standardizing on just going and using install peers. But I also understand why someone would want to avoid yet another library.

Collapse
 
yvonnickfrin profile image
🦁 Yvonnick FRIN

Thank you for sharing! I'll test it and a note about it in the article 👌

Collapse
 
yechenchao profile image
Peter Ye

Thanks for the useful guide! The original package read me didn't say much about this usage.

Collapse
 
pp profile image
Paweł Ludwiczak

🙌

Collapse
 
isabellachen profile image
Isa Chen

If its failing in the tests, put 'react' in both peer-dependencies and dev-dependencies.