Everyone and their dog shits on the JavaScript ecosystem for being chock full of random tiny dependencies. And that’s not entirely unwarranted. Having so many independent dependencies raises many a question relating to security, maintainability, reliability, and other topics.

And?

As someone who likes to re-invent the wheel at every turn, I recently did a little experiment. I wanted to implement a terminal pager program like less but without any outside dependencies. What I allowed at first was the Rust standard library and that’s it. Everything must be done using ANSI escape sequences (which implies a dependency on a compliant terminal, but let’s ignore that for now).

I quickly ran into the problem of getting any information back from the terminal, seeing as ANSI escape codes only work one way. Well, maybe I can have one call to ioctl in there to get the output terminal size. ++dependencies.

Now to be able to read a keypress without having the user hit enter requires (as far as I know) setting terminal attributes via termios. ++dependencies. And I need to read standard input directly and not via a buffer. So manual calls to read it is, and of course to handle errors we need errno too.

Finally I’ve done it, a barely somewhat kinda working pager that doesn’t properly reset the terminal state when done! And it only took me several days of few hour sessions to do.

Okay?

The next part was to restart, but this time do it using a ready-made terminal user interface (TUI) library. I went with Ratatui, which pulled in a total of some 80 dependencies if I’m not mistaken. All I did was run cargo add ratatui and cargo add crossterm to get my dependencies sorted. (Note: crossterm is a dependency of Ratatui, but I needed it as a direct dependency to access its event API.)

It took me about an hour to get feature parity but with a much better implementation that doesn’t corrupt the terminal for everyone else.

So what?

Implementing a simple thing from scratch: 10+ hours and result is buggy and code is ugly.

Implementing a simple thing with library: 1 hour and result is clean and code is okay.

Pulling a whole bunch of random packages made for not only a better end result, but also made it happen faster. Of course I learned a lot about how TUIs work under the hood doing it from scratch, which is why I did the first implementation in the first place.

How does this relate do dependencies again?

Dependencies are inevitable. Even in my “from scratch” implementation I had to rely on external libraries and programs. In my “good” implementation I relied on dozens of external libraries, even when the thing I was trying to do was very, very simple on the surface.

Most of those dependencies are transitive. All I needed in the end was two external libraries, but the result was some 80 packages in Cargo.lock. I personally don’t think either of these programs is in a good state: one is a maintenance burden and the other is a liability.

The golden path I believe is somewhere in the middle: not no dependencies, not many small dependencies, not few giant dependencies, but a few moderately sized dependencies.

What this would mean in practice is that tiny one-thing adding packages should not really be dependencies directly, as that is how we get to the npm situation of “oops, all cryptominers”. No-one can be bothered to verify each of the hundred tiny packages on every update. They should be aggregated into medium sized packages where dozens of tiny package updates happen at once and thus can be verified on each less frequent update. Moreover the aggregate package maintainer already severely lessens the impact of one malicious small package update from propagating.

As a sort of example: consider the C++ aggregate library Boost. Boost is a collection of smaller libraries, which are usually more medium size than small ones to begin with. Instead of a C++ project taking on 17 dependencies, it can take only one and have everythin needed. Some amount of unneeded stuff too, yes, however that is far less of a problem. Well structured aggregate packages can (I conjecture) direct the compiler (or equivalent) to discard unused sub-libraries, so the extra burden is mainly of storage space.

No, that’s dumb

Okay.