Get stuck in with Tim & Deno
Published: 27 April 2021
Updated: 13 October 2021
Its not the NodeJS killer, but it might go that way...
Deno? I hear you ask. Deno is a Node(JS) anagram/rival/alternative that is sharply increasing in popularity, particularly after it's 1.0.0 released in May 2020 and aims to be better than its predecessor and fix a lot of its well-known pitfalls.
Table of Contents
Assumptions
-
I'm assuming you're already familiar with Javascript, NPM/Node and have some basic experience using a terminal environment (however, these aren't essential and will be explained briefly as we go).
-
I'll also be assuming you're using a Mac and VSCode (as I am), but I'll try and keep instructions as broad as possible to support as many varied setups as possible.
-
Everything in this article is deemed fit for use with Deno v1.11.1. Given Deno is still relatively young, some parts of this article may no longer be correct and may require updating as and when needed.
What is Deno?
Like Node, Deno is a technology we can use to write "server-side Javascript". In many ways, it is extremely close to Node and I consider it to be a spiritual successor to Node. It shares the same creator and uses the V8 Javascript engine but differs in that it is implemented in Rust rather than C++ and supports both WebAssembly and Typescript by default (no need for a tsconfig.json
or any other setup process! However, you can include one for your specific needs).
Deno tries to be as browser-friendly by supporting as many of the current web standard APIs as possible, such as fetch
, addEventListener
, removeEventListener
, setInterval
, clearInterval
, dispatchEvent
and even has a window
object that allows us to listen for lifecycle events such as
window.onload = (e) => console.log("Howdy 🤠");
One of the goals of the project was to avoid inventing new APIs, which was a requirement in 2009 by Node for handling operations like require
ing modules before the ES6 import/export syntax existed which is why there was a focus on being browser-friendly. However, there are some APIs that it does need to invent for file i/o like Node's top-level FS
operations (most of which are available in the standard library). However, Deno differs again as Deno's FS implementation is achieved by a compatibility layer as opposed to writing C++ addons as Node requires)
A common use case I've seen for Deno is combining the pros of its server-side capabilities with ExpressJS for full-stack typescript applications, but the only hands-on experience I've had with it is for building little tools for process automation and document transformations (generating CSVs, automate big copy/paste jobs etc...) but essentially whatever you can do with Node, Deno should be able to do it just as well!
Deno's Standard Library & Third-Party Modules
Deno provides us with a set of standard modules (similar to how NodeJS has built-in modules such as fs
, path
etc) for common use cases such as serve
from the http module via
import { serve } from "https://deno.land/std@0.92.0/http/server.ts";
const server = serve({ hostname: "0.0.0.0", port: 8080 });
Note: I'm specifying std@0.92.0
to ensure the correct version is used when others use my project. You can safely omit the version and Deno will grab the latest version instead. In my case, omitting the version gave me the following Warning Implicitly using latest version for https://deno.land/std/http/server.ts
A full list can be found at https://deno.land/std, along with a more detailed explanation of the Standard library from https://deno.land/manual/standard_library.
If you're looking for third-party modules (such as we'll be using to build a react app later), you'll be able to search for them via https://deno.land/x in a similar way to using the NPM repository
No more NPM Packages/package.json = No more node_modules
Deno also aims to fix some of the main issues synonymous with its predecessor but can't be altered due to said faults being key to its architecture, hence the need for a clean slate. The elephant in the room being the NPM package dependencies, package.json and the hell they bring with them. Deno does away with all of this extra fat and opts for a much simpler way to import packages by allowing you to include them directly via a URL that a package is being served from. For example, to use an external library for testing we can simply use the ES6 module syntax:
import { assertEquals } from "https://deno.land/std@0.92.0/testing/asserts.ts"; // No need for `npm install`
assertEquals("hello", "hello");
assertEquals("world", "world");
console.log("Asserted! ✓");
The beautify of this is that we don't need to "install" our application, as the packages are only retrieved the first time you run the application which is then cached locally to save requesting them every time a file calls for it - akin to how <script src="https://deno.land/std@0.92.0/testing/asserts.js">
would work in an HTML document.
Okay, so how do you manage dependencies for big projects?
If your project has a lot of dependencies, it may not make sense to add them to every file they are required in via the absolute URLs. This can be overcome by having a single file (depts.ts
by convention) be the home of all the dependencies and export them as needed. For example, here is how the "depts" are managed in our example-react
folder:
// example-react/deps.ts
export * as ReactDOM from "https://jspm.dev/react-dom@17.0.0";
import * as React from "https://jspm.dev/react@17.0.0";
const { default: any, ...rest } = React;
const react = React.default;
export { react as React };
export { rest as react };
// example-react/index.tsx
import { React, react, ReactDOM } from "./deps.ts"; // importing react from the depts file
import App from "./App.tsx"; // regular import for our component
ReactDOM.render(
<react.StrictMode>
<App />
</react.StrictMode>,
document.getElementById("root"),
);
Note: with Deno support, you may not be able to use them. It can be hit and miss using NPM packages/repos in your project, but one of the things it seems to boil down to in my experience is whether the packages use Node-specific APIs (FS
et al), but some may work thanks to Deno's use of compatibility layers and Node polyfills (very experimental).
No more callback hell
All async actions are promised based and having a top-level await means we don't need any extra boilerplate code to resolve our promises!
Improved security measures
Deno is also secure by default. One example would be trying to log the current working directory of the filesystem by using console.log(Deno.cwd());
in your script, then executing deno run index.ts
, doing so will give you:
error: Uncaught PermissionDenied: read access to "/path/to/file", run again with the "--allow-read" flag
This means unless you, the developer, run the command and let the script access your filesystem, any requests to do so will result in errors and potentially prevent malicious code from being executed. You can specify more such as allowing write access to the disk, network access, plugin use etc. Here are some examples:
Flag | Description |
---|---|
allow-env |
allow environment access |
allow-hrtime |
allow high-resolution time measurement |
allow-net= |
allow network access |
allow-plugin |
allow loading plugins |
allow-read= |
allow file system read access |
allow-run |
allow running subprocesses |
allow-write= |
allow file system write access |
allow-all |
allow all permissions (same as -A, known as "Node mode") |
For a full list of permissions, you can control/specify, see Deno Permissions List
Install
Instructions vary based on OS and how you manage your software/packages. The Deno manual has instructions for most OS's and package managers: https://deno.land/manual/getting_started/installation
For example, on my Mac, it was as simple as running brew install deno
Setup
For setting Deno up for use via shell/cmd and for help setting up your IDE, see https://deno.land/manual/getting_started/setup_your_environment
For my setup in VSCode, I just needed to add the VSCode Deno extension and include the following in my .vscode/settings.json
:
{
"deno.enable": true,
}
Getting started
Check the install worked
To test your installation and setup are working as intended, let's run the example code by running
deno run welcome.js
and if you see the welcome message, you know everything worked okay 🤠
Example I/O scripts and what they do
Note: Make sure you've CD
'd into the example-io
folder for these
deno run --allow-read example-read.ts
This file demonstrates how we would use Deno to grab the contents of a text file
This also demonstrates how there is no extra setup for using TS and is supported by default
deno run --allow-read --allow-write example-write.js
This file demonstrates how we would use Deno to write the contents of a text file
Also, note the inclusion of exists imported from the FS in the Deno Standard Library
While Deno.write can overwrite the original file, we're doing this the long way round to demo how to import a Standard Library and use it
deno test
This file demonstrates how we would use Deno to write tests
Also, note the inclusion of imported assert functions from the Deno Standard Library
Example React app
Note: Make sure you've CD
'd into the example-react
folder to try these out
For demo purposes, we'll be using Deno's version of create-react-app, available to use over at https://deno.land/x/create_react_app@v0.1.2. Following the quick start guide from the module's README, we can be up and running as quickly as:
Note The first command uses the unstable flag, which is perfectly safe for our example, but you can find out more here: https://deno.land/manual/runtime/stability
# Grab the Third-Party Library for generating create-react-apps
deno install -A --unstable -n deno-create-react-app https://deno.land/x/create_react_app/mod.ts
# Initialise an instance of a new react app
deno-create-react-app init <name-of-app>
cd <name-of-app>
# Run the project in dev mode
# -w, --watch, watch file change to rebuild, default is true
# -p, --port, server port, default is 8000
deno-create-react-app run -w -p 3000
# Deno also has a built-in linter we can use on our app:
deno lint --unstable --ignore="dist"
# Build the project to dist/build, your app is ready to deploy
deno-create-react-app build
And that's it for the create-react-app 🥳
Example of bundling and compiling to a single executable file
Note: Make sure you've CD
'd into the example-compile
folder to try these out
Note: Up to now, all examples have been included in the repo but I haven't included the compiled curry-sums due to its large size. Wanna see it? Make your own!
Essentially, deno compile [--output <OUT>] <SRC>
creates a self-contained executable file based on your scripts.
In the example folder, you'll see our index.ts
requires the add.ts
and subtract.ts
, which in turn each require the curry.ts
. What compile lets us do is produce a single file with all of our required files combined inside of it - our four files become one!
How do we do that? In our case, running this should do it:
# Compile into a single executable
deno compile --unstable --output="compiled-curry-sums" index.ts
# Run the executable we just made
./compiled-curry-sums
Note Since the compile functionality is relatively new, the --unstable flag has to be set for the command to work.
And if we check inside the output
folder we'll find out curry-sums
file. To execute it, we can simply run ./output/curry-sums
, which should give us the same output as running just the index file (deno run index.ts
) - the difference being that our curry-sums
file is stand-alone and can be moved anywhere and will still work just fine!
This is great for portability and for protecting the source code when sharing with others, but it does come at a cost... In our case, our file comes to a less than modest 77.80MB (running the compile with the --lite command will use a slimmed-down runtime-only binary, but still creates a ~55MB file)! While NodeJS also has compiling options similar to this, they are producing even bigger outputs than that of Deno (usually 70+MB).
What else can Deno do?
Tim.Bryan@Potato example-deno % deno --help
deno 1.11.1
A secure JavaScript and TypeScript runtime
Docs: https://deno.land/manual
Modules: https://deno.land/std/ https://deno.land/x/
Bugs: https://github.com/denoland/deno/issues
To start the REPL:
deno
To execute a script:
deno run https://deno.land/std/examples/welcome.ts
To evaluate code in the shell:
deno eval "console.log(30933 + 404)"
USAGE:
deno [OPTIONS] [SUBCOMMAND]
OPTIONS:
-h, --help
Prints help information
-L, --log-level <log-level>
Set log level [possible values: debug, info]
-q, --quiet
Suppress diagnostic output
By default, subcommands print human-readable diagnostic messages to stderr.
If the flag is set, restrict these messages to errors.
--unstable
Enable unstable features and APIs
-V, --version
Prints version information
SUBCOMMANDS:
bundle Bundle module and dependencies into single file
cache Cache the dependencies
compile UNSTABLE: Compile the script into a self contained executable
completions Generate shell completions
coverage Print coverage reports
doc Show documentation for a module
eval Eval script
fmt Format source files
help Prints this message or the help of the given subcommand(s)
info Show info about cache or info related to source file
install Install script as an executable
lint Lint source files
lsp Start the language server
repl Read Eval Print Loop
run Run a JavaScript or TypeScript program
test Run tests
types Print runtime TypeScript declarations
upgrade Upgrade deno executable to given version
ENVIRONMENT VARIABLES:
DENO_AUTH_TOKENS A semi-colon separated list of bearer tokens and
hostnames to use when fetching remote modules from
private repositories
(e.g. "abcde12345@deno.land;54321edcba@github.com")
DENO_CERT Load certificate authority from PEM encoded file
DENO_DIR Set the cache directory
DENO_INSTALL_ROOT Set deno installs output directory
(defaults to $HOME/.deno/bin)
DENO_WEBGPU_TRACE Directory to use for wgpu traces
HTTP_PROXY Proxy address for HTTP requests
(module downloads, fetch)
HTTPS_PROXY Proxy address for HTTPS requests
(module downloads, fetch)
NO_COLOR Set to disable color
NO_PROXY Comma-separated list of hosts which do not use a proxy
(module downloads, fetch)