Deno - New Dawn or Firefly?

Deno - New Dawn or Firefly?

What is Deno? - its motivations - and how does it weigh off against its spiritual predecessor Node?

In this post I’ve attempted to provide a useful resource for addressing the question, “What is Deno?”. In the account, I’ve tried to remain generally objective in terms of a ‘Node vs Deno’ argument and since my experience of Node projects has largely been project expansion (and just now adding just a dash of Deno and popping in the brain-oven for a day or two), I think it likely I might achieve that more through a sort of mutual blissful ignorance, than through any significant restraint on my part.

So, I’m somewhat tearing the lid from the Deno box, shaking the contents onto the desk and having a prod through its internet history whilst spending some very brief - if quality - tinker time in an effort to highlight some of the differences, similarities  - and possible pros and cons of the technology.

This piece has many links to other sources - both official and otherwise - and is intended to inform, so I hope you enjoy having an explore around the edges and perhaps have time to play for yourself.

Suffice to say, Deno does have an appeal for me in some - possibly nostalgic - areas around clarity and simplicity and I think I could very well consider it for a variety of scripting (helper style) tasks. Time will tell for more complex work and whilst I certainly wouldn’t discount it, I’d presently reserve judgement that decision-makers might feel the same in a project technology showdown.

My name’s Simeon and I work for Savient, a growing technology solutions company & consultancy based in Cheltenham, UK. From a C++ software engineering background over 20 years ago - ranging across production graphics tooling, aerospace trainers and varied consultancy in between - my experience has far more breadth than depth these days, but I do very much enjoy diving into specific subjects & technologies given the opportunity.

So what is Deno? ..

Deno is a secure Typescript & Javascript runtime in V8. Developed by Ryan_Dahl  - the creator of Node.js - and is intended as a re-imagining of a server-side runtime.

First announced by Ryan.D at JSConf.EU in June 2018, it accompanied a presentation and retrospective regarding Node in which he highlights his regrets about some design decisions (or in some cases, their absence) in its original conception.

Deno (v1.21.3 at time of writing) is pitched as a rethink of the Node concept, redressing those perceived failings, accommodating JavaScript's evolution in the last decade and with the aspiration of enabling a 'scientific language get-coding feel' whilst also enabling developments to rapidly evolve to more robust solutions - by enabling TypeScript support out of the box.

Dahl draws an analogy of Deno against his discovery of Perl - after primarily using C in education - and the freedom he experienced in the easy power and reward of a dynamic language.

As opposed to Node’s C++ construction, Deno is built primarily using Rust (though formerly Go) and interchanges data with Googles V8 Engine through Rust Crate objects (housing typed-arrays). To date, this aspect has been highlighted (by Dahl and community) as one of Deno’s core performance constraints (in the Serialize/DeSerialize of this messaging) and a Key Performance Indicator for the team when committing new core code (Deno Benchmark).

Deno - for reasons that are perhaps understandable - was not originally designed to be compatible with Node.js modules.

Since v1.15 (Oct 21) however - Deno provides a compatibility mode that enables a subset of programs authored for Node to run directly in Deno (listed on the link), so it's possible slow uptake may have fuelled a course correction.

So what's different

Security

[spri@fedora deno]$ deno run --allow-net=github.com,192.168.1.1 --allow-read=/~ --allow-env=HOME my-script.js

As opposed to Node, which has the potential to provide an ambiguous level of access between the V8 VM and OS, Deno’s default position is to be locked down and to explicitly require a user to define the scope of access Deno code may have.

At its simplest, this means providing cli permissions (such as --allow net , --allow-read, --allow-write amongst others ) to enable a script to access network, disk and other OS features. It is also possible to apply these permissions selectively and specifically at runtime.

In the absence of these, a Deno program will prompt a user every time an access is attempted, which - if simplistically - gives the developer clear oversight every time their code attempts to reach out in this way.

In the spirit of limiting access to known channels, any code invoking potentially sensitive access must do so via the standard library functionality provided by the Deno core team, but more on this later.

TypeScript

With the goal of enabling prototyping behaviours to more readily evolve into robust code development ,Deno enables the optional use of TypeScript out of the box. Runtime checking for this can be toggled at the command line (--no-check).

Support for Promises

Deno’s API were written with the concept of Promises built-in, so there’s no longer a need to have callbacks everywhere and not having to mix and match promises and callbacks would appear to provide a simpler experience.

It's also possible to have a top-level await without first having to wrap it in an async function first - enabling the removal of a bit of annoying boilerplate….

//await-my-woohoo.js
await Promise.resolve(console.log('🎉'));

…though possibly the merit of this may be lost on anything except the simplest of scripts.

Tokio & libuv - in the wild

For asynchronous behaviours Node used libuv (an abstraction of libev[ent] which was primarily built for its use). Deno instead uses an async Rust runtime called Tokio for multi threaded scheduling behaviours employing Rust Futures (somewhat analogous with a JS Promise). Tokio as a technology selection in this instance appears as simple as its nativity to Deno’s Rust codebase.

There is a deeper discussion concerning Event Loops and performance (with particular reference to libuv & Tokio) and for those interested a narrative & potted history may be found here - (The battle of the event loops). The original video presentation here by Ujjwal Sharma at HolyJS 2019 - 1 hour).

Notable that from observations over the intervening time, libuv remains the more performant.

Dependency System Simplification

Package Management - node_modules is dead, long live deno cache

Deno has dispensed with the need for any external package management system and no longer needs supporting dependencies to be held within the code project (so no more local /node_modules, npm, or nvm ).

Further, it does away with the package.json and the possible fallback to index.js, neither of which are required.

Whilst Node had a range of common functionality built in, Deno has been constructed in as minimal a fashion as possible and provides a growing set of standard library functionality that can be drawn on using standard ES import statements (as opposed to the Node require).

ES style imports

In contrast to Node's potentially ambiguous Require statements (i.e leaving it up to the resolver to work out file extensions), Deno Import Statements must be explicit, but are more in keeping with general web-development behaviours in allowing the developer to import from any accessible resource location.

import { copy } from "https://deno.land/std/streams/conversion.ts";
import { myobject } from "./my_stuff.ts"
import { bobs_ob } from "https://somewebsite.com/bobs-stuff.js"
Examples of a Deno Import

To avoid uncontrolled dependency change, imports are vendored to a cache upon first execution and subsequent executions will use this cache unless explicitly refreshed (see --reload ).

Deno has default cache locations on install, but this may be specified at the command line (see DENO_DIR here or cli >deno info )

For the purpose of production and general project stability, it's suggested that these caches can be captured in a git repository, however it's not immediately clear how that might work in practice, or that a centralised cache simplifies management, particularly if many projects are in play.

Fortunately however, it is also possible to prompt Deno to vendor dependencies for a named component to a local vendor folder, but - waaait a minute 🤨 -  this feels familiar .. /deno_modules anyone?... but at least there are options I guess.

Avoiding Dependency Hell

Through the decision to provide a grounded standard library for fundamental behaviours, Deno hopes to pre-emptively address the issues of dependency hell - where an apparently small dependency requirement can spider into an unpleasant cascade of further knotted dependencies or incompatibilities.

The aim being that since all 3rd party builds will ultimately terminate in the standard library, that the dependency tree should resolve relatively quickly within that.

Language Aesthetic

In general, some of the fundamentals of Deno language and environment appear to be oriented with Web and Linux terminologies and behaviours in mind [ “Dahl - more Webby and Unixy” ].

Webby Behaviours

From a web perspective, Deno supports the execution of WASM libraries and features like Fetch, Web Workers, Web Storage, WebSocket and the main window object, amongst others.

Whilst some have questioned the need for server-side code for some of the client side-representations (given the possible confusion), there are suggestions that it may support maintainability in Isomorphic JS development - which defines some merit to the idea of client/server side duplication (for SEO amongst other things).

For a great brief description of Isomorphic JS check out this short video - What the Heck is Isomorphic JavaScript.

For a good overview of Deno’s present capabilities on the web compatibility front, also take a look at this recent blog post.

Unixy Behaviours

Perhaps more inclined toward standard scripting tasks, Deno maintains a Unix-like approach to resources and uses recognisable terminology like stdin & stdout as well as maintaining a record of current resources (such as open files), providing identifiers that may later be used to track and manage them.

To obtain an understanding of Deno’s core capabilities - web oriented or otherwise - its possible to query the cli to output the core API code.

[spri@fedora deno]$ deno types > types.ts
//deno_test.js

let filename = "/etc/passwd"; //or Deno.args[0] for cli args

//Get current resource table
console.table(Deno.resources());

//Open a file
let file = await Deno.open(filename);
let resourceId = file.rid;
console.log(`File ${filename} Resource ID is ${resourceId}`);

//Get current resource table
console.table(Deno.resources());

//Close the file - using ID
await Deno.close(resourceId);

//Get current resource table
console.table(Deno.resources());
Example of a Linux-like script

produces...

[spri@fedora deno-ex1]$ deno run deno_test.js
┌───────┬──────────┐
│ (idx) │ Values   │
├───────┼──────────┤
│     0 │ "stdin"  │
│     1 │ "stdout" │
│     2 │ "stderr" │
└───────┴──────────┘
⚠️  ️Deno requests read access to "/etc/passwd". Run again with --allow-read to bypass this prompt.
   Allow? [y/n (y = yes allow, n = no deny)]  y
File /etc/passwd Resource ID is 3
┌───────┬──────────┐
│ (idx) │ Values   │
├───────┼──────────┤
│     0 │ "stdin"  │
│     1 │ "stdout" │
│     2 │ "stderr" │
│     3 │ "fsFile" │
└───────┴──────────┘
┌───────┬──────────┐
│ (idx) │ Values   │
├───────┼──────────┤
│     0 │ "stdin"  │
│     1 │ "stdout" │
│     2 │ "stderr" │
└───────┴──────────┘
IO Tables

Deno & React

Since my own experience of using React and Node are relatively limited, I’ll refrain from trying to dig into too much detail here, however some research - and a quick play hack here and there - identifies the Aleph JS framework as a potential parallel to Next and Gatsby (using Deno for server-side operations).

In line with Deno itself, Aleph appears relatively early stage, moving to release v1.0 in April 2022.

Some highlights are as follows:

  • Supports Typescript by default (because Deno)
  • Server Side Rendering and Static Site Generation
  • Built in CSS support (LESS & SASS)
  • Doesn’t require webpack or other bundler - using ES syntax
  • Hot Module Replacement with Fast Refresh

Other [cool] Features

In addition, to the other features already covered Deno provides a number of built in tools useful for working in JavaScript & TypeScript

Amongst Others…

  • Script Installer - install and distribute single-executable code
  • Linter (deno lint) -  code linter for JavaScript and TypeScript.
  • Bundler (deno bundler) - output a single JS file for consumption in Deno, including all dependencies
  • Test runner (deno test) - test runner that you can use for testing JavaScript or TypeScript code
  • Code Formatter (deno formatter) -  code formatter that will auto-format js,ts,md,json and variants
  • Dependency Inspector (deno info) - will inspect a specified ES module and all of its dependencies
  • Documentation Generator (deno doc) - print the JSDoc documentation for each of the module's exported members.

Deno has plugin support in various IDE’s. My own explorations were carried out in VSCode, purely since IntelliJ IDEA appeared to require the payable Ultimate Edition to function.

Within VSCode at least, code completion and static linting - at least - are available and possibly much more that I’m unaware of.

In Closing

At the outset, Deno would appear to be borne of Ryan Dahl’s desire to get it right this time, where it concerns his regrets regarding Node and its entirely possible that initial burst of altruism to avoid those mistakes - by divorcing it entirely from Node - may make it a leap too far for some folks. This division - at least - appears to be having a minor change of heart, but it's equally possible that this is indicative of some adoption concerns, so you can judge for yourself if that's any measure of concern.

In exploring Deno examples - and in particular its applicability to the glue-like land of helper (bash) scripts and prototyping - I find myself quite drawn to it from its apparent simplicity, with the inclusion of core standard features making it easy to - like bash - just get going without the faff of significant dependency and off-putting project boilerplate that would otherwise defeat the object of the simplicity.

Add to this the power, flexibility and familiarity of JS with the ability to compile security-conscious self-contained binary outputs (clearly useful in cases of dependencies and ease of distribution) - make it feel like it has real potential to slide into that role quite elegantly.

Beyond this, whilst I can’t easily identify any noteworthy Deno-based developments as yet - and certainly I haven’t constructed any significant developments of my own during the course of this investigation - it would appear that it has the makings of community support that may enable it to parallel Node's depth in future, but - despite being four years now - it does still feel like very early days.

On the flip side then, it's unclear if the differences (benefits?) - valuable as some may be - will be enough for Deno to develop significant momentum of its own, at least as a match to Node’s already rich history, community and 3rd-party support.

Performance wise - and despite ongoing efforts - it would appear there’s still what one might call an emergent trade-off in the Deno architecture for the security it offers. Couple this with the Tokio components similar performance impediment - when compared to its libuv counterpart - and this doesn’t sound a claim that Deno will make in the short term.

Notably also - and perhaps unfortunately - Deno doesn’t presently exhibit anything that might be classed as a Killer App and its easy to imagine that in any risk-reward discussion - when making a case to select Deno or Node in significant new developments - it seems likely presently, people would land in the Node camp through the security of familiarity alone.

What is certain is that Node is not going anywhere anytime soon.