To illustrate the problem, let me start with a seemingly straightforward example:
node_modules directory looks like:
ansi-gray/ graceful-fs/ object.map/
Let that listing sink in for a moment. That’s 198 dependencies installed (a total of 5.5 MB of code assets), and so far all I have is my task runner. I haven’t even started to write actual application code that will need real dependencies.
Yeah, I’ve just been waiting to use this meme one day…
This is just a tiny bit insane to me, not just because of the size of the dependencies, but also because of how ridiculously niche a lot of them are. Let’s pick one at random:
path-root. (With apologies to the author, Jon Schlinkert: I’m not doing this to call him out. His code was just a convenient example.) What does this do? According to its README: “Get the root of a posix or windows filepath.” Okay, seems straightforward. Then I look at the code:
That’s literally the whole “library.” A single function that matches a string to a regular expression. Even more crazy, the regular expression itself does not exist in this module; it’s in a nested dependency,
path-root-regex. What. The. Heck.
This raises several questions in my mind:
- Why is there an entire module dependency dedicated to what could be boiled down to one line of code?
- Why is that module in turn dependent on a completely different module that basically acts as a data source for the first module?
- Why did the developers of other modules resort to importing these dependencies? (Point of order: it’s not gulp, it’s actually like four dependencies deep.)
I bring up this particular example because it’s very indicative of how Node.js modules are built and distributed: as tiny pieces with an arcane and unfathomable dependency chain. You want A, and you end up getting B, C, D, etc. whether you wanted them or not, and the value/provenance of those dependencies is suspect. In my own searches of looking for tools in the npm repository, I’ve stumbled upon dozens of these types of micro-modules that someone wrote, single-function and seemingly pointless. Did I really make myself more productive by importing two modules into my project instead of writing a single-line regular expression match?
I’m not steeped in the culture of Node.js/npm by any stretch of the imagination, but we use Node.js for quite a few web applications at my workplace, and I also have taken to using them on my personal web sites as a means of learning more about them. This way of writing code seems insane to me, but I also came from a background in monolithic application software written using the .NET Framework. I think this is where I get lost. Instead of the Node.js environment providing a standard library of common features that developers can build on, we’re instead left to our own devices, writing whatever we think might be useful and posting it onto a central repository for everyone to pull into their software, probably with varying degrees of review. The flaws in a completely democratic system can be self-evident.
It’s also not uncommon when trying to find something that does what you want, you’ll find at least a half-dozen implementations from various sources (often with confusingly similar names), in various states of maintenance. There is a lot of abandonware in npm, and also it’s not always obvious if there are tons of hidden bugs you’re inheriting that the author never found and/or never intends to fix. It’s very much a “buyer beware” scenario.
Of course, I’m not novel in recognizing this. While I was researching for this post, I found a nice post by Mateusz Morszczyzna that reaches pretty much the same conclusions I did. He even used the same joke image I picked out. Oh well.
At this point, I’d almost be willing to go back to PHP–while the language has its flaws, at least it had a well-maintained, well-documented set of standard extensions to cover all the common scenarios where you’d want to take on dependencies. I feel like npm’s design actively encourages dependency hell and obfuscation, and it results in people pulling in first-level dependencies unnecessarily and second-level dependencies unwittingly.