npm downloads every dependency recursively. If a depends on d (= 1.2.3) and b depends on d (= 1.2.4), then both versions of d get downloaded into a and b’s respective node_modules.
All other package managers I’m aware of resolve dependencies into a flat list then download, and you can only have one version of the same package on your system.
You mean npm duplicates even if the the two dependency versions are compatible?
you can only have one version of the same package on your system.
That couldn’t be, right? Otherwise, if you installed two packages that rely on different incompatible versions of another package, one of the two would break. Reading a bit they should check for “satisfiability”, I found some really interesting things on the topic looking around:
You mean npm duplicates even if the the two dependency versions are compatible?
By default yes, unless you explicity use the “peer dependency” system which isn’t the default. The “default” naive implementation is for every package in your node_modules to have a node_modules of its own, all the way down recursively. There are tricks nowdays to deduplicate packages with the exact same version, but not to automatically detect “compatible” versions and use those instead (in my experience nothing would work if that was the case, deleting package-lock.json causes way too many issues due to the… uh, let’s call it “brave” approach of JS devs to stability).
That couldn’t be, right? Otherwise, if you installed two packages that rely on different incompatible versions of another package, one of the two would break
Correct. This is intended behavior which is solved in several ways:
Correctly declaring your dependencies. If newer versions of a dependency break your package, disallow them, but that is not normally needed for minor version changes.
Focus on quality. Semver exists for a reason, and 1.2.3 should not break something built against 1.1.2. JS and NPM’s cascade of stupid implementations bred a culture of “move fast and break things”, but that’s not the norm in any other commonly used ecosystem
Linux distros almost exclusively use curated repositories, so they are (mostly) internally consistent and incompatibilities are rare and quickly fixed. A good package manager will resolve dependencies and automatically detect incompatibilities, proposing several fixes (typically abort the upgrade or uninstall one of the problematic packages)
Not breaking down packages into a constellation of smaller packages. glibc6 is glibc6, not glibc_string (1.2.3) + glibc_memory (2.6.5) + glibc_fs (1.5.3) + glibc_stdio (1.9.2) + glibc_threads (6.1.0) + …
Internally glibc6 is a bunch of modules, but they get bundled into one package specifically to simplify dependency management.
Not being able to install two versions of the same package sounds restrictive, but it’s a HUGE security benefit: glibc6 (1.2.3) is vulnerable to CVE-2024-1, then updating to glibc6 (1.2.4) secures your entire system at once. With NPM though, you have to either wait for every. single. dependency on that vulnerable package down your tree to recursively update, or patch those versions yourself (at your own risk because again, small version changes often break things since developers think that NPM’s dependency model means they don’t have to actually provide stability guarantees).
npm
downloads every dependency recursively. Ifa
depends ond (= 1.2.3)
andb
depends ond (= 1.2.4)
, then both versions ofd
get downloaded intoa
andb
’s respectivenode_modules
.All other package managers I’m aware of resolve dependencies into a flat list then download, and you can only have one version of the same package on your system.
You mean npm duplicates even if the the two dependency versions are compatible?
That couldn’t be, right? Otherwise, if you installed two packages that rely on different incompatible versions of another package, one of the two would break. Reading a bit they should check for “satisfiability”, I found some really interesting things on the topic looking around:
By default yes, unless you explicity use the “peer dependency” system which isn’t the default. The “default” naive implementation is for every package in your
node_modules
to have anode_modules
of its own, all the way down recursively. There are tricks nowdays to deduplicate packages with the exact same version, but not to automatically detect “compatible” versions and use those instead (in my experience nothing would work if that was the case, deleting package-lock.json causes way too many issues due to the… uh, let’s call it “brave” approach of JS devs to stability).Correct. This is intended behavior which is solved in several ways:
1.2.3
should not break something built against1.1.2
. JS and NPM’s cascade of stupid implementations bred a culture of “move fast and break things”, but that’s not the norm in any other commonly used ecosystemglibc6
isglibc6
, notglibc_string (1.2.3)
+glibc_memory (2.6.5)
+glibc_fs (1.5.3)
+glibc_stdio (1.9.2)
+glibc_threads (6.1.0)
+ …Internally
glibc6
is a bunch of modules, but they get bundled into one package specifically to simplify dependency management.Not being able to install two versions of the same package sounds restrictive, but it’s a HUGE security benefit:
glibc6 (1.2.3)
is vulnerable to CVE-2024-1, then updating toglibc6 (1.2.4)
secures your entire system at once. With NPM though, you have to either wait for every. single. dependency on that vulnerable package down your tree to recursively update, or patch those versions yourself (at your own risk because again, small version changes often break things since developers think that NPM’s dependency model means they don’t have to actually provide stability guarantees).Wow, awesome explanation! I think I understand now