The hidden cost of your dependency tree
Every package you add is a promise to maintain code you didn't write. We trace what a single npm install really commits you to — and when it's worth it.
An install is a signature
Adding a dependency is not a one-time event. It is a line in your package.json that outlives the sprint where someone typed the install command — and a footprint in your lockfile that every CI run, security audit, and onboarding doc will trip over for years.
You chose the API at the top. The tree underneath chose itself.
Every node in that tree is a place a breaking change can enter, a maintainer can walk away, or an advisory can land at 2 a.m. You did not pick those packages. You picked the one at the top, and inherited the rest by transitive consent.
You don't install a package. You install its dependency tree, its release cadence, and its abandonment risk.
The five-year view
A dependency is cheapest on the day you add it. After that the bill arrives in instalments. The library ships a major version and your usage is now legacy. An advisory lands and your audit log turns red until someone upstream cuts a patch — if anyone still can. A maintainer burns out, the repo goes quiet, and the thing you depend on is now technically present and practically frozen.
None of this is hypothetical, and none of it is anyone’s fault. It is simply what happens to code over time when you are not the one steering it. The platform, by contrast, has a maintainer with a very long support window and no plans to abandon the project: the browser. Code you write against web standards ages at the speed of the standards — which is to say, slowly, and with warning.
What you were about to install
Here is a concrete one. You need “3 hours ago” instead of a raw timestamp. The reflex is to reach for a date library. But the platform has shipped relative-time formatting, localized and built in, for years:
timeAgo.js · 0 dependencies// Native, localized, no install.
function timeAgo(date) {
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const seconds = (date - Date.now()) / 1000;
const units = [
['year', 31536000], ['month', 2592000], ['day', 86400],
['hour', 3600], ['minute', 60], ['second', 1],
];
for (const [unit, secs] of units) {
if (Math.abs(seconds) >= secs || unit === 'second')
return rtf.format(Math.round(seconds / secs), unit);
}
}Fourteen lines, localizable to any language the browser supports, and nothing to update, audit, or remove. The library version would have been shorter to type and longer to own. That is the whole trade in miniature.
A test before you install
Before anything lands in package.json, we run it through four questions. Not as bureaucracy — as a habit that catches the installs we would regret at year three.
Can the platform already do this?
Intl,fetch,Popover,View Transitions— the list grows every year. The reflex to reach for npm is often a reflex to reach for 2019. Check the baseline first.How heavy is the tree, really?
Not the gzip on the landing page — the transitive count, the duplicate utilities, the polyfills you already ship. One direct dependency can mean two hundred you never opened.
Who maintains it — and what if they stop?
Bus factor, release cadence, issue backlog, funding model. A quiet repo is not a stable repo. You are betting on a stranger's stamina for the life of your product.
How hard is it to remove later?
Coupling compounds. The longer a library owns your data layer, your router, or your component model, the more expensive the exit. Prefer things you can delete in an afternoon.
The install command is the easy part. The tree underneath is the contract — and contracts outlive the person who signed them. Default to the platform when it is enough. When it is not, know what you are inheriting, and make sure the team that maintains it in five years is the team in the room today.