Deno - first impression

Cover image

Deno is a new secure runtime for javascript and typescript created by Ryan Dahl, the original author of NodeJS. Recently Deno 1.0.0 was released and this is my first impression about it.

Meet Deno

Deno is a new secure Typescript and Javascript runtime. Yes similar to our loving NodeJS. Deno was created by Ryan Dahl the original author of NodeJS.

Deno is written in Rust, Tokio and Typescript using V8 under the hood.

Why Deno, why another Node

So you might be wondering why Ryan put all his efforts to develop something new as Deno while his previous project NodeJS has gained a massive community and used by almost everyone today.

NodeJS was developed in 2009. Javascript today we see was not there when they were developing Node. Yes, that's why there is callback hell even in most libraries today you see. Node had to invent new concepts, later adopted by organizations like ECMA as language features. With the large userbase, Node moved very slowly.

You might be thinking what about babel, we can use new language features with Node easily using Babel. But you need to admit that it involves a lot of external tooling and if you ever tried to use Typescript with NodeJS, it's creating a lot of boilerplate codes.

This is not very pleasing. In my personal experience, this is taking some time to set up correct tooling and you need to maintain them as well.

Checkout 10 Things I Regret About Node.js - Ryan Dahl for more.

Let's check how Deno solves these issues.

Features

According to the official website deno.land, Deno provides these key features

  • Secure by default. No file, network, or environment access, unless explicitly enabled.
  • Supports TypeScript out of the box.
  • Ships only a single executable file.
  • Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).
  • Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std

In addition following things can be noticed

  • Decentralized module system
  • Browser like API

Secure by default ๐Ÿ”’

Deno provides a sandboxed environment. So when executing any program, you can determine if it can access your network, filesystem or environment variables etc. This means you can execute a script directly from the internet and control how it behaves. Just like a browser.

Typescript support ๐Ÿš€

Javascript is not good when your program scales. Typed languages provide much more insights and even cut half of the errors at compile time rather than in production. Typescript is written for Javascript, with types. Most JS developers love Typescript.

With Node, you have to transpile Typescript to Javascript using tools. Deno does this for you and even provides fewer boilerplates in your code.

Single Binary, no dependencies ๐Ÿ“ฆ

Deno is shipped as a single binary with no dependencies. If you compared to NodeJS comes with tons of shared libraries and other stuff, deno is much simpler. it's about ~14MB. Simple to install and maintain. Even perfect for DevOps environments.

Install Deno Now. It's simple. ๐Ÿ’ฏ

Excellent tooling ๐Ÿ”จ

Have you ever worked with language like Golang? Golang provides an excellent toolset with go command. You can format your code, find potential bugs and there are a ton of things you can do with it. Everyone uses go fmt. So the code looks the same everywhere.

But in JS world, we need to use external tools to achieve this. Also, there are like tons of coding styles.

Deno solves the issue by providing tooling itself. Yes, no extra tools needed. Run deno fmt in your project to format your code. Deno even provides a way to bundle your code without any tools like Webpack with deno bundle. Also, you can install scripts with deno install

Read more about tooling here

Standard Library ๐Ÿ“š

Deno provides a well written standard library. This allows writing programs as you do in a language like Golang. Provides much stuff out of the box so it's up to your imagination.

But the API is not compatible for NodeJS. But there are plans to bring existing large Node package base to Deno easily.

Checkout standard library here.

No NPM โšก

Node's eco-system is built around NPM. A centralized package management system. So if you want to publish a package you have to go through a process to let others use it.

Golang took a radical approach to use a decentralized module system. With Go, packages can be loaded directly from a Version Control system easily. No painful process involved.

Deno allows you the same. If you can put your code somewhere on the internet(for example GitHub), you can pull it directly to your application

For example,

import * as colors from "https://deno.land/std@0.50.0/fmt/colors.ts";

ES6 style imports directly over HTTP.

Don't worry, Deno knows to cache them. So you won't download it again.

Also, Third party packages can be submitted here to display nicely.

Browser like API ๐Ÿ‘Œ

Deno tries to stay as much as compatible with the browser. You can also have a window object like in a browser.

For example, famous fetch is available on deno as well.

const res = await fetch(url);

Creating a simple grep command in Deno

Install Deno

Follow the installation guide to get started with Deno. Check if it's installed by executing deno in your shell. It should be like this

Deno shell
Deno shell

Getting lines from Stdin

Our simple grep command will be executed like

cat file.txt | deno run sgrep.ts --colors=true needle

Create a file in your working directory called sgrep.ts

For a grep utility, we need to take input from the standard input. Deno provides Deno.stdin in the global namespace so we can use it directly to access the stdin as we do in any programming language.

To read line by line, we can use bufio module provided by deno standard library.

import * as bufio from "https://deno.land/std@0.50.0/io/bufio.ts";

Now we can use bufio.readLines() to read. This returns an AsynIterator thus we can simply use our favourite await keyword

import * as bufio from "https://deno.land/std@0.50.0/io/bufio.ts";

for await (const line of bufio.readLines(Deno.stdin)) {
  console.log(line);
}

Deno allows you to use await top level without wrapping it in a async function.

Now run this with deno run sgrep.ts

You will see something like below. Now try typing some words and check them echo back.

Reading stdin and echo
Reading stdin and echo

Parse arguments

Deno provides a simple method to get arguments anywhere. Use Deno.args will provide you with an array with the arguments passed after your file name

deno run file.ts --flag=1 anything after this

So Deno.args will return you an array with,

["anything", "after", "this", "--flag=1"]

Now to process flags, like --colors=true Deno provides a standard library module called flags

import { parse, Args } from "https://deno.land/std@0.50.0/flags/mod.ts";

const flags: Args = parse(Deno.args);

Now flags._ will contain anything other than a flag. In the above case ["anything", "after", "this"].

we can check for flag value like

  if (flags["colors"] === "true") {
    // do something
  }

So for our example, we need exactly one non-flag argument and need to check if colours enabled.

if (flags._.length !== 1) {
  Deno.exit(1);
}

if (flags["colors"] === "true") {
  // output colors
}

// read the first non-flag argument as needle
const needle: string = flags._[0] as string;

Showing grep output

To output, we can use string.includes like this

for await (const line of bufio.readLines(Deno.stdin)) {
  if (line.includes(needle)) {
    console.log(line);
  }
}

For colour output, deno also provides colors module.

import * as colors from "https://deno.land/std@0.50.0/fmt/colors.ts";

Try running

console.log(colors.red("hey this is red"));

If your terminal supports, it will show red colour text like below.

Color output
Color output

Now to highlight any text with the matching needle we can simply use a RegEx as below

console.log(
    line.replace(RegExp(escapeRegExp(needle), "g"), colors.red(needle)),
 );

escapeRegExp is used to escape the regular expression syntax as described in MDN Docs

function escapeRegExp(string) {
  return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

Connecting dots

Now final piece in the puzzle is to connect these dots.

We already know how to output colour and in normal mode. But doing this by checking for --colors in the for loop is not good.

Let's define our functions to print colour and normal

function normalOutput(needle: string, line: string): void {
  console.log(line);
}

function colorOutput(needle: string, line: string): void {
  console.log(
    line.replace(RegExp(escapeRegExp(needle), "g"), colors.red(needle)),
  );
}

Both functions have the signature of (string, string) => void. So we can simply call them using a function callback type in typescript(a function pointer if you familiar with C/C++)

let outputFn: (n: string, s: string) => void = normalOutput;

Now we only need to change this to colorOutput if the flag is enabled.

if (flags["colors"] === "true") {
  outputFn = colorOutput;
}

and finally in the loop

for await (const line of bufio.readLines(Deno.stdin)) {
  if (line.includes(needle)) {
    outputFn(needle, line);
  }
}

Running only in main module

Most programming languages allow you to have a main entry point for the program.

For example in go,

func main() {
  // application logic goes here
}

But with Node we did not have this.

Deno solves this issue as well providing you with a way to distinguish the main module from other.

if (import.meta.main) {
  // run main app logic
}

So our program needs same.

Let's refactor our program like below

Final Code

import * as colors from "https://deno.land/std@0.50.0/fmt/colors.ts";
import * as bufio from "https://deno.land/std@0.50.0/io/bufio.ts";
import { parse, Args } from "https://deno.land/std@0.50.0/flags/mod.ts";

function printUsage() {
  console.log("usage:");
  console.log("\tdeno sgrep.ts [--colors] needle");
}

function normalOutput(needle: string, line: string): void {
  console.log(line);
}

function escapeRegExp(s: string) {
  return s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}

function colorOutput(needle: string, line: string): void {
  console.log(
    line.replace(RegExp(escapeRegExp(needle), "g"), colors.red(needle)),
  );
}

async function run(): Promise<void> {
  let outputFn: (n: string, s: string) => void = normalOutput;

  const flags: Args = parse(Deno.args);

  if (flags._.length !== 1) {
    printUsage();
    Deno.exit(1);
  }

  if (flags["colors"] === "true") {
    outputFn = colorOutput;
  }

  const needle: string = flags._[0] as string;
  for await (const line of bufio.readLines(Deno.stdin)) {
    if (line.includes(needle)) {
      outputFn(needle, line);
    }
  }
}

if (import.meta.main) {
  await run();
}

Testing our sgrep

So when we run our program, we need cat to pipe out input to sgrep.

cat file.txt | deno run sgrep.ts who

sgrep output without colors
sgrep output without colors

cat file.txt | deno run sgrep.ts --colors=true who

sgrep output with colors
sgrep output with colors

Now we have created a small nice grep utility with deno. Hooray. ๐Ÿ’ช

You can find full code here.

Wrap up

Deno will change the future of Javascript and Typescript. It's still young, released v1.0.0 in 2020-05-13. But you now see its potential. The power brings down you with Typescript and other cool features. Rest is up to your imagination.

Visit deno.land today and start exploring this amazing world.

We should thank Ryan Dahl and other contributors for creating this amazing runtime.

Let's deno ๐Ÿ”ฅ