I Removed Three Dependencies. One of Them Actually Left.
In forty-six minutes one morning I opened three pull requests, and every one of them deleted a dependency.
The commits are timestamped, so I can be precise about it:
- 07:33 replace Sharp transforms with
Bun.Image - 07:55 enforce signed upload content type (the AWS SDK removal that didn’t happen)
- 08:19 adopt
Bun.YAMLfor sync
Two merged with the dependency gone from package.json. One I abandoned on purpose. That’s a decent morning, and it’s how I described it to myself at the time.
Then I went back and looked at the lockfile.
Only one of those packages actually left.
Sharp Is Still Sitting in node_modules Right Now
The image one looked like the clean win. My media pipeline generates thumbnail, medium, and large variants, plus WebP versions, and it used Sharp to do it. Bun 1.4 ships a native Bun.Image, so I rewrote src/lib/media.ts onto it and pulled "sharp": "^0.35.3" out of my dependencies.
The benchmark was encouraging. Same workload, 2400×1600 inputs, separate processes, /usr/bin/time -l on macOS:
| Input | Sharp peak RSS | Bun.Image peak RSS | Elapsed |
|---|---|---|---|
| JPEG | 218 MB | 75 MB | 224ms → 207ms |
| PNG | 185 MB | 80 MB | 389ms → 549ms |
| WebP | 177 MB | 77 MB | 238ms → 180ms |
Memory is not close. Roughly a third of the footprint across all three formats. Speed is a wash, and PNG got noticeably worse, about 40% slower. The encoded output isn’t byte-identical either: JPEG came out 9% larger, PNG 2% smaller, WebP 3% larger. Fine for my purposes, but it’s a re-encode, not a port.
One caveat I want to be honest about, because it undercuts the comparison: Sharp wouldn’t load under Bun 1.4.0 on this machine at all. It failed on a missing libvips-cpp.8.18.3.dylib. Under node -e 'import("sharp")' it imported fine. So that table is Sharp-under-Node against Bun.Image-under-Bun, which means it’s measuring two runtimes, not two image libraries.
Anyway. I removed Sharp, the tests passed, I merged it.
Sharp is still installed. Here it is:
$ grep -c '"@img/sharp-\|"sharp"' bun.lock
27
$ ls -d node_modules/sharp
node_modules/sharp
Astro 7 declares Sharp as an optional dependency for its own image integration. My lockfile says so in as many words:
"[email protected]" ... "optionalDependencies": { "sharp": "0.35.2" }
So when I deleted my direct dependency, the resolver didn’t drop Sharp. It just stopped hearing my opinion about which version to use, and fell back to Astro’s. Sharp went from 0.35.3 to 0.35.2. My dependency removal was, in lockfile terms, a downgrade.
The One That Actually Left
The YAML change is the boring one and it’s the only real removal.
My blog sync script parsed frontmatter with the yaml package. Bun 1.4 has Bun.YAML with parse and stringify, which is the entire surface I was using:
const data = Bun.YAML.parse(frontmatter);
That’s it. "yaml": "^2.9.0" came out of package.json, one entry came out of bun.lock, and nothing else in the tree wanted it. Gone.
No benchmark for this one. I didn’t run one, because a dependency that does one thing I can do with a builtin doesn’t need a performance argument to justify deleting it.
The Third One I Talked Myself Out Of
The AWS SDK is the one I wrote about separately. Short version: Bun’s native S3Client presigns URLs about 32x faster and would take 25 packages out of the lockfile, and I kept the AWS SDK anyway, because Bun 1.4 can’t send Cache-Control on writes and can’t force Content-Type into a presigned URL’s signed headers. Faster, smaller, and not behaviorally equivalent.
That one at least failed honestly. I evaluated it, wrote down why not, and the packages are still there because I decided they should be.
Three Words That Mean Three Different Things
Here’s what I actually learned, and it’s a vocabulary problem more than a technical one. “I removed a dependency” turns out to describe at least three unrelated states:
- It’s out of my
package.json. Sharp, yaml. This is the one people mean when they say it, and it’s the weakest of the three. - It’s out of the lockfile and off the disk. Only yaml. Sharp is still there, 27 entries, physically present in
node_modules. - It’s out of
trustedDependencies. This is the one I undersold at the time.esbuildis now the only package in my project allowed to run install lifecycle scripts. Sharp used to be in that list and isn’t anymore.
Number three is the one that actually changed my exposure. Sharp is still on disk, but nothing in my code calls it, and it no longer gets to execute arbitrary code at install time. Number two never happened. Number one is mostly bookkeeping.
I would have told you I removed two dependencies that morning. The lockfile says I removed one, downgraded another, and revoked install-script trust from a package that’s still sitting right there. All three of those are fine outcomes. They’re just not the same outcome, and I had been filing them under one word.
Check your lockfile after you delete something. Transitive optional dependencies do not care what your package.json says.
Sources
- Bun.Image runtime documentation — the image API that replaced Sharp
- Bun.YAML runtime documentation —
parseandstringify, nothing else - Bun 1.4 release announcement — released 2026-08-20
- Astro images guide — Astro’s built-in image service, which is why Sharp stays
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].