I keep a list in my notebook of potential topics to write about in this newsletter. This list has already over 30 items, but it was already Thursday afternoon and I couldn’t find the motivation (nor the time) to write about any of them, so I chose to make a Twitter call to see if any of my followers could suggest a topic I felt a bit more excited to write about with the time I had.
It was then when my good friend @koke0117 mentioned Deno. Actually, it had been a few weeks since I started reading and playing around with Deno, so I opened my text editor and started writing.
From Node to Deno
So what exactly is Deno? “Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.” Started by Node.js creator, Ryan Dahl, it is an attempt to fix many of the current issues of Node.js while accommodating to the new trends and advancements in Javascript from the last ten years. This talk of Ryan at JSConf EU illustrates perfectly some of the reasons why he chose to start a new Javascript runtime project. Some of the things he mentions in the talk that would have done differently now if he were to redesign Node are related with the node_modules, Node.js’ security, and the ability to run code in the browser and in the server.
As result of these concerns, Deno was born. Let’s go through some of the new features introduced by Deno:
(i) Secure by default. Deno runs your code in a secure sandbox by default, so you will have to give explicit permissions to your code if you want to access files, the network, or the environment you are running on. Let’s see what happens if we want to run this simple script that fetchs data from an API:
const data = await fetch("https://jsonplaceholder.typicode.com/todos/1")
console.log(data)
If we run this using “deno run test.ts” we get the following:
Our script doesn’t have to permission to access the network. We need to explicitly grant him with the required permissions running “deno run --allow-net test.ts”. Pretty sweet, right?
(ii) Top level await compatibility, and Typescript support out of the box. Haven’t you seen anything weird in the code I shared above? I WAS USING AN AWAIT OUT OF AN ASYNC FUNCTION. This is another of Deno’s cool features. Forget about having to explicitly resolve promises using .then() when you can’t wrap the code inside an async function, Deno has you covered. The same way it has you covered if you want to use Typescript. In my previous example I didn’t have to compile my .ts file to run it, Deno directly knew how to do this for me.
(iii) And one of the features most loved by some and really hated by others, no need for NPM and the node_modules folder anymore. Deno moves from centralized package managers (npm or yarn) to the use of a decentralized approach where dependencies are imported using ES6 imports and the url address of the source code repository (the Golang way! Actually I am a bit biased because I love Go, so I won’t comment further about this). In previous posts, I already warned about the risks of centralized package managers, but if you want to read a bit more about this, check this post.
To test this new approach to dependencies you can run this example and see how the dependency is downloaded and made available to you in your local cache:
import { serve } from "https://deno.land/std@0.50.0/http/server.ts";
(iv) Deno ships software as a single executable file, and this is really convenient, as it produces a dependency-less single file from your code. You can, for instance, run “deno bundle test.ts test.js” in the previous example to see the magic happen.
(v) Built-in utilities and (audited) standard modules. Deno ships many of the things I love about Go such as “deno fmt” for code formatting and “deno info” for dependency inspection. Moreover, deno has a standard library with many useful functionalities to remove the need of external unknown dependencies.
(vi) Finally, Deno is compatible with “the browser” (Deno’s API should be compatible in the browser), and it supports the execution of Web Assembly (yay! and you know this is something I love).
Playing with Deno
If you want to start playing with Deno and try the examples shared above, the first thing we need to do is to install it. There are several ways of doing this, being my favorites the use of their provided install script:
curl -fsSL https://deno.land/x/install/install.sh | sh
Or if you have Rust installed, and as Deno is written in Rust, cargo installing it:
cargo install deno
Remember to export Deno’s path to use it directly from your CLI. To see if your installation has been successful, you can run the following command:
deno run https://deno.land/std/examples/welcome.ts
You’ll see how deno downloads every required dependency and runs the welcome typescript file 🦕.
A cool second example to test in Deno, is to run a web server using its standard library (it is the example suggested in their official site).
import { serve } from "https://deno.land/std@0.59.0/http/server.ts";const
s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
req.respond({ body: "Hello World\n"
});
}
With this simple piece of code you will see may of the new features of Deno in action.
I know Node.js already supports the execution of WASM binaries, but this was another thing I wanted try in Deno. There are several tutorials out there that guide you on how to build your Web Assembly binary from Rust or Golang and run it in Deno. All of my past approaches to WASM have been using Rust, so this time I chose to go with Golang, see what happens.
My first impression was that the integration WASM-Go is not as straightforward as with Rust (I haven’t figured out yet how to compile to WASM a function with parameters from Go and run it and passing the arguments from JS, but we are here to talk about Deno, more about this in future posts).
This is the simple program I am going to compile into WASM:
package main
import "fmt"
func main() {
fmt.Println("hello from WASM")
}
To compile it we just need to run the compiler with the following options:
$ GOOS=js GOARCH=wasm go build deno.go
$ mv deno deno.wasm
To run it from JS we first copy an auxiliary file used to load WASM from JS (I guess this file makes the interfacing work of the automatically generated JS files you get when compiling WASM in Rust). We copy this file in our project’s root:
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
And write the JS script that will run the WASM binary:
import * as _ from "./wasm_exec.js";
const go = new window.Go();
const f = await Deno.open("./deno.wasm")
const buf = await Deno.readAll(f);
const inst = await WebAssembly.instantiate(buf, go.importObject);
go.run(inst.instance);
This script loads the binary from our local environment and runs it. When running it we have to remember to give access permissions to file within Deno. Thus:
$ deno run --allow-read deno.js
And there you go, we have our WASM binary running within Deno.
Closing thoughts
I don’t think Deno will replace Node.js, and probably many won’t even switch to Deno for a single project, but for those of use more comfortable with typed programming languages, and psyched with the future of WASM, I feel Deno is a step forward the future of Javascript, as it introduces by design many features that in Node.js require a significant overhead if you want to use modern Javascript (I am thinking about Typescript compilation, bundles, and top level await support). Adding to this the improvements in security, it poses in Deno a really interesting alternative to Node.js in the long run. But this my humble and probably biased opinion, I would love to hear your thoughts about Deno.
PS: I highly recommend this post as a complementary view of the role and future of Deno in the ecosystem.