Figure: Source: https://deno.land/artwork
Deno (/ˈdiːnoʊ/, “dino”) is a runtime for JavaScript and TypeScript (as well as WebAssembly) announced in mid-2018 by Ryan Dahl, the creator of Node.js, and released exactly two years later in version 1.0. With Deno, just like with Node.js, the idea is to be able to execute JavaScript in the backend as well.
Now the question naturally arises as to why Dahl created Deno, although Node.js basically has exactly the same use case. There are various reasons for this, which he humorously summarises in his talk at JsConf EU “10 Things I Regret About Node.js” (2018). Dahl has a flair for dynamic programming languages, with JavaScript being the best dynamic programming language in his view. When he stopped being involved in the further development of Node.js in 2012, and instead became involved in developing performant servers in Golang, but became more involved with Node.js again in the six months leading up to the aforementioned JsConf EU Talk, he noticed the design flaws he had made with Node.js. Since the beginnings of Node.js in 2008, web development has evolved; areas such as “security”, which were not exactly at the forefront of Node.js’ design, have become more important. In summary, the lessons learned from the development of Node.js, combined with Dahl’s continued enthusiasm with JavaScript, led him to create Deno, and correct his mistakes of yesteryear.
To understand Deno, we first need to look at what Node.js design flaws led Dahl to create Deno.
Ryan Dahl’s Node.js design flaws
- Promises: Earlier adoption of Promises could have accelerated adoption of async/await.
- Security: A stronger focus on security from the start could have built on the existing potential of Node’s underlying V8 runtime.
- Node Package Manager (npm): Node modules are stored in a centralised privately controlled database. The fact that npm modules themselves can have many dependencies makes them confusing and vulnerable to insecure and potentially malicious code.
- node_modules: The node_modules folder, where modules are stored, must be recreated for each project, even if different projects use the same modules, and can get very large (see image below).
- CommonJS Module System (CJS): CJS is clunky and outdated compared to the newer way of managing modules, ES-Modules (ESM). Because CJS is so prevalent in Node.js, it can no longer be removed.
Figure: Source: https://github.com/denolib/awesome-deno/blob/main/resources/design-mistakes-in-node/design-mistakes-in-node.pdf
Deno’s features
So let’s take a look at Deno’s top features:
- Batteries Included: Deno does not require any additional utilities. Most of the required tools like Package Manager, Compiler, Code Formatter, Linter are directly included in Deno.
- Secure by Default: In Deno, access permissions, for example network access, hard disk access, etc., must be explicitly given. This prevents programmes from having only the permissions they need, according to the principle of least privilege.
- TypeScript support.
- Tested Standard modules that do not rely on external dependencies.
- ES Modules support.
- Packages/Modules are decentralised: Only one URL is needed to import the corresponding file.
- Top Level Await:
await
no longer needs to be wrapped in anasync
function, which makes for clearer code. - Browser API access: No additional packages are needed to use
fetch
for example.
As we can see, Deno has several features that make life easier for developers and improve the clarity and readability of code. Ryan Dahl’s problems with Node.js were mainly related to handling external dependencies, which Deno definitely does better. Since Deno comes directly with many practical tools, the term “runtime” is perhaps a little short-sighted. It could rather be called a JavaScript/TypeScript toolchain.
ES Modules
In Deno, ES Modules are used instead of CommonJS Modules. Importing packages then no longer looks like const package = require("package")
as in Node.js, but like import package from 'package'
. ES Modules have the following advantages:
- With
import
you can select exactly which part of a package you want to import, which saves memory. - ES Modules are no longer loaded synchronously as with CommonJS Modules, but asynchronously.
For example, an import of the assert.ts
package from the standard library of Deno looks like this:
|
|
Permissions
Permissions can be used to set the permissions of the programme that is being executed. These permissions are given by various flags. For example, if our programme should be able to access the internet, the --allow-net
flag must be given. If we don’t give any access permission explicitly, Deno will automatically ask us for each one if we want to allow it.
Let’s try to access the page https://example.com without permissions using the Deno version of a curl program:
Of course, we can now simply confirm with y, but if we grant permission from the beginning, we won’t get any problems and we’ll also get the corresponding header returned directly:
Tip: Use the -A
flag to allow all permissions. This can be helpful in development if you don’t always want to bother with the appropriate permissions.
Top Level Await
await
functions in Node.js always have to be wrapped in an async
function, which can get confusing:
|
|
The outer wrapper can now be omitted thanks to Top Level Await in Deno:
|
|
Structure of Deno
Deno is written in Rust and JavaScript. Basically, Deno is just a collection of Rust crates. With this information in mind, let’s take a closer look at Deno’s architecture.
The Deno runtime consists of the following building blocks:
- JavaScript Engine: V8 (C++) and layers built on top of it, namely
rusty_v8
anddeno_core
, which make V8 usable by Rust. - Event Loop: Tokyo - The equivalent of libuv in Node.js.
- Type Script Compiler: TSC + SWC
- Code Caching and Analysis: “module_graph”.
JavaScript Engine
Rusty_v8 is a rusty crate that provides high-quality bindings for the V8. This rusty_V8 crate is in turn used by the deno_core crate. deno_core is also a low-level crate, but one level higher than rusty_V8. So when you use deno_core, you don’t have to interact with the V8 directly, but the V8 is abstracted a bit, which makes your life easier.
deno_core
The deno_core not only provides the JavaScript runtime, but is also responsible for the implementation of ES modules. Deno_core is also responsible for the provision of “Ops” and “Resources”; this involves the connection of high-level JavaScript code with low-level Rust code. Basically, you can say that deno_core simply takes care of the execution of JS files. It is responsible for all tasks that are not taken over by Tokio or the V8.
Tokio
Tokio is the Rust Crate responsible for the event loop. Tokio is responsible for asynchronous I/O, just like libuv, which is used by Node.js. For the handling of these asynchronous processes, so-called “futures” are used in Rust, which can be compared with the concept of “promises” known in JavaScript.
module_graph
The module_graph takes care of the recursive fetching and caching of the dependencies. These are stored in a similar way to Node.js, but not per project, but are available locally for the entire system. The module_graph not only takes care of the caching of external sources, but also of the caching of local sources. This is the case, for example, when a TypeScript file is to be executed; this must first be transpiled to JavaScript so that it can run on the V8, and is also cached.
TypeScript Support
Regarding Deno’s TypeScript support, Ryan Dahl mentions in a podcast in 2021 that the early TypeScript support in Deno was a mistake. Dahl justifies this by saying that Deno’s main goal is to unify server-side JavaScript and browser JavaScript - but browsers fundamentally do not support TypeScript, which has greatly complicated Deno’s development and maintaining web compatibility.
One of the problems with supporting TypeScript out-of-the-box is that the TypeScript Compiler (TSC) runs completely synchronously, and is therefore slow. This problem was solved by outsourcing all heavy-lifting, i.e. dependency analysis, transpiling etc. of TypeScript code to Speedy Web Compiler (SWC), a TypeScript compiler written in Rust. Currently, TSC is only responsible for type checking TS code.
And the others?
- dprint: Rust-based code formatter
- deno_lint: Code Linter
- deno_doc: Documentation Generator
- There are many other Rust craters that are not visible on this graphic.
Figure: Deno Architecture
Deno vs. Node.js
Can Deno and Node.js be used together?
Actually, there is the possibility to use NPM packages in Deno as well. In many cases, these probably also work. You can find more information about this here. During my research I also came across this cheatsheet here, which simplifies the transition between Node.js and Deno.
Is Deno really faster than Node.js?
Meanwhile, Deno is in most cases equally fast or faster than Node when it comes to, for example, the number of requests a server can handle. I found an interesting benchmark here that compares many projects in this area on a daily basis. Deno itself tracks the speed for every single commit, which is a sign that this kind of optimisation is a high priority for the development team. Still, for specific use cases, there are probably more optimal, efficient solutions than Deno.
Conclusion - Is Deno really better than Node?
On a personal note; I was pleasantly surprised by Deno. In 2020, when the first version of Deno came out, there was a lot of hype in the JavaScript scene. This media attention quickly died down again, but the Deno developers stayed on the ball. Personally, I find Deno very attractive because of its simplicity and the fact that it comes directly with some practical tools. This, combined with the good conscience that the Deno Standard Modules are vetted by the core Deno team and that the ecosystem around Deno in general gets by with far fewer dependencies, makes one feel good.
Many wondered if Deno could replace Node.js in the future. I can imagine that, perhaps only for simple projects at the beginning. But the value proposition of Deno is probably not so strong that everyone will abandon their Node.js projects and run to Deno.
Further links
This TechUp has been automatically translated by our Markdown Translator. 🙌