What makes a package useless, or "When should I reinvent the wheel"?
Tags: javascript, webdev, npm
Published: 2021-08-23 15:57:43

This is a response to a couple of comments in the discussion below this article:

{% post https://dev.to/jyotishman/10-useless-npm-package-with-millions-of-downloads-de9 %}

@ansile wrote:

> array-includes is a polyfill, first published 7 years ago.
array-flatten is kinda the same. It's not a polyfill, in a technical sense, but Array.prototype.flat is a new feature and the package pre-dates it.

Arguably, that makes `array-flatten` "useless" for new projects, because it's better to polyfill a standard API, as you can remove the dependency later on if you stop supporting legacy browsers. You can't do this if you're using a non-standard API.

Of course, that doesn't mean the package was useless when it was written, nor even that older projects should instantly switch to the standard API. Any such change requires some amount of additional work, so there's a trade-off between rework and technical debt.

> ismobilejs is a device os/type/etc detector. Some apps definitely need that.
>
> And store2 is a wrapper with a huge amount of features, some convenience-based (e.g. modifying currently stored value instead of manually doing get+set), and some unique (e.g. checking how many space is left).

Difficult to argue with either of these. Both of these packages fulfill usefull purposes, and reinventing the wheel here not only takes additional work but also won't be as well battle-tested as a public, open-source package with thousands of GitHub stars.

The main reasons _not_ to use these would be:

1. Preference for a different package that covers the same use case. For example, instead of a wrapper for the somewhat outdated `localStorage` and `sessionStorage` APIs such as `store2`, you could use the excellent [`idb-keyval`](https://github.com/jakearchibald/idb-keyval), which wraps the `IndexedDB` API instead, giving significant performance and other advantages.

2. Reduce bundle size by using a custom yet very simple, small, yet hacky alternative. For example, maybe you don't need all of `ismobilejs`'s features. Per [MDN's recommendation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#:~:text=In%20summary%2C%20we%20recommend%20looking%20for%20the%20string%20Mobi%20anywhere%20in%20the%20User%20Agent%20to%20detect%20a%20mobile%20device.), if you simply need to know whether a UA is mobile and don't care about other details, you can get a pretty damn good approximation with just 1 line:

```js
export const isMobile = navigator.userAgent.includes('Mobi')
```

Meanwhile, @mcmath argues:

> I’m going to go out on a limb and say that none of these are useless. Take upper-case, for example. I imagine it’s supposed to be useless because we already have toUpperCase(). But sometimes we want to do this:
>
> ```js
>  let upperCaseStrings =
>    lowerCaseStrings.map(upperCase);
> ```
> 
> ... Yes, it's easy to implement yourself. In fact, I have (many times). Maybe I'll download upper-case next time instead.

I'd argue that importing the `upper-case` package for this purpose would be a huge mistake and lead to increased tech debt for virtually no benefit. For extremely simple features such as this, even if you frequently need a `map`-able version, it'd be much better to have a custom file somewhere in _your own_ codebase, rather than an external dependency. Perhaps it'd be called something like `/src/utils/string-formats.ts` and look something like this:

```js
export const upperCase = (str: string) => str.toUpperCase()
export const lowerCase = (str: string) => str.toLowerCase()
// ...
```

Importing an external package for such simple features would be a mistake, for a few reasons:

1. Many developers will assume the package is doing something special and unique, rather than just calling `String#toUpperCase()`. They'll end up wasting time poring over GitHub repos, trying to figure out why someone has bothered to include this package as a dependency.

2. Meanwhile, other developers will just ignore it and treat the package as a "black box". They won't be quite certain what it does, but they'll assume it does something vaguely similar to `String#toUpperCase()`. Instead of a standardized, tried-and-tested, well-known, painstakingly specified, well-documented API, they'll be left wondering. Does it work on Greek or Cyrillic text? Is it locale-sensitive, and if so, does that mean it may have different results in different user agents? Is calling `upperCase(lowerCase(upperCase(str)))` always identical to calling `upperCase(str)` for every possible value of `str`? Who freakin' knows!

3. Breaking changes might be introduced to the package, which would never (or [very rarely](https://dev.to/richharris/stay-alert-d)) happen with native web platform features. In general, you want to keep packages up to date, for security reasons if nothing else; but you also don't want your project to break thanks to the updates.

---

In general, I'd suggest the following heuristics in determining when to use a third-party package or when to "reinvent the wheel":

* Can I implement the feature myself trivially and reliably?

* How well is this functionality supported by existing Web (or Node) APIs?

* Will updates to this package typically be an advantage or a disadvantage?

* Is this a critical part of the app for which I want to be sure the solution is robust and battle-tested?

* Does the benefit the package brings justify the increased bundle size?

Would you agree with these heuristics? What other ones would you add/remove?