Cycles are handled using the property of node's module system that it walks up the directories looking for node_modules
folders. So, at every stage, if a package is already installed in an ancestor node_modules
folder, then it is not installed at the current location.
Consider the case above, where foo -> bar -> baz
. Imagine if, in addition to that, baz depended on bar, so you'd have: foo -> bar -> baz -> bar -> baz ...
. However, since the folder structure is: foo/node_modules/bar/node_modules/baz
, there's no need to put another copy of bar into .../baz/node_modules
, since when it calls require("bar"), it will get the copy that is installed in foo/node_modules/bar
.
This shortcut is only used if the exact same version would be installed in multiple nested node_modules
folders. It is still possible to have a/node_modules/b/node_modules/a
if the two "a" packages are different versions. However, without repeating the exact same package multiple times, an infinite regress will always be prevented.
Another optimization can be made by installing dependencies at the highest level possible, below the localized "target" folder.
Consider this dependency graph:
foo
+-- [email protected]
+-- [email protected]
| +-- [email protected] (latest=1.3.7)
| +-- [email protected]
| | `-- [email protected]
| | `-- [email protected] (cycle)
| `-- asdf@*
`-- [email protected]
`-- [email protected]
`-- bar
In this case, we might expect a folder structure like this:
foo
+-- node_modules
+-- blerg (1.2.5) <---[A]
+-- bar (1.2.3) <---[B]
| `-- node_modules
| +-- baz (2.0.2) <---[C]
| | `-- node_modules
| | `-- quux (3.2.0)
| `-- asdf (2.3.4)
`-- baz (1.2.3) <---[D]
`-- node_modules
`-- quux (3.2.0) <---[E]
Since foo depends directly on [email protected]
and [email protected]
, those are installed in foo's node_modules
folder.
Even though the latest copy of blerg is 1.3.7, foo has a specific dependency on version 1.2.5. So, that gets installed at [A]. Since the parent installation of blerg satisfies bar's dependency on [email protected]
, it does not install another copy under [B].
Bar [B] also has dependencies on baz and asdf, so those are installed in bar's node_modules
folder. Because it depends on [email protected]
, it cannot re-use the [email protected]
installed in the parent node_modules
folder [D], and must install its own copy [C].
Underneath bar, the baz -> quux -> bar
dependency creates a cycle. However, because bar is already in quux's ancestry [B], it does not
unpack another copy of bar into that folder.
Underneath foo -> baz
[D], quux's [E] folder tree is empty, because its dependency on bar is satisfied by the parent folder copy installed at [B].
For a graphical breakdown of what is installed where, use npm ls
.