Node.js compatibility in Deno

Mayank Choubey
Tech Tonic
4 min readNov 20, 2021

--

Introduction

Since a long time, Deno’s standard library had a module called node that mimicked some Node.js’s core modules (like http, fs, etc.) in Deno. The purpose of compatibility is to provide an ability to run Node.js programs in Deno. The primary advantage is running a Node.js app as is, i.e. without rewriting it. Only a subset of the Node core APIs were supported, while a good number of them were still in To-Do. This ES module stayed in standard library for a long time, with occasional enhancements here and there.

Since v1.15, Deno is getting more serious about Node.js compatibility. Recently, there have been numerous enhancements to the standard library’s node module. A lot of work has been put in to reach feature parity with Node.js.

The biggest enhancement is to bring in the std’s node module in the core runtime through a flag ( — compat). This way, there is no need to include/import anything. All the Node core modules are loaded at startup. In other words, all the Node’s core modules are available as a standard import, rather than importing from the standard library.

In this introductory article, we’ll evaluate Deno’s current level of Node.js compatibility through some simple examples.

Startup

There is a new flag called — compat that can be used to load the Node modules into Deno’s core runtime. This happens at startup. The loading of core modules happens from the same standard library’s node module.

For Deno v1.16.2, the core modules are loaded from https://deno.land/std@0.115.0/.

It is expected that the std version would get updated for every Deno release.

Here is the list of core modules that are loaded in the compatibility mode:

assert, assert/strict, async_hooks, buffer, child_process, cluster, console, constants, crypto, dgram, dns, domain, events, fs, fs/promises, http, https, module, net, os, path, path/posix, path/win32, perf_hooks, process, querystring, readline, stream, stream/promises, stream/web, string_decoder, sys, timers, timers/promises, tls, tty, url, util, util/types, v8, vm, zlib

The compatibility mode is currently under unstable umbrella, therefore needs — unstable flag to load Node’s core modules.

$ deno run --compat --unstable nodeApp.js

The compatibility mode allows CJS (common JS) modules to get loaded through require() API. Unlike standard imports that don’t require a read permission, CJS imports (i.e. require) requires explicit read permission.

$ deno run --compat --unstable --allow-read nodeApp.js

That’s all about the background! Once compat mode is specified, all the core modules get loaded in the background. This helps in running a Node.js program as is.

Let’s go through some simple examples and evaluate how much of Node.js compatibility is there.

Examples

Example 1

Check if command line args can be referred through process.argv.

//nodeApp.js
console.log(process.argv);
//--$ deno run --compat --unstable --allow-read nodeApp.js
[ "/usr/local/bin/deno", "/Users/mayankc/Work/source/denoExamples/nodeApp.js" ]

Verdict: Compatible

Example 2

Check if environment variables can be accessed with process.env.

//nodeApp.js
console.log(process.env);
const l=process.env.LANG;
//--
{
SSH_AUTH_SOCK: "/private/tmp/com.apple.launchd.r6GGfQ0uSK/Listeners",
OLDPWD: "/Users/mayankc/Work/source",
__CFBundleIdentifier: "com.apple.Terminal",
TERM: "xterm-256color",
....
LANG: "en_US.UTF-8",
NVM_IOJS_ORG_MIRROR: "https://iojs.org/dist"
}
l: en_US.UTF-8

Verdict: compatible

Example 3

Check if files can be written using core fs module with callbacks.

//nodeApp.js
const fs = require('fs');
const data ='Trying FS module in compat mode';

fs.writeFile('/var/tmp/fsCompatTest.txt', data, (err) => {
if (err) throw err;
console.log('File is created successfully.');
});
//--$ deno run --compat --unstable --allow-read --allow-write nodeApp.js
File is created successfully.
$ cat /var/tmp/fsCompatTest.txt
Trying FS module in compat mode

Verdict: Compatible

Example 4

Check if data can be read from file using core fs module with callbacks.

//nodeApp.js 
const fs = require('fs');

fs.readFile('/var/tmp/fsCompatTest.txt', (err, data) => {
if (err) throw err;
console.log('Read from file:', data.toString('utf-8'));
});
//--$ deno run --compat --unstable --allow-read --allow-write nodeApp.js
Read from file: Trying FS module in compat mode

Verdict: compatible

Example 5

Check if a standard ‘hello world’ kind of HTTP server can be used with Node.js APIs like createServer() and listen().

//nodeApp.js
const http = require('http');
const respData='HTTP server in Node.js compat mode';
http.createServer((req, res) => {
res.writeHead(200, {
'content-type': 'text/plain',
'content-length': `${respData.length}`
});
res.end(respData);
}).listen(8080, () => console.log('Listening...'));
//--$ curl http://localhost:8080 -v
< HTTP/1.1 200 OK
< content-length: 34
< content-type: text/plain
HTTP server in Node.js compat mode

Verdict: compatible

Example 5

Check if buffers can be created and converted to different formats.

//nodeApp.js
const {Buffer} = require('buffer');
const buf = Buffer.from('hello world', 'utf8');
const h=buf.toString('hex');
const b=buf.toString('base64');
const u=new Uint8Array(buf);
//--
h: 68656c6c6f20776f726c64
b: aGVsbG8gd29ybGQ=
u: Uint8Array(11) [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]

Verdict: compatible

Overall impression: It works for simple programs!

It’s important to note that the feature parity is still getting built. Modules like https, vm, etc. are not yet available in compatibility mode.

We’ll go through more examples in a subsequent article.

--

--