Thoughts on “Dependency hell is NP-complete”
At some point we will do a more in depth write-up about this, but I think we have a very unique take on this at RunKit as we&#x27;ve effectively installed every version of every package. It&#x27;s actually more interesting than that, we&#x27;ve installed every reasonable combinatorial dependency result of every version of every package on npm.<p>Long story short, I think there was tremendous insight in npm 2&#x27;s original model of having every dependency duplicated. Beyond allowing neat things like diffing two versions of the same package (<a href="https:&#x2F;&#x2F;runkit.com&#x2F;tolmasky&#x2F;api-diff-example-4" rel="nofollow">https:&#x2F;&#x2F;runkit.com&#x2F;tolmasky&#x2F;api-diff-example-4</a> ), I think it most accurately portrays the meaning of a package&#x2F;version.<p>The fundamental problem with trying to &quot;unify&quot; all the dependencies a program will need, is that it almost guarantees that code will be running in an untested state. Package A was written when Dependency 1 was at version 1.0.0. By assigning it the semver range 1.x.x (instead of the strict 1.0.0 dependency), that means that at some later point it will run against code its never seen or was tested against before. Sure, semver ranges are supposed to protect against this having a &quot;meaningful&quot; effect, but this simply kicks the can down the responsibility chain, changing the error to &quot;human error&quot; as if it makes a difference. For example, let&#x27;s say at the time of publishing Dependency 1 had a bug that Package A accounted for. According to semver, a fix for this bug would qualify as a 1.0.1, but now Package A&#x27;s workaround may actually <i>break</i>. The fact of the matter is: 1. rarely is code fully tested, 2. humans can&#x27;t be trusted to properly assign semver, 3. semver doesn&#x27;t even account for every possible &quot;meaningful&quot; change (I&#x27;ve seen performance changes that had devastating effects despite the &quot;result&quot; being the same). Different code is different code.<p>Now, you may be saying, what does this have to do with npm, it sounds like what you want is strict versioning -- the problem is that compounding this already tenuous situation with versions that change depending on the combination of dependencies you have in order to maximize deduping makes this problem even worse. Let me give you a <i>security</i> example:<p>1. Package A relies on Dependency 1, ranged at 1.x.x.<p>2. Dependency 1 is found to have a security problem, so a 1.0.1 fix is issued.<p>3. Package A correctly no longer suffers from this problem.<p>Later, Package B is added, which someone has maliciously, 10 sup-denendencies deep, added a strict dependency to Dependency 1 1.0.0. The deduping algorithm now says:<p>1. 1.x.x and 1.0.0 are the requirements, I will use 1.0.0.<p>Now Package A is using an insecure version of Dependency 1. Previously, if every package was given its own dependencies, the issue would be scoped to Package B, instead it now infects everyone that relies on this Dependency. Sure, you can fix this by saying &quot;dedupe after giving every package its highest possible dependency&quot;, but now you still suffer from monkey-patching affecting other code. You end up with very difficult to understand results. Sometimes, all the code uses the same package, other times not, seemingly randomly. The problem is that a performance issue is being turned into a functional issue. Package should be black boxes, not things you change the guts of regularly. There exists a tool for sharing dependencies: &quot;peerDependencies&quot;. This says &quot;I expect this to be around&quot; vs. &quot;I am including this&quot;.<p>The basic idea here is that you would never say &quot;let&#x27;s arbitrarily replace functions I&#x27;ve written in my program with approximately the same function in order to be able to be able to share them and reduce program size&quot;. This is nuts! Package versions should be considered hashes that only coincidentally have human names. NPM 2 is the closest I&#x27;ve seen anything come to this, and it creates the least surprises.<p>Most the remedies I&#x27;ve seen revolve around solving performance or efficiency problems this has. But I think it should first be important to make it right, THEN make it fast. You can definitely imagine a system that uses content-hashes to get the space efficiency without the cost of the conceptual separation. In fact, IED does this quite well: <a href="https:&#x2F;&#x2F;github.com&#x2F;alexanderGugel&#x2F;ied" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;alexanderGugel&#x2F;ied</a>