loading...
Blue Tomato Dev

What are those PeerDependencies in a NodeJS project?

manpenaloza profile image Manuel Penaloza Updated on ・4 min read

PeerDependencies, one of the terms that brought up confusion at least to me when I got a PeerDependency warning in my terminal like the following:

alt text

Recent happenings about a malicious code attack in a node package that heavily include the topic of PeerDependencies finally made me that curious about this topic to start some deeper investigation about how PeerDependencies work. In this blog post I'll write down what I found out about NodeJS PeerDependencies in a way that also might help you to better understand this topic.

Searching for "What are peer dependencies" using Google - of course - returns some results. Nevertheless none of the main references Google returned made me understand PeerDependencies in way I was satisfied with. After some time I found this Stackoverflow Page including a great PeerDependency explanation of Stijn De Witt. His explanation came quite close to a version that made me understand the basics of PeerDependencies and brought up some imaginary "Aha!" moments (Thank you Stijn!). But somehow and as I am more a visual learning type Stijn's "text-driven" Stackoverflow explanation did not bring up that imaginary last-mile satisfaction for me in terms of understanding PeerDependencies. As a result I draw some code around his explanation (you can see quoted below) and suddenly things became more clear to me.

What's the problem?

Upfront: in the upcoming example, JillsModule will be the tricky part (subsequently the PeerDependency) of the process. That's why I added fictional version appends (@1.0, @2.0) when using it.

Let's say we are building OurCoolProject and are using JacksModule and JillsModule@2.0. Also let's suppose that JacksModule also depends on JillsModule, but on a different version, say JillsModule@1.0. As long as those 2 versions don't meet, there is no problem. The fact that JacksModule is using JillsModule below the surface is just an implementation detail. We are bundling JillsModule (as the code uses 2 different versions, but not related to each other (!)) twice, but that's a small price to pay when we get stable software out of the box.

In code this means something like

// OurCoolProcject.js

import JacksModule from 'jacksmodule';
import JillsModule(@2.0) from 'jillsmodule(@2.0)';

const OurCoolProcject = () => {
    // do some stuff with JacksModule
    // do some stuff with JillsModule(@2.0). stuff won't break as we have the compatible @2.0 version of JillsModule available in this scope.
}

export default OurCoolProject;
// jacksmodule.js (an npm module)

import JillsModule(@1.0) from 'jillsmodule(@1.0)';

const JacksModule = () => {
    // do some stuff with JillsModule(@1.0). stuff won't break as we have the compatible @1.0 version of JillsModule available in this scope.
}

export default JacksModule;

But next this dependency relationship gets more tricky.

Now let's suppose that JacksModule exposes its dependency on JillsModule in some way. It accepts an object instanceof JillsClass for example... What happens when we create a new JillsClass using version 2.0 of the library and pass it along to jacksFunction (that excepts the 1.0 version as we know!)? All hell will break loose! Simple things like jillsObject instanceof JillsClass will suddenly return false, because jillsObject is actually an instance of another JillsClass, the 2.0 version.

In code this means something like this:

// OurCoolProcject.js

import jacksFunction from 'jacksmodule';
import JillsModule(@2.0) from 'jillsmodule(@2.0)'; // node resolves to OUR dependency of JillsModule which is 2.0!

const OurCoolProcject = () => {    
    const jillsObject = new JillsModule(@2.0).JillsClass;

    // next the beginning of all evil, we'll pass a jillsObject of version 2.0
    // to jacksFunction (that would expect jillsObject of version 1.0 🤦‍♀️)
    jacksFunction(jillsObject); 
}

export default OurCoolProject;
// jacksmodule.js (an npm module)

import JillsModule(@1.0) from 'jillsmodule(@1.0)';

const jacksFunction = (jillsObject) => {
    // make sure jillsObject is compatible for further usage in this function
    const jillsObjectRocks = jillsObject instanceOf JillsModule(@1.0).JillsClass;
            // └─> 🔥🔥🔥 `jillsObjectRocks` will be a big, fat FALSE
            // as the JillsModule dependencies actively used in this function and
            // passed to this function differ in versions (1.0 vs. 2.0) 🤦‍♀️
    ...
}

export default jacksFunction;

Do you notice what's going on here? jacksFunction receives an incompatible jillsObject as the object was constructed from JillsModule(2.0) and not from JillsModule(1.0) JacksModule is be compatible with. So far, this only shows the problem that in the worst case, leads to non-working software.

How PeerDependencies solve this problem

Luckily npm has some built-in intelligence trying to solve this. If JacksModule declares JillsModule(@1.0) as a PeerDependency, npm can warn the user about this when installing dependencies of your project. So JacksModule's package.json should include this declaration:

{
  "name": "JacksModule",
  ...
  "peerDependencies": {
    "JillsModule": "1.x"
  },
  ...
}

So npm's PeerDepenedency intelligence basically triggers a console output notifying us developers with a warning saying this:

"Hey, this is JacksModule speaking here. Let me tell you: I need this specific package of JillsModule, but I really need the version that is part of my JacksModule project and listed in my package.json file. So please make sure it's installed and make sure it's not some other version of JillsModule you might have installed for your own usage somewhere else in your application."

So in the end - thinking this further - depending on npm packages that require PeerDependencies can be tricky. In case you need a new version of the package X for separated usage in your application this might lead to problems if another dependency you use in your application has a PeerDependency on another version of package X. If this pops up - and in the worst case also leads to problems with your software - you are on your own to decide which package to use or which code maybe needs refactoring to meet all requirements.

I hope those explanations and code examples made sense for you and closed the last thought gap you've had about PeerDependencies. If you've questions or want to suggest some article optimization, feel free to contact me or leave a comment.

This post was originally posted here.

Discussion

pic
Editor guide
Collapse
eunicejhu profile image
zuoqin

Nice article. I got several questions about it.

  1. the solution for the error in the screenshot is the maintainer of react-redux package should declare a peerDependency of react@0.14.0 in its package.json. am I right?
  2. My second question is why they let this error happen. why do you let the jacksFunciton take an jillsObject of version2 instead of the version 1 that it needs? I assume the person who call jacksFunction already know the exact version it needs.
Collapse
manpenaloza profile image
Manuel Penaloza Author

Hey!
Thx for your questions.

  1. what you've described, is what the maintainer of the react-redux package has already done, see here github.com/reduxjs/react-redux/blo.... That's the reason why the warning is thrown here. The solution to this warning is, that the developer of that project (that uses react-redux) has to add react@0.14.0 on its own as a project dependency.

  2. "... I assume the person who call jacksFunction already know the exact version it needs." > the person who calls jacksFunction might not necessarily know this when installing and initially using the npm package jacksmodule. The definition of a peer dependency in jacksmodule's package.json will throw the helping hint that the person should install the proper version of jillsModule.

Hope that helped to answer your questions (?).

Collapse
eunicejhu profile image
Collapse
norayr93 profile image
Norayr Ghukasyan

I specially signed in to write a comment for this article. Really thank you, man. It's already 2 months I can't really understand and even find a good article related to peerDependencies. One small thing I couldn't understand from StackOverflow answer and from this article is it.

From StackOverflow -
How peer dependencies solve this.
They tell npm
I need this package, but I need the version that is part of the project,
not some version private to my module.

From this article -
"Hey, this is JacksModule speaking here. Let me tell you: I need this
specific package of JillsModule, but I really need the version that is
part of my JacksModule project and listed in my package.json file. So
please make sure it's installed and make sure it's not some other version
of JillsModule you might have installed for your own usage somewhere else
in your application."

Note the difference. You say that I need this
specific package of JillsModule, but I really need the version that is
part of my JacksModule project and listed in my package.json file.
and StackOverflow says I need this package, but I need the version that is part of the project,
not some version private to my module.

Could you please explain it ?

Collapse
rajsekhar240 profile image
rajsekhar mahapatro

This is pretty cool and awesome.

Collapse
manpenaloza profile image
Collapse
sagunpandey profile image
Sagun Pandey

No online resource was clear enough for me to understand peerDependencies except this one. Great Article!

Collapse
manpenaloza profile image
Manuel Penaloza Author

thank you! happy I could help!

Collapse
smolinpavel profile image
Pavel Smolin

Nice article! Thanks!

Collapse
manpenaloza profile image
Collapse
pierreyveslebrun profile image
Pierre Lebrun

Finally found an explanation that makes me feel confortable, thanks

Collapse
manpenaloza profile image
Manuel Penaloza Author

nice, glad it helped you!

Collapse
mcgrawm profile image
Mike McGraw

Thank you, great overview, it certainly helped clarify things for me. Keep up the great work! :)

Collapse
manpenaloza profile image
Manuel Penaloza Author

glad to read that, thx!

Collapse
olehdevua profile image
Oleh Devua

What the point to just repeat someone others explanation from stackoverflow

Collapse
manpenaloza profile image
Manuel Penaloza Author

"In this blog post I'll write down what I found out about NodeJS PeerDependencies IN A WAY THAT ALSO MIGHT HELP YOU to better understand this topic."
...
"But somehow and as I am more a visual learning type Stijn's "text-driven" Stackoverflow explanation did not bring up that imaginary last-mile satisfaction for me in terms of understanding PeerDependencies. As a result I DRAW SOME CODE AROUND HIS EXPLANATION (you can see quoted below) and suddenly things became more clear to me."

does that answer your question?

Collapse
olehdevua profile image
Oleh Devua

Okay, makes sense, thanks.