FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
valdottown

valdottown

blog

Val Town's Blog
Public
Like
8
blog
Home
Code
9
components
12
posts
12
routes
6
styles
2
utils
8
IMAGES.md
README.md
TODOs.md
H
index.ts
Branches
5
Pull requests
1
Remixes
16
History
Environment variables
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
posts
/
2025-09-11-gardening-dependencies.md
Code
/
posts
/
2025-09-11-gardening-dependencies.md
Search
9/9/2025
Viewing readonly version of gardening-dependencies branch: v5
View latest version
2025-09-11-gardening-dependencies.md

Tips on gardening your dependencies

Val Town is a React application with a ton of dependencies. It's complicated and we have to deal with dependency upgrades all the time. We are committing the cardinal sin of overcomplicating the web: our node_modules directory is 863MB as of this writing. Whew!

Is it, though? Are we installing dependencies freely, taking on technical debt left and right? I'd say not really.

The thing is, there's some essential complexity in what we're trying to build. We aren't going to DIY our own TypeScript transpiler or avoid installing CodeMirror and use a textarea for our code editing. I spent a little time every week looking through package.json and thinking which of these can I remove. Sometimes I find a dependency that can be yanked, but a lot of times I come up empty-handed: we actually need all this junk. My ability to judge other people fades as I learn the hard way how principles make contact with reality.

But that's not to say that there's no art in dependency grooming. There are a bunch of techniques and tools that all fit together into a general sort of dependency hygiene that I've developed. I'm not sure I've written it down anywhere in full. Here's a shot.

Read all new dependencies

Rule #1 is to read. This is very literal: read the source code of any dependency that you're about to introduce into your project. And, of course, the README. Don't ask your LLM to, don't be a baby, put on your spectacles and at least read the parts of the module that your application is going to rely on.

Fairly often you'll do this and discover that the new dependency you're adding is just 50 lines long, and is better off vendored rather than installed with NPM: just copy that code over and preserve its open source license in a code comment.

Or you'll realize that the module is 2MB gzipped and introduces 3 new transitive dependencies, but you're only actually using 50 lines of it. Again, this is not a good scenario: you're introducing a lot of surface area that's pure downside. It'll take up more room in node_modules, or it could have security vulnerabilities in the parts that you aren't using and you'll have to triage them just the same.

If you don't read, you won't succeed.

npm ls and reading package-lock.json is your friend

Or, if you're using pnpm, pnpm-lock.yaml and pnpm why. And a similar command for whatever other package manager you're using. The reason is pretty simple: your direct dependencies are inevitably the tip of the iceberg. What really fills up node_modules is all of the stuff they bring along, and that stuff - transitive dependencies - is extremely important.

For example, let's say your project needs to transpile TypeScript. It's fairly likely that you already have a transpiler installed: in our project, we have copies of esbuild that are dragged in by drizzle-kit, by Vite, and by tsx. So using esbuild as a direct dependency costs us nothing: it gets deduped to the same esbuild binary that everything was using under the hood before. This is a useful little game: when you're installing something for your application, if you can find a way to reuse something that's already installed transitively, you can get a dependency for free!

And read package-lock.json or pnpm-lock.yaml. It's not that bad, and you'll learn something. There's a lot in there. It'll familiarize yourself with what modules others rely on, which builds a little PageRank algorithm in your head so that you can recall, off the bat, what you might want to use for something new. Feed your curiosity and open those npmjs.com webpages.

Have a quick cheatsheet of people who write good modules

The NPM module ecosystem is made up of people. It's useful to know who those people are! For example, when I'm looking for something related to Promises (or many other topics), I'll check if Sindre Sorhus has already published something. Very often he has!

Other folks who have lots of solid work are good to know - isaacs, Matteo Collina, Mafintosh.

If you're doing something with Markdown, you should know all the wooorm and unified repos. Nextgen Node.js stuff? Check unjs. Transpiler internals, you should always check Rich Harris's projects, which include so many gems.

I don't know if there's a way to systematize this, and I would be cautious of one because incentives are usually subverted. It's more like learning the 'lay of the land' of the internet. I find it kind of fun. Maybe you will?

What makes a good NPM module

But what are you really looking for? The definition of a good module keeps shifting, but pretty often it'll look something like: a decent history of maintainership, built-in TypeScript types, passing tests, good documentation.

A slang shorthand for this would be that it should have a vibe of competence. Even if you're building something by the seat of your pants and making lots of mistakes, you want the parts that you're using to be solid. An application's bugs are the sum total of the bugs you write and the bugs you inherit from others, so it's actually kind of fair to have higher standards for code that you install than code that you write.

What makes a bad module? Of course something that's abandoned and poorly written is bad, but even worse than that is a module that solves the wrong problem - something that doesn't actually fit the problem you have and instead you have to shift the problem to make it work. You can fix this by reading and spending a little time understanding both your problem and the solution. Or asking the LLM, you little baby.

Get rid of what you don't use and keep the rest up to date

You should be using Renovate. It'll nag you to keep your modules up to date, and it's better to do that kind of work incrementally rather than in one yearly push.

And you should be using Knip. It's just straight-up magic: it's so fast, extremely accurate. It'll tell you which modules you have specified in package.json but that you aren't actually using. It's super easy to lose track and have a lot of junk lying around from old versions of a project. Get rid of it with Knip! It'll even show you which files your project no longer uses. If there was a Knip t-shirt I'd be wearing it right now, that's how good it is.

Dependencies are an inevitability

That's the truth: we're all building on the shoulders of each other. But there's an art to finding the right shoulders to build on.

In a way, it's frustrating that the web platform, and the NPM module ecosystem, moves so fast and requires so many nitpicky updates and decisions. But that's pretty much the norm: even next-generation languages that have learned the lessons of NPM and Node suffer from bloated package ecosystems. Gardening dependencies is part of the job and should do it well.

FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
ExploreDocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareersBrandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.