I ran yarn upgrade on a six-month-old project and lost an entire day.
Not debugging my code. Not shipping a feature. Not doing anything remotely productive. I lost a day fighting tooling—the stuff that's supposed to help me write software, not prevent me from writing it.
Here's how it happened, and why I think this kind of thing represents a genuine, measurable loss for everyone building on the web.
The upgrade that broke everything
I have a small Vue 2 project—an admin UI for. Tiny. One added dependency. Created with vue cli six months prior. I picked it up to finish some work and started with yarn upgrade.
Blindly upgrading all dependencies is never a great idea. I know that. But this is a tiny WIP project, and GitHub's Dependabot had been screaming at me every month to upgrade something, so I figured—what's the worst that could happen?
The build broke instantly:
Syntax Error: TypeError: eslint.CLIEngine is not a constructor
Let that sink in. A syntax error. Not in my code. In the tooling.
Has JavaScript changed so completely in six months that the build tools themselves can't keep up? Despite the layers and layers of transpilation black magic that these tools apply? Do I now have to worry about the internal compatibility of my toolchain's dependencies as much as my own business logic?
The answer, apparently, is yes.
The rabbit hole goes deeper than you think
Turns out, eslint got bumped to v8, which has significant breaking changes that are incompatible with projects created with earlier versions of vue cli. I genuinely don't care about eslint or babel or their million transitive dependencies. I want to work on my project. But this is a showstopper, so I started Googling.
Pages and pages of cryptic stack traces on GitHub issues. Stack Overflow threads going in circles. No clear answers.
I figured: maybe it's not worth fixing the broken build system. Maybe I should just port to Vue 3. It's out now (after many release candidates over many years). It's the future. I'd learn the new composition API and move on.
Here's where things get absurd.
Step 1: I discover that Vite is the new hotness for creating Vue 3 projects. It's fast because it uses esbuild—which, ironically, is not written in JavaScript.
Step 2: I create a new Vue 3 project with Vite and try to add Buefy, the UI library I was using. Buefy does not support Vue 3. Its Vue 3 roadmap makes it obvious this isn't happening anytime soon.
Step 3: Fine, I'll learn a different UI framework. ChakraUI is popular. Except—ChakraUI also doesn't support Vue 3.
Step 4: I stop looking for a new UI library entirely because the Vue 2 vs. Vue 3 split is clearly going to be a painful, drawn-out fiasco. Major projects can't rewrite everything to the new Vue 3 way for obvious reasons—including the state of JavaScript dependencies.
Step 5: I give up on Vue 3 and decide to just recreate the broken project in Vue 2 + Buefy with a fresh vue cli scaffold, then copy over my WIP business logic (thankfully, very little at this point).
Step 6: But wait. Vite doesn't support Vue 2.
So it's back to the not-super-fast, non-esbuild vue create flow.
I run yarn upgrade --all on the new project to see if eslint v8 breaks it again. It doesn't. Clearly, vue cli has fixed the issue—by pinning eslint to v < 8, presumably.
By this point, I've wasted hours Googling, reading, running, and copy-pasting. And I've shipped exactly zero features.
This is not an isolated incident
This ordeal came right after we spent an inordinate amount of time at work battling WebPack over some seemingly trivial change. The issue was such a rabbit hole that I can't even recollect what the problem was or how it began.
And that's the thing that makes this so insidious. It's not one catastrophic failure. It's death by a thousand cuts. Each individual issue feels solvable. But the cumulative cost—across teams, across projects, across the industry—is staggering.
Why should anyone care about tooling that changes month to month? How is it acceptable for frameworks, libraries, and build systems to constantly get in the way of writing business logic—the entire point of writing software?
I genuinely wonder how people keep track of the incessant barrage of JavaScript frameworks, frameworks on top of frameworks, build and dev tools, a maze of technologies that compete, conflate, and conflict with each other in impossible ways.
This isn't about JavaScript the language
I don't mind JavaScript. It's a language with warts—more than its counterparts, sure—but every language has warts. I've been writing JavaScript since 2001. I have context on "vanilla", jQuery, frameworks, and the pre- and post-build era.
I wrote PHP for many years. That was messy. Python 2 vs. 3 was messy. I wrote many messy things in many messy languages for many years.
I cannot recall any language's ecosystem getting "modernized" like this—becoming so comically complicated that it reads like parody. The symptoms were evident with left-pad. Nothing changed.
That people find this level of confusion and complexity acceptable is baffling. And to think there's a whole generation of developers for whom this is the baseline—who think this is normal—that's genuinely painful.
It reminds me of a developer I spoke to who only knew how to deploy a static website via a "CI/CD" system connected to a K8s cluster. They didn't know it was possible to cp or rsync an index.html to a directory on a Linux system running a web server. They couldn't even visualize that fundamental concept because CI/CD and K8s was their baseline.
Maybe a bit dramatically: all this complexity, this bloat, this hot new bloat to fix previous bloat—it's a net loss for humanity. If quantified, the cost in terms of time, effort, energy, and money must be enormous.
What a collective, mammoth exercise in intellectual dishonesty. What a monumental waste.
The saga continued (October 2021)
I started porting the tiny project to the fresh Vue 2 scaffold and installed sass-loader—the de facto JavaScript Sass library—to customize a couple of Buefy CSS variables.
Syntax Error: TypeError: this.getOptions is not a function
sass-loader stopped working with Vue 2 at v10 because subsequent versions require WebPack 5. Vue 2 apps created with vue cli don't support WebPack 5. Vue cli v5 will have WebPack 5 support—but with an option to downgrade to WebPack 4, because many other libs won't support WebPack 5 yet.
Let me draw you the dependency graph:
Vue 2 ↔ Vue 3 ↔ Vite ↔ vue cli 4 ↔ vue cli 5 ↔ WebPack 4 ↔ WebPack 5 ↔ sass-loader 10 ↔ sass-loader 12 ↔ ∞ dependencies
A CSS library is so tightly coupled with the build toolchain that it breaks the toolchain. And that's just one library.
The fix: permanently pin sass-loader to v10.1.1.
That threw another error:
Syntax Error: Error: PostCSS received undefined instead of CSS string
More Googling. Turns out, if sass-loader is used with WebPack, you have to install the sass library manually.
The app compiled. Eventually.
The ending nobody expects (June 2022)
I gave up.
Gave up on the entire framework-and-build-system mess. Hand-rolled the UI with some CSS. Used Alpine.js as a drop-in library for interactivity and reactivity. No build dependencies. No framework lock-in.
The update that proves the point (September 2025)
The Alpine.js-based admin has been working fine as a project. No upgrade nightmares. No ridiculous chain of dependencies. No lost days.
I continue to use the no-framework, only-library, modular approach for frontend projects that touch the JavaScript ecosystem.
Here's the mental model I carry now: if your tooling requires more maintenance than your application code, the tooling is the problem—not the solution. Choose libraries over frameworks. Choose stability over novelty. Choose shipping over configuring.
Revisiting this in 2025: the numbers tell the story
I'm revisiting this blog post in 2025. Four years of distance gives you clarity you don't have in the middle of a rant. So let me add what was missing from the original: hard numbers and actionable advice.
The dependency comparison I wish I'd made in 2021
When I was thrashing between Vue 2, Vue 3, Vite, and vue cli, I never stopped to count the actual transitive dependencies I was dragging into a tiny admin UI. I did that exercise recently. The contrast is staggering.
Read that again. 1,274 transitive dependencies for a small admin panel. Each one is a potential breaking change. Each one is a Dependabot alert. Each one is a node in the incompatibility graph that ate my entire day in January 2021.
The Alpine.js version? A single <script> tag from a CDN. No node_modules. No lockfile. No build step. No Dependabot.
Four years later, it still compiles. Because there's nothing to compile.
Aside: The exact dependency counts will vary depending on when you run npm ls. The Vue 2 + Buefy + vue cli number I got was from a clean scaffold with Buefy and sass-loader added. The point isn't the precise number—it's the order of magnitude.
What I'd actually recommend if you're starting a project in 2025
After four years of using the "no-framework, only-library" approach and watching the ecosystem continue to churn, here's my honest decision tree:
The first question isn't "React or Vue or Svelte?" It's: do I need a build step at all?
For anything that doesn't require client-side routing, complex state management, or server-side rendering—admin panels, dashboards, internal tools, marketing sites, documentation—the answer is almost always no. Drop-in libraries like Alpine.js and HTMX give you reactivity and interactivity without the 1,274-dependency tax.
If you do need a build step, choose the lightest tool that gets the job done. Astro, 11ty, and Svelte are built around the principle of shipping less JavaScript. They won't solve the dependency problem entirely, but they reduce your surface area dramatically.
If you're building a complex application—real-time collaboration, rich client-side state, deeply nested routing—then yes, reach for Next, Nuxt, or Remix. But go in with your eyes open. You're not just adopting a framework. You're adopting its dependency graph, its upgrade cadence, and its breaking changes.
Regardless of what you choose:
- Pin your dependencies. Use exact versions in
package.json. Lockfiles are non-negotiable. - Audit regularly. Run
npm ls --depth=0and ask yourself: do I actually need each of these? - Minimize the surface. Every dependency you add is a liability. The best dependency is the one you don't install.
- Start vanilla. Reach for a library only when vanilla JavaScript genuinely can't do the job—not when it's slightly less convenient.
The JavaScript ecosystem hasn't gotten less messy since 2021. If anything, the churn has accelerated. But my relationship with it has changed. I stopped fighting the mess and started routing around it.
That turned out to be the only fix that actually worked.