How to Use Deno’s Built-in Tools

Craig Buckler
Share

One surprising difference between Deno and Node.js is the number of tools built into the runtime. Other than a Read-Eval-Print Loop (REPL) console, Node.js requires third-party modules to handle most indirect coding activities such as testing and linting. The Deno built-in tools provide almost everything you need out of the box.

Before we begin, a note. Deno is new! Use these tools with caution. Some may be unstable. Few have configuration options. Others may have undesirable side effects such as recursively processing every file in every subdirectory. It’s best to test tools from a dedicated project directory.

Install Deno

Install Deno on macOS or Linux using the following terminal command:

curl -fsSL https://deno.land/x/install/install.sh | sh

Or from Windows Powershell:

iwr https://deno.land/x/install/install.ps1 -useb | iex

Further installation options are provided in the Deno manual.

Enter deno --version to check installation has been successful. The version numbers for the V8 JavaScript engine, TypeScript compiler, and Deno itself are displayed.

Upgrade Deno

Upgrade Deno to the latest version with:

deno upgrade

Or upgrade to specific release such as v1.3.0:

deno upgrade --version 1.30.0

Most of the tools below are available in all versions, but later editions may have more features and bug fixes.

Deno Help

A list of tools and options can be viewed by entering:

deno help

Read-Eval-Print Loop (REPL)

Like Node.js, a REPL expression evaluation console can be accessed by entering deno in your terminal. Each expression you enter returns a result or undefined:

$ deno

Deno 1.3.0
exit using ctrl+d or close()
> const w = 'World';
undefined
> w
World
> console.log(`Hello ${w}!`);
Hello World!
undefined
> close()

$

Previously entered expressions can be re-entered by using the cursor keys to navigate through the expression history.

Dependency Inspector

A tree of all module dependencies can be viewed by entering deno info <module> where <module> is the path/URL to an entry script.

Consider the following lib.js library code with exported hello and sum functions:

// general library: lib.js

/**
 * return "Hello <name>!" string
 * @module lib
 * @param {string} name
 * @returns {string} Hello <name>!
 */
export function hello(name = 'Anonymous') {

  return `Hello ${ name.trim() }!`;

};

/**
 * Returns total of all arguments
 * @module lib
 * @param {...*} args
 * @returns {*} total
 */
export function sum(...args) {

  return [...args].reduce((a, b) => a + b);

}

These can be used from a main entry script, index.js, in the same directory:

// main entry script: index.js

// import lib.js modules
import { hello, sum } from './lib.js';

const
  spr = sum('Site', 'Point', '.com', ' ', 'reader'),
  add = sum(1, 2, 3);

// output
console.log( hello(spr) );
console.log( 'total:', add );

The result of running deno run ./index.js:

$ deno run ./index.js

Hello SitePoint.com reader!
total: 6

The dependencies used by index.js can be examined with deno info ./index.js:

$ deno info ./index.js

local: /home/deno/testing/index.js
type: JavaScript
deps:
file:///home/deno/testing/index.js
  └── file:///home/deno/testing/lib.js

Similarly, the dependencies required by any module URL can be examined, although be aware the module will be downloaded and cached locally on first use. For example:

$ deno info https://deno.land/std/hash/mod.ts

Download https://deno.land/std/hash/mod.ts
Download https://deno.land/std@0.65.0/hash/mod.ts
Download https://deno.land/std@0.65.0/hash/_wasm/hash.ts
Download https://deno.land/std@0.65.0/hash/hasher.ts
Download https://deno.land/std@0.65.0/hash/_wasm/wasm.js
Download https://deno.land/std@0.65.0/encoding/hex.ts
Download https://deno.land/std@0.65.0/encoding/base64.ts
deps:
https://deno.land/std/hash/mod.ts
  └─┬ https://deno.land/std@0.65.0/hash/_wasm/hash.ts
    ├─┬ https://deno.land/std@0.65.0/hash/_wasm/wasm.js
    │ └── https://deno.land/std@0.65.0/encoding/base64.ts
    ├── https://deno.land/std@0.65.0/encoding/hex.ts
    └── https://deno.land/std@0.65.0/encoding/base64.ts

For further information, see the Deno Manual: Dependency Inspector.

Linter (Syntax Checker)

Deno provides a linter to validate JavaScript and TypeScript code. This is an unstable feature which requires the --unstable flag, but no files will be altered when it’s used.

Linting is useful to spot less obvious syntax errors and ensure code adheres with your team’s standards. You may already be using a linter such as ESLint in your editor or from the command line, but Deno provides another option in any environment where it’s installed.

To recursively lint all .js and .ts files in the current and child directories, enter deno lint --unstable:

$ deno lint --unstable

(no-extra-semi) Unnecessary semicolon.
};
 ^
    at /home/deno/testing/lib.js:13:1

Found 1 problem

Alternatively, you can specify one or more files to limit linting. For example:

$ deno lint --unstable ./index.js
$

For further information, see the Deno Manual: Linter. It includes a list of rules you can add to code comments to ignore or enforce specific syntaxes.

Test Runner

Deno has a built-in test runner for unit-testing JavaScript or TypeScript functions.

Tests are defined in any file named <something>test with a .js, .mjs, .ts, .jsx, or .tsx extension. It must make one or more calls to Deno.test and pass a test name string and a testing function. The function can be synchronous or asynchronous and use a variety of assertion utilities to evaluate results.

Create a new test subdirectory with a file named lib.test.js:

// test lib.js library

// assertions
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';

// lib.js modules
import { hello, sum } from '../lib.js';

// hello function
Deno.test('lib/hello tests', () => {

  assertEquals( hello('Someone'), 'Hello Someone!');
  assertEquals( hello(), 'Hello Anonymous!' );

});

// sum integers
Deno.test('lib/sum integer tests', () => {

  assertEquals( sum(1, 2, 3), 6 );
  assertEquals( sum(1, 2, 3, 4, 5, 6), 21 );

});

// sum strings
Deno.test('lib/sum string tests', () => {

  assertEquals( sum('a', 'b', 'c'), 'abc' );
  assertEquals( sum('A', 'b', 'C'), 'AbC' );

});

// sum mixed values
Deno.test('lib/sum mixed tests', () => {

  assertEquals( sum('a', 1, 2), 'a12' );
  assertEquals( sum(1, 2, 'a'), '3a' );
  assertEquals( sum('an', null, [], 'ed'), 'annulled' );

});

To run all tests from all directories, enter deno test. Or run tests stored in a specific directory with deno test <dir>. For example:

$ deno test ./test

running 4 tests
test lib/hello tests ... ok (4ms)
test lib/sum integer tests ... ok (2ms)
test lib/sum string tests ... ok (2ms)
test lib/sum mixed tests ... ok (2ms)

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (11ms)

$

A --filter string or regular expression can also be specified to limit tests by name. For example:

$ deno test --filter "hello" ./test

running 1 tests
test lib/hello tests ... ok (4ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 3 filtered out (5ms)

Longer-running tests can be stopped on the first failure by passing a --failfast option.

For further information, see the Deno Manual: Testing. A few third-party test modules are also available, including Merlin and Ruhm, but these still use Deno.test beneath the surface.

V8 Debugger

Deno provides the same V8 engine and debugger as Node.js. It’s possible to attach to the debugger using the Chrome browser or VS Code, then step through code to view variable and object changes.

To launch the debugger, run a script with --inspect or --inspect-brk to stop on the first line. If you need to attach to the debugger from another device on your network, add its IP address and a port or allow a connection from anywhere using --inspect=0.0.0.0:9229. For example:

$ deno run --inspect-brk=0.0.0.0:9229 ./index.js

Debugger listening on ws://0.0.0.0:9229/ws/ceb123...
Debugger session started.

Open in a new Chrome tab and the deno script will appear as a new Remote Target:

Chrome inspect

Note: the “dedicated DevTools for Node” will not connect to Deno’s debugger, even though they are similar.

Click the target’s inspect link to launch DevTools. This will be familiar if you’ve used client-side debugging. The Sources tab is the most useful and allows you to step through code execution:

Deno V8 Debugger DevTools

For further information, see the Deno Manual: Debugger.

Code Formatter

The built-in code formatter auto-formats JavaScript and TypeScript code in a similar way to Prettier. Deno’s formatter is also opinionated and it’s not currently possible to configure options.

To use it, enter deno fmt to recursively format every file in every subdirectory. For example:

$ deno fmt

/home/deno/testing/index.js
/home/deno/testing/test/lib.test.js
/home/deno/testing/lib.js

Alternatively, you can format one or more individual files — for example, deno fmt ./index.js.

If you examine the lib.test.js file, you’ll see the formatter has removed some spacing and converted strings to use double-quotes ("):

// hello function
Deno.test("lib/hello tests", () => {
  assertEquals(hello("Someone"), "Hello Someone!");
  assertEquals(hello(), "Hello Anonymous!");
});

Individual blocks of code can be ignored by adding a // deno-fmt-ignore comment. For example:

// deno-fmt-ignore
const bin = [
              1, 0, 0,
              0, 1, 0,
              0, 0, 1,
            ];

Whole files can be ignored by adding a // deno-fmt-ignore-file comment at the top of the code.

For further information, see the Deno Manual: Code Formatter.

Warning! Automated formatting can adversely affect JSDoc comments.

Documentation Generator

Deno can generate documentation from JSDoc comments in the source code, which explain a function’s purpose, parameters, and return value. Currently, Deno will only generate documentation for modules which export functions. For example:

$ deno doc ./lib.js

Defined in file:///home/deno/testing/lib.js:9:0

function hello(name)
  return "Hello <name>!" string
  @module lib
  @param {string} name
  @returns {string} Hello <name>!

Defined in file:///home/deno/testing/lib.js:21:0

function sum(...args)
  Returns total of all arguments
  @module lib
  @param {...*} args
  @returns {*} total

$

Adding a --json flag outputs the documentation in JSON format.

For further information, see the Deno Manual: Documentation Generator.

Script Bundling

Your main script and all its dependencies can be bundled into a single file using:

deno bundle <main-script> <output-script>

For example:

$ deno bundle ./index.js ./index.bundle.js

Bundle file:///home/deno/testing/index.js
Emit "./index.bundle.js" (3.13 KB)

The resulting script can then be executed:

$ deno run ./index.bundle.js

Hello SitePoint.com reader!
total: 6

This could be useful when distributing scripts to end users or deploying a final codebase to a live server.

Note: top-level await calls can fail when bundling, so an async wrapper function must be added. This is a known issue which will be fixed in future Deno releases.

For further information, see the Deno Manual: Bundling.

Script Installer

A Deno script can be globally installed so it can be run from any location in the file system. It’s similar to installing global Node.js modules, but somewhat simpler and easier to use.

The deno install command must be passed:

  1. Any required runtime permission flags such as --allow-read, --allow-write, or --allow-net.
  2. An optional installed-script name with --name <scriptname>.
  3. An optional installation root folder with --root <path>. If this isn’t set, Deno installs the script to the path set in the DENO_INSTALL_ROOT environment variable or $HOME/.deno/bin/.
  4. The module path or URL.

The example script above can be installed with:

$ deno install --name myscript ./index.js

✅ Successfully installed myscript
/home/deno/.deno/bin/myscript

A myscript file is created in the .deno/bin/ directory with the following content:

#!/bin/sh
# generated by deno install
deno "run" "file:///home/deno/testing/index.js" "$@"

myscript can now be run from anywhere on the system. For example:

cd ~
$ myscript

Hello SitePoint.com reader!
total: 6

This process makes it easy to tell users how install your application from a published URL. For example:

deno install --name myapp https://myserver.com/myapp.js
myapp

Deno doesn’t currently provide an uninstall or remove command. The only way to remove the script is to manually delete the generated file from the .deno/bin/ directory or wherever it was installed.

For further information, see the Deno Manual: Script Installer.

A Complete Deno Toolkit?

Deno’s tools are new and some are rudimentary, but a documented “standard” approach has benefits. Node.js offers numerous third-party options, but this can lead to choice paralysis or continual solution switching. Have you stuck with the same Node.js testing suite?

However, be wary: these built-in tools may evolve rapidly as Deno usage grows.

Deno Foundations

Get up to speed with Deno. Our Deno Foundations collection helps you take your first steps into the Deno world and beyond, and we’re adding to it constantly. We’ll bring you the tutorials you need to become a pro. You can always refer to our index as it’s updated at the end of our Introduction to Deno:

Deno Foundations