DEV Community

Cover image for Deno - An Unfinished Beauty
EmNudge
EmNudge

Posted on • Updated on

Deno - An Unfinished Beauty

2022 Update:

My concerns around immaturity have long since been addressed since the 1.0 release. While issues around arm32 builds might still exist, my concerns around instability, ICU, npm-interop, and permissions have all been addressed. You may read this article as a lens into the time period when it was released as opposed to a modern look on the tooling.

This is going to be a bit dense. I've been using Deno for a while now and I've been told it might be useful to publish my thoughts on it so far. I'll be jumping around a lot, going over its features, so don't expect a super philosophically consistent article. This is mostly an opinion piece based on my experiences.

For those unaware, Deno is a JavaScript runtime.

A What?

The differences between a JavaScript runtime and an engine aren't super clear, but colloquially I've found it to be:

JavaScript Runtime = JavaScript Engine + User-Facing Layer

The user-facing layer would be both the included APIs and how you run your code. Chrome, Node.js, and Deno all use Google's V8 engine to actually run the code, but they have varying APIs and ways with which you set up your code to be executed.

With Chrome you must include the script in an executed HTML file, with Node.js you use the node binary and perhaps a package.json, and with Deno you use the deno binary with a bunch of flags to define security and features.

As for their APIs, some things stay the same, while others might differ. All platforms implement the URL API the same. All platforms allow for WebAssembly, but the way your run WASM in the web differs from Deno and Node. Node and Deno both include (almost identical) path utilities, whereas the web does not. Deno and the web often include very similar APIs such as WebSockets, FormData, and ArrayBuffer, whereas Node might have something vaguely similar or requires third-party libraries. The web also includes some very useful APIs such as Canvas and DOMParser which Deno and Node are forced to use third-party libraries for.

But of course at the end of the day, they all run JavaScript. Deno is targeted at being a better version of Node, using the web and Node's faults as lessons to build from. It generally tries to match Web APIs where it can, but being a stand-alone binary, it can leverage a lot of power that a regular website cannot.

The Great

With that general introduction, Deno has a lot of really nice Quality Of Life features. I'm going to separate them into categories because they'd be too much to tackle individually.

The Standard Library

This one isn't mentioned all that often, but Deno has a really nice standard library. It has a lot of helpful functions that really do feel like extras rather than necessities, but I don't mean that in a bad way. Where the web and node will be forced to use 3rd party libraries for many things, Deno has some great packages out of the box.

When I say "replacement" I don't mean they perform the same thing, but you might not need the entire 3rd party library when you can instead use Deno's versions.

And then of course you've got your standard filesystem utilities

  • path - like Node's path
  • fs - like Node's fs
  • hash - like Node's Crypto library
  • http - like Node's http

and so on.

A recurring theme in Deno is going to be removing the need for 3rd party tooling, a popular complaint of the modern JS ecosystem. I think this is a much nicer benefit than people give it credit for.

TypeScript

I use TypeScript for just all my JavaScript projects these days. It's more about type completion and IDE intellisense than anything else. Refactoring code is much much easier and I don't have to keep tabbing between documentation and my code editor.

Deno includes TS compilation out of the box. It claims to be a runtime for TypeScript, but it's more like a compiler with some Deno integration. You can import a TypeScript file directly, it's rather quick at compiling, and it doesn't produce any JavaScript files as output, as it's unnecessary.

Not needing to set up your whole project with ts-node is a big win for people upset with boilerplate and configuration.

It does feel like an unusual choice though, since TypeScript still feels "unofficial". It feels like as much of a necessity as something like Flow. I much prefer TypeScript and TypeScript does see much more use these days, but I do understand why some might see this as a bit controversial.

Deno's API

This is not the same as its standard library as these don't need to be imported and are generally considered stable (more on that later). Here are 2 fantastic ones:

  • FormData - a web API for sending forms instead of JSON-encoded data
    • Required for Discord Bots when uploading files, usually implemented with third party libraries in Node
  • fetch - a web API for sending server requests.
    • Node requires 3rd party libraries. There are several to choose from with different strengths.

I've done a fair amount of web testing and scraping with Deno. Having these 2 built in has been so much nicer than Node.js where the implementations are up to the library creators and may not follow how the web APIs work.

Deno also defaults to promise-based operations instead of callbacks. In Node you will often need to promisify libraries or APIs in order to not slip into callback hell. With Deno, Iterables and promises are defaults, so there's no need for glue code anymore!

The Good

Those are my favorites. Now onto the "good" - the things that are really nice to have, but not as important to me as the aforementioned materials.

Deno's Multi-Purpose Binary

Deno's binary doesn't just run code. It also provides the ability to install Deno projects, inspect a Deno project, bundle code, generate documentation, and format.

  • installer - install Deno projects as CLI tools, like npm i -g
  • formatter - format code like Prettier according to predefined rules
  • bundler - collects all code into a single file for use, even inlines imports for use in web environments
  • doc generator - emits documentation using JSDoc to stdout or JSON to be used with a documentation displayer.
  • info - shows dependencies
  • watcher - mentioned in passing, it is an argument that arguably replaces nodemon

A lot of these were again possible with third-party tools, but having an opinionated, built-in way to do all this is super useful. The doc generator could be better, but is much better than nothing for all the people creating Deno libraries.

The formatter is opinionated, so it lets many Deno projects maintain a consistent style. There are some aspects to it that I would change if I could, but I'm sure others think similarly about parts I would hate to be changed, so it's a compromise.

The installer really cements my opinion of Deno's use case which I will again get into later.

No Package.json

This can also be seen as a negative, but it's again one of the usual complaints against Node. The package.json file is often unwieldy and hard to read for some. It includes metadata about the package, custom scripts, and dependencies split into different categories. It does a lot at once, so Deno just gets rid of it.

There is no one file to view all the dependencies, but that's what deno info is for. The less configuration a project has, often the easier it is to read, so that's a nice plus. With no package.json, package-lock.json, or node_modules/, the root directory is often a bit cleaner than most Node projects.

Import Maps

One of the "bad" parts of Deno is that imports are always URLs or paths, never referencing some locally installed package directory, like node_modules. Deno caches imports, so this isn't extraordinarily problematic, but it does make imports a big ole mess.

We've gotten rid of both the package.json and package.lock files, but to remedy this, people usually end up making somewhat of an exports.ts file. This file imports everything from URLs and then re-exports it for the rest of the package to use. This feels like yet another package.json, so Deno also includes import maps.

Import maps let you alias imports and are something seen usually only with build tools like Rollup and Webpack. So now instead of looking at:

import { readText } from 'http://packages.example.org/deno/cool-coder55/CoolLib/src/utils';
Enter fullscreen mode Exit fullscreen mode

We can instead have our imports looking like:

import { readText } from 'CoolLib/src/utils';
Enter fullscreen mode Exit fullscreen mode

I've taken advantage of this a lot. Sometimes URLs are also versioned, so this allows us to update the version of a library by changing it in one place instead of every URL in our project one-by-one.

Security

It might seem a bit weird to list this one last, but it's not something I think is as effective as it seems.

Deno forces users to also list the permissions they grant a program when installing projects or running files. This can be things like network ability, write access, read access, and environment variable access.

This is usually one of the first things about Deno mentioned and I do think it makes sense as a default. It's sort of like immutability as a default in a language, you'd rather choose times when things should have access then try to restrict things after. I just don't think it has quite the effect that it feels it would in practice.

The OSS scene is usually vetted for malicious code, so people are unlikely to check exactly why a program requires certain permissions. In fact, I often see people run files with -A which automatically grants all permissions, rendering this precaution null and void. However, I do see cases where this is useful.

The Bad

With all the good over, we can now get into the frustrations. This section is not as large as the previous 2 combined, but it arguably has greater weight when deciding whether or not to adapt Deno for your projects. You may have been able to pick out some of this section from how I phrased things in earlier sections.

Immaturity

What's fairly obvious is Deno is new. like really new. v1.0.0 was released in May of this year (2020) and it still feels far from 1.0.0 when looked at as a whole.

Deno still has no Arm32 builds which means that hosting scripts on a Raspberry pi Zero, 1, or 2 is not yet possible. I unfortunately produced a Deno Discord bot using Discordeno before finding this out and I have since rented a VPS.

Deno has not had ICU support for a while, which means all JavaScript unicode-aware functions and properties will not work. This includes the u RegExp flag and String.prototype.normalize(). It was initially rejected due to the proportionally massive bump in file size this would force, but recent discussion has shown that they're making progress with its integration.

There is clearly a problem with npm interop. There has been some recent work towards that goal, but it's nowhere near ready. There are ways to import web-libraries or node libraries that don't use any Node APIs, but if your png-to-jpeg Node library even makes a single call to Node's API, that package isn't getting in your Deno project without a repository fork.

There are still a few web APIs that Deno lacks. Node has some third party libraries to fill in the gaps, but these cannot be imported by Deno, so we're stuck without any option in that area. Sometimes the problem is even more fundamental than it just using Node APIs, like with node-canvas using Cairo. I am watching this issue at the moment, though.

I don't know of any build tooling, so if you have a particular feature you'd like to add onto JavaScript, like glob imports, good luck. This is theoretically possible to implement, but I've yet to see it in practice and I think many users of Deno would be against it philosophically.

Deno has some support in code editors and debugging environments, but because it's a small player and it piggybacks off of the work done for Node.js, its debugger in VS Code has broke due to some update, which forced me to roll back my VS Code version.

Additionally, I've found bugs in Deno itself. This isn't much for a library, but when it affects crucial things like text rendering in console and leads to non-actionable error states, it surely presents a hurdle for anyone with less experience who wouldn't know where to turn.

In general, I don't think I would have faired so well if I didn't have years of experience with other frustrating dev environments and the know-how of who and where to ask for help.

Breaking Changes

Oh, and on the topic of it not feeling like 1.0.0? The standard library seems to be far from 1.0.0 these days.

The standard library of course being the thing containing many very important utility functions, like basic file reading and parsing. Really interesting new libraries get added often and this means the entire library's stability is held back by new content. Since, according to the semver spec, semantic versioning doesn't need to be respected before 1.0.0, the entire standard library is unstable. Since most Deno scripts use the standard library, you cannot guarantee that you're able to run an older Deno script without rolling back your Deno version. The version of Deno isn't listed anywhere in a project, so you just have to guess.

A lot of very important features are locked behind an --unstable flag. With Deno, unlike some other languages/projects, this often means breaking changes are more likely than not. An awful lot is still behind stable, so it's rare to find a project that doesn't require it if it ever interacts with the file system.

--unstable also doesn't let you opt into specific features. Recently, Deno forced projects to use import type when importing types or your code would not run. This also affects libraries you import, so you cannot use modern Deno features anymore with older libraries. This requirement is theoretically going to be pushed into stable Deno, breaking its stability (unless they push the version to 2.0.0 by then).

The tsConfig.json can be edited, but most features cannot. Once you edit one, you must provide the defaults for everything else as well. Some features cannot be edited at all and this aspect wasn't extraordinarily clear, making the breaking change a very frustrating experience.

The Lack of Scripts

With the removal of package.json, we can no longer bake a bunch of custom scripts into the project with short aliases. With Deno, the command to run a program can be rather long. Assuming the program has yet to be installed globally, you might be using:

 deno run --allow-net --allow-read=/usr --allow-write=/usr mod.ts -q Stephen Fry -n funny -r 10 -p 10 
Enter fullscreen mode Exit fullscreen mode

And yes, this is very similar to a script from a project of mine. Everything after mod.ts is from my own project and so mostly my own doing. Luckily I can just press UP on my keyboard to re-execute it, but this makes managing multiple scripts a bit messy.

Some projects recommend including some sort of run.sh file when the same commands need to be run again fairly often. This ends up defeating the benefit of a cleaner root directory, but it does provide a simplification over package.json since the file only does one thing. This does require another file per script. We could probably add a scripts.toml file and then add a make a nushell or bash script to read it and execute the desired script, but these are again things not native to Deno.

Admittedly, the most common use cases for differing scripts are testing, transpiling TS, bundling, and file watchers. Deno includes all of those natively. One might use a bundler for more than what Deno provides, however, such as supporting custom JS features through a second stage of traspilation.

Conclusion - The Use Case

Deno is not production ready. It has passed 1.0.0, but it has not "passed 1.0". By "production ready" I mean don't build something if you're hoping for stability.

I'm a big fan of "new software" in general. Maybe that's a naive quality. I find that newer software has the advantage of creating much more sensible defaults based on past experiences. It is able to learn from other frustrations and break convention. Languages like Rust and Nim and now Deno show this well.

Even Deno libraries are often "better" versions of Node libraries that had some clear faults. It often takes some getting used to, but you end up with a better product (either in DX or performance).

Deno's main use case seems to be with small scripts. As long as these scripts are bundled using deno bundle and a Deno compatibility version is listed, it should be fine. It will be rather frustrating to switch Deno versions each time you want to run a script, however.

I don't live in the CLI myself, but due to Deno's match with Web APIs, I've found web scraping and http requests to be much nicer with Deno. There should be some advancement on that end as well if Deno ever decides to add DOMParser.

I'm going to continue using Deno for basic scripts and web scraping. I've worked through enough bugs and frustrations that I think I'll be able to avoid them to some extent. It's much easier to set up a Deno project than a Node.js one which makes prototyping much nicer. I think this is a use case most could get behind.

Top comments (11)

Collapse
 
ivan_jrmc profile image
Ivan Jeremic • Edited

Every programming language is a Runtime, Python, JAVA, C#, and so on, so basically, everyone calling Node a JS runtime is right but it is also unnecessary, Theoretically, you can call Node a programming language because this is exactly how other languages are built, They are a Box/Program where they run.

Collapse
 
hamidb80 profile image
Hamid Bluri

Whaaat

What do you mean by "Runtime" exactly?

Java is not a Runtime!

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

Java is a runtime language it runs on a runtime.

Thread Thread
 
hamidb80 profile image
Hamid Bluri

Java compiler compiles .java code to "java byte code"

Then JVM runs that byte codes.

Thread Thread
 
ivan_jrmc profile image
Ivan Jeremic

Yes thats what I said, Java is a runtime language.

Collapse
 
alexanderjanke profile image
Alex Janke

I haven't dabbled with Deno at all so far but I'm curious as to how people would tackle the missing script-section of the package.json.

For example I have a script:

"scripts": {
   "coverage": "test script here",
   "postcoverage": "do stuff after coverage is done",
Enter fullscreen mode Exit fullscreen mode

Like you described we probably could setup some .sh or other centralized collection of scripts but ... aren't we just rebuilding the package.json at this point?
Without any of those you probably end up noting all your possible scripts somewhere in your readme so other devs know what to execute? Sounds iffy. Am I missing the obvious solution to this problem?

Also, how would a typical CI like GitHub actions work out? Let's say we have a GitHub action running whenever someone wants to merge into our master-branch. What does the github action execute? The .sh? I'm really open for new things but I just don't see any gain from this :|

Collapse
 
sno2 profile image
Carter Snook

Work for a DOMParser that will be integrated into Deno going on at github.com/b-fuze/deno-dom if anyone wants to contribute.

Collapse
 
emnudge profile image
EmNudge

I actually linked it at the end! I’m familiar with the project and its contributors personally. I used it for a project of mine and I’m very happy it exists. It’s been making steady progress for a while now.

Collapse
 
sno2 profile image
Carter Snook

Ah, sorry. My eyes must've glazed over the difference in color. I even re-read the end twice searching for the linkπŸ˜‚

Thread Thread
 
emnudge profile image
EmNudge

Haha, not a problem! I didn't write anything to make you think it was different than the link previously in the article.

Collapse
 
swyx profile image
swyx

haha I enjoyed the part where package.json is effectively replaced by exports.ts! not trolling just mildly amused