I think Deno made a huge mistake.
Deno intended to be the redo of ‘Javascript outside the browser’, making it simpler while getting rid of the legacy.
When Deno was announced in 2020, Deno was its own thing. Deno bet hard on ESM,
re-used web APIs and metas wherever possible, pushed for URL imports instead
of node_modules
, supported executing typescript files without tsx
or tsconfig.json
and so on.
However since 2022, Deno is trying to imitate Node more and more, and this is destroying Deno’s ecosystem.
Users’ Perspective
“If Deno implemented Node APIs and tried to imitate Node and NPM ways of doing things, existing libraries and frameworks written using Node will automatically work in Deno and thus adopting Deno will be easier.” I don’t know who said this, someone must have said this.
What has happened instead, is that Deno trying to imitate Node has disincentivized formation of any practical ecosystem for Deno, while the existing libraries and frameworks are unreliable when used with Deno.
I tried using Next.js via Deno some time back, and Next.js dev server crashed when Turbopack is enabled. There is a workaround, so for the time being that issue is solved. But today there is another issue, type checking (and LSP) for JSX is broken.
This is my experience with using Node libraries with Deno. Every hour of work is accompanied with another hour (sometimes more) of troubleshooting the libraries themselves.
I think this is the consequence of trying to imitate something you are not. Deno is trying to be compatible with Node. but there are gaps in the said compatibility. I think achieving compatibility with Node is hard, and the gaps in compatibility will stay for a long time.
For example, at the time of writing, FileHandle.readLines is not implemented in Deno.
import fs from 'node:fs/promises';
const hd = await fs.open(Deno.args[0]);
for await (const line of hd.readLines()) {
console.log("Line: ", line);
}
The above script crashes despite having no issues with Typescript.
$ deno check test.ts
Check file://path/to/test.ts
$ deno run -R test.ts input.txt
error: Uncaught (in promise) TypeError: hd.readLines(...) is not a function or its return value is not async iterable
for await (const line of hd.readLines()) {
^
at file://path/to/test.ts:4:29
$
Using NPM libraries is also typically accompanied with a complete disregard
for Deno’s security features. You just end up running deno with -A
all the
time.
Library devs’ Perspective
Deno 1.0 is released, and library devs are excited to join the ecosystem. Projects like drollup, denodb, drizzle-deno are started,
But then Deno announces Node and NPM compatibility and all that momentum is gone.
Now, it seems like Deno’s practical ecosystem is limited to first party libraries like @std and Fresh, libraries on JSR, and a small subset of libaries on NPM that works on Deno.
If you look at the situation from library or framework dev’s perspective, it all seems reasonable. Most of them are not new to Javascript; they are much more familiar with Node than with Deno.
When Deno is announced, some of them might want to contribute to Deno’s ecosystem. But then Deno announces Node and NPM compatibility, and now there is not enough incentive to develop software for Deno. It doesn’t matter that Node compatibility is spotty, because they’d rather just go back to using Node like they’re used to. Supporting multiple runtimes is painful. If you want to understand the pain, ask anyone who tried to ship any cross platform application written in C or C++.
Deno should have promoted its own API
If the competition is trying to be more like Node, Node is the winner.
There is a lesson to be learned here. If you are trying to replace a legacy system, don’t re-implement the same legacy system. Instead, put the burden of backwards-compatibility on the legacy system.
Deno aimed to uncomplicate Javascript. (Deno’s homepage literally says that.) By trying to mimic Node, Deno has unintentionally put Node’s complexity problem at the center of the stage. And now, it cannot be removed. Instead of being a brand new thing, Deno ended up being a less reliable variant of Node.
Deno should have supported its own API on top of Node instead. Since Deno controls its API, supporting its own API on Node would be simpler than supporting Node APIs. For library and framework developers, libraries made for Deno would work on Node and there would be no need to support multiple runtimes.
This would have resulted in a much larger ecosystem of software made for Deno which is more reliable and free of Node’s legacy.
Deno intended to be the redo of 'Javascript
That’s their first mistake.
I love coding in JavaScript but most other aspects are atrocious. ESM and CommonJS basically splinter the entire ecosystem, node_modules and dependency resolution is a mess, the heavy use of Promises make control flow difficult to follow, and the use of native code is complicated so vendors are less inclined to support JS drivers (i.e. they’d basically hand over the source code).
Statistically speaking, you are right.
This was pointed out a few years ago, using comparisons from the js framework wars, and the js tooling wars.
To gain adoption, they need to do things better by a multiplier. Why is Vite the standard now? Because gulp was doing too much, and webpack was a complex mess. Vite is so fast, lightweight, and works.
Node and the Node contributors are taking every single idea Deno has and implementing them faster and better.
Deno is a nice idea, but it’s getting their lunch eaten.
Given the prevalence of NodeJS and its compatible tools and platforms, I can’t see it as a mistake.
Through compatibility, Deno established an upgrade path.
However since 2022, Deno is trying to imitate Node more and more, and this is destroying Deno’s ecosystem.
My impression was that Deno specifically does not try to nor want to imitate Node. They specifically announce and document their intended tooling and ecosystem which is different from the NodeJS and NPM ecosystem.
Their reasons for NodeJS support is for compatibility and enabling users of those platforms to use Deno.
Without it, I don’t see Deno replacing NodeJS in a considerable manner. Now, it’s a possibility. (But the sheer volume and prevalence still makes it seem unlikely.)
People may show excitement over the hot new thing, but nobody is going to migrate their entire architecture/platform over unless it’s incredibly convenient or they’re rebuilding from scratch. Backward compatibility makes it less of a barrier of entry to migrate.
That’s good for adoption.
But also a foot gun, since the roadmap of Deno just starts to look like Node.
Through compatibility, Deno established an upgrade path.
Sure, but Node compatibility needs to work, and it needs to work reliably. Which means every last detail of Node needs to be supported.
This is what I am trying to convey… the engineering effort to make an objectively better JS runtime while being Node compatible is likely too much effort. Many popular Node projects are already having issues with Deno. Now imagine how the compatibility scene will look like with every single proprietary Node project out there.
So instead of trying to replace NodeJS or offering an upgrade path for existing Node projects, incentivize formation of ecosystem around Deno.
Maybe automated conversion as an upgrade path would be a lower effort with not the same, but similar usefulness.
You’re ignoring the fact that for many projects it does work.
It only needs to be perfect if you want to run 100% Node.js software unaltered. While that may be a lofty goal, it’s also an infeasible one.
That doesn’t mean imperfect support is futile though. By your logic, Bun has no right to exist because it only supports Node.js APIs and doesn’t have noteworthy APIs of its own, and they’re not perfect either. Yet they seem to be at least as successful as Deno is.
Or for an example in a different domain: Your argument would state that a project like WINE shouldn’t exist because it doesn’t have perfect compatibility with Windows, and it disincentivizes development of Linux games. Yet it is largely thanks to WINE that Valve has been able to make the Steam Deck and that Linux gaming is finally taking off.
I think what your argument fails to take into account is that you need a significant amount of users to make any impact on the market. And many users have legacy requirements that they can’t throw out overnight, so you have to support those legacy environments. And even with imperfect legacy support you can support your users, especially if the users are willing to make a few changes here or there. But if you have no legacy support, you also get no users except those that have niche greenfield requirements.
So instead of trying to replace NodeJS or offering an upgrade path for existing Node projects, incentivize formation of ecosystem around Deno
They are incentivizing their own ecosystem. That’s what Jsr.io is all about. But the world isn’t black and white. They can do more than one thing.
There is no way legacy projects are going to switch to Deno. Even when Deno is 100% compatible, the only advantage Deno provides is slightly higher performance. Node’s complexity problem? All those configs needs to be supported for compatibility anyway. Typescript? The project already has
tsconfig.json
set up, so they might as well continue to usetsx
. Security? I bet users will just get tired and use-A
all the time.To benefit from Deno, Node’s legacy needs to be shed.
Wine is a different case. The reason Wine makes sense is because Windows is so much worse than Linux that even with scrappy game compatibility, Linux offers a better experience. For Linux users, the alternative to Wine is not switching to Windows, it is not being able to play games. On the other hand, legacy Node projects have a very easy alternative… just continue to use Node.
And btw Bun is making the same mistake.
By your logic, Bun has no right to exist because it only supports Node.js APIs and doesn’t have its own APIs
I don’t know much on the topic, but I know I used Bun a bit for some scripts, and it most certainly does have its own APIs, so that’s incorrect.
Yes, it has a few APIs of its own. I merely think they are negligible in this discussion because they only provide a minimal superset over Node.js’s own APIs and are also very minimal compared to what Deno provides.
I’ve updated my post to mention “noteworthy” APIs.
I don’t think I’d agree about it being negligible, but I think that’s ultimately subjective, so fair enough. I get the impression a lot of Bun’s own library are nicer to use equivalents of libraries present in Node, but I think even that is significant for anybody looking to write new code.
The momentum on a lot of the “popular node.js package but for Deno” packages died well before they added npm: specifiers and more support for node builtins. Aside from a few notable outliers, denoland is full of stuff that hasn’t been updated in 4+ years.
What I like about deno is that I can do everything with web standards, then fill the gaps for server-side needs with the node standard lib. Web standards have come a long way for server code thanks to web workers, but there are still some missing pieces. The deno experience is way better with web standard libraries and jsr, so my hope is the momentum will continue to head in that direction such that the npm: and node: imports just become nice-to-have capabilities for niche uses.
Deno was created by the creator of Node. When he first announced deno he stated he was going to “fix” n number of issues with node. In his presentation with the n number of issues, I think only two of them weren’t the same dumb mistakes that he had made with node (but it’s been a long time since watching so I could be remembering wrong). As far as I remember, Deno was always going to end up just as bad as node if not worse.
They definitely didn’t. Yes it is technically better to use their own new fancy system, but they’re a business. Backwards compatibility is killer, even if you don’t want it from a technical point of view.
I guarantee they looked at the numbers, interviewed users and asked them why they weren’t using Deno, and the number one reason would have been “we’d love to but we need to be able to use the X node package”.
They probably have to improve Node compatibility, but the Node API surface is actually not that big. They’ll get there.