Skip to content

Command Palette

Search for a command to run...

How "Uncomplicate JavaScript" Got Complicated

10 min read

I never stuck to a single language when learning to code. Basic, Pascal, C — I've always been polyglot by nature. That served me well professionally. People in developer meetups knew me as a PHP guy until Node.js came around, then as a JavaScript person. Today I still write plenty of Go, but I suspect most folks in the community think I'm a frontend dev who lives and breathes Deno 24/7. Because that's pretty much how I come across — a JavaScript evangelist in the wild.

This piece is an open critique of both JavaScript and Deno. Ironically, I wrote a similar one about PHP back in 2015 — dig through the archives if you're curious. With npm suffering yet another string of security incidents, and axios — the package people clung to because JavaScript never had a proper standard library — getting hit with a supply chain attack today, it felt like the right time to write this.

To sum it up: my intellectual output in software has mostly revolved around JavaScript. And right now, I'm putting on my journalist hat to do some open editorial follow-up.

Deno 1.0 — The Phantom Menace

Like everyone else, my biggest gripe with JavaScript since ES6/ES2015 has been the tooling chaos. The node_modules hellscape, the never-ending parade of transpilers and compilers, dozens of tools — many not even written in JavaScript — dictating the standards: webpack, rollup, vite, turbo, jest, babel, TypeScript... We were essentially doing local DevOps just to get a project off the ground.

I personally tried to reinvent what Vite + Nitro does today two or three times — with sey in 2015 and darty in 2018.

Despite the mess, there was a promise the tooling community kept making: "V8 is catching up, once everything aligns with ES2015 standards we won't even be needed." I bought it. Because unlike most languages, JavaScript doesn't belong to any single team or company — it belongs to the commons. Every tool comes and goes; what matters is the forward march. If we were talking .NET, we'd have to worry about Microsoft's priorities. But JavaScript was ours. The diversity of tooling was a feature of the ecosystem's breadth — every possibility explored, the fittest surviving through chaotic but vibrant natural selection.

Ryan Dahl and "10 Things I Regret About Node.js"

Ryan Dahl first put server-side JavaScript on the map with Node.js. Then he answered the mess I described above with what's still Deno's website tagline today: "Uncomplicate JavaScript." His contributions remain criminally underrated. The man is still fighting a legal battle over the JavaScript trademark as we speak.

When he took the JSConf EU stage in 2018 with "10 Things I Regret About Node.js," the manifesto was clear: strip away the design mistakes he couldn't foresee when building Node. The node_modules chaos, the all-or-nothing permission model, the tight coupling with package.json — he owned all of it, said "I'll get it right this time," and introduced Deno. I've got a stream on my YouTube channel reviewing Deno 1.0 when it dropped. Boy, was I optimistic 🙈

Because Deno's promises read like a prescription for every average developer's headaches: TypeScript as a first-class citizen. URL-based imports so you'd never need a centralized package manager. A granular permission system that made security the default. A clean, consistent standard library built on Web APIs. Ryan had convinced us — and himself — that what we saw in Go and Rust was possible in JavaScript too. A clean, deterministic ecosystem where the toolchain stayed out of your way.

Brighter Days Ahead

With Deno's initial releases, that promise started coming true. We moved from CommonJS to ESM. Deno strong-armed Node.js into an ESM transition it had been dragging its feet on for years. The unified toolchain was heaven. One day you ripped out eslint, prettier, and jest from your codebase and nothing broke. Deno never got the recognition that the vite ecosystem or Bun got. I think Deno was a revolution — and the whole point wasn't even to capture people's machines. They didn't play populist.

I thought Deno was too idealist for its own good and wasn't telling its own story well enough, so I tried to be its voice in the communities I was part of. I've probably given 10+ talks and YouTube streams about it. Through our local JStanbul meetup group, I must have introduced Deno to hundreds, maybe thousands of people.

When I reached out to the Deno team about all this effort and asked "could you list us as an official meetup group?", the answer was something like "we don't have a program for that, but we can send you T-shirts." My thing isn't merch — I buy my own :) But none of that mattered. I believed in the idea, not the team's community skills. I guess I'm a hopeless idealist and a Star Trek fan. The Enterprise crew seeking out something better, pushing toward modernity — that's what draws me in. I was following people like Ryan who were searching for what's right for JavaScript's users.

I went all-in on Deno for my own projects. Built my eser/stack monorepo on top of it. Formatter, linter, test runner — all built-in and consistent. In the Node.js ecosystem you had to pick, configure, and maintain a separate tool for each of those. With Deno, everything just worked out of the box.

Deno 2.0 — Attack of the Clones

The Node.js clones'... sorry, Bun's arrival in 2022 changed the balance. Bun originally showed up as something like a bundler à la vite, then pivoted on the way to 1.0 toward being a drop-in Node.js replacement — just faster. Bun claimed it could run npm packages seamlessly, implement Node.js APIs, and do it all with jaw-dropping performance. The market rewarded that.

Deno — by then a company — responded with Deno 2. And that response fundamentally altered the project's direction.

I won't list everything — package.json support came back. node: specifiers put backward compatibility with Node.js's standard library and npm packages on the roadmap. These were pragmatic, adoption-driven decisions and they made commercial sense. Deno now had to survive as a company, and that meant growing the user base.

Credit where it's due: this runtime competition benefited the ecosystem. As I mentioned, Deno's existence pushed Node.js toward standards compliance. Then Bun came along and pushed Deno toward node-compat. Each time, the broader JavaScript ecosystem won. But in the process, Deno went from being the one applying pressure to the one under pressure.

So I'm not criticizing the pragmatism, or a Deno that got pulled down to earth by competitors. Deno 1.0 could have shipped with a node-compat mode too. My criticism is that the leadership of modernization was abandoned. I'll dig into that in the next section. Because where we stand today, Deno and Bun are "Node.js clones."

Deno 3.0 — Revenge of the Node?

What I noticed with Deno 2 was this: Deno was no longer expanding its own API surface — it was implementing Node.js's. Every time Node.js maintainers add a new API — a logger interface, a new diagnostics API, whatever — Deno and Bun have to scramble to make themselves compatible. And they'll keep doing that. Bottom line: Node.js expands, Node.js calls the shots, and everyone else just plays catch-up. Nobody is building anything new to get JavaScript out of its mess. Everyone seems content enough with the status quo that all they want is for their own runtime to be the one people use. Let's just be pragmatic about it. JavaScript idealism, its modernism — dead.

When npm kept running into problems, JSR emerged. Outside of pnpm, nobody stepped up. I've been practically single-handedly pushing JSR support for the npm CLI since October: https://github.com/npm/npm-package-arg/pull/214

I recently had a similar pull request experience with the Deno project itself. Those who know me know I'm no fan of the Deno.* APIs — I think they should be extracted into a JSR library. Still, using Deno's Deno.* APIs is more Deno-native than reaching for Node.js APIs through the compat layer. So I opened a PR: https://github.com/denoland/deno/pull/33049

After a Deno maintainer suggested I just use the Node.js compatibility layer, I followed their advice from the PR and opened an issue to discuss it properly. My argument was straightforward: Deno was born to fix Node.js's design mistakes. That's its realpolitik — the stated reason for its existence. Deno's own linter has a rule blocking the use of the process global. Yet ironically, maintainers are now telling contributors to "just use process.argv0 from node-compat" for a missing API.

I opened the issue too, and the picture became even clearer: https://github.com/denoland/deno/issues/33051

The Deno side says...

  • The no-process-globals lint rule will be removed in the next release. Meaning Ryan's principle that browser JavaScript and server JavaScript shouldn't diverge? No longer in effect. Using Node.js's process global in Deno is no longer considered bad practice.

  • "We will not be adding new Deno APIs to cover what's already available in node:* APIs." So Deno's standard library is now Node.js's standard library.

  • "So just use process.argv0 :)" So that's the end of the revolution, then.

These aren't my feelings or interpretations. This is a strategy explicitly articulated by project maintainers, openly steering users in a direction. And that direction leads to the death of modernization.

ryan's deno3 tweet

I'd like to read all of this alongside Ryan Dahl's tweet about Deno 3 being a Python 2.7-to-3 style transition where "changes are coming" — a tweet I replied to with "I hope this means we're dropping Deno.* APIs, having those on JSR would be wonderful."

What Shouldn't Have Happened

In Revenge of the Sith, Padme Amidala (no spoilers, but things don't end well for her) says "this is how democracy dies, with thunderous applause." As someone who's been part of this community and tried to contribute to this modernization effort, that's exactly how I feel.

While writing this, axios got hit with a supply chain attack — because we never made the native transition to Web APIs, and people still use a third-party package for basic HTTP calls. npm treats me like a high-security bank when I try to update my own packages — countless OTPs, MFA prompts — and none of it matters. Because we're trying to hold together a patchwork ecosystem with band-aids.

Deno's original vision solved exactly this: make Web APIs first-class citizens, get rid of unnecessary dependencies, don't run postinstall scripts by default, let the permission model control everything. In Deno's world, fetch was already there — and thanks in part to Deno pushing for it, Node.js eventually got it too. You didn't need axios, and a package couldn't drop a RAT on your machine through postinstall because network access required explicit permission.

Am I supposed to keep living in the same toolchain reality that Ryan himself called a mistake five years ago with Deno 3.0 too? If there's no difference between Bun and Deno anymore, why am I still backing Deno?

Now the same Deno is dragging us back into the npm ecosystem. Same node_modules, same dependency chains... Today's axios attack shows how real and urgent the problems Deno set out to solve from day one truly are. And the irony: Deno has given up on solving them and is trying to fit in instead.

Obi-Wan: "You were the chosen one! It was said that you would destroy the Sith, not join them! Bring balance to the Force, not leave it in darkness!"

Share:

E
Written by
Founder @acikyazilim • Streaming @ eser.live • Open Source, DevRel, DevOps and Agile Evangelist • Generalist
Publications

Discussion

No comments yet

Be the first to comment!