Under the hood, we find a snapshotted version of the Typescript compiler, the V8 engine, and the Tokio event loop. Altogether, shipped as one binary of less than ten MB or as a Rust crate.
A considerable effort is made today to bring them up to speed and keep consistent versioning at the same time. Many of the API calls must still be wrapped in constructors like
promisify to be used with the
Promise syntax. This extra step adds overhead to development and increases boilerplate in applications.
In contrast, Promises are Deno's native bindings for async behavior. The Rust backend mirrors the promise objects received from the Typescript frontend with Rust Futures. Async actions in Deno always return a
Another noticeable thing about Node is that it relies on
Buffer objects to read and write data. In a step to bring uniformity with browser interfaces, Deno uses
TypedArrays everywhere. Being consistent when reading and writing files across the backend and front end is much easier when using the same data structures.
If you use Typescript, you know it is a remarkable tool. It introduces a type system that can be enforced as applications grow. This reduces the overhead of conventional static typing by providing flexibility. A project can be partially typed in the begging, and type coverage can be extended as the application grows.
In Node, Typescript can be used directly with
ts-node, although one must be careful in production. The safest and most performant choice is to use
The current resolution scheme of Node overcomplicates module resolution. The algorithm provides flexibility in file location and naming with a considerable tradeoff in complexity.
require call would first search for a file with the same name and a
.node extension. If the path specified does not include a leading
'../' node assumes the module is a core module or a dependency in the
node_modules folder. If the name does not match, a core module node will check the node_modules at that location. If nothing is found, it will get to the parent directory and continue to do so until it reaches the root of the file system.
Additionally, folders can be specified as modules in the
package.json file. The
require function is also aware of the
package.json file of all the folder begins checked. Once a folder is found, Node will look for an
index.node file inside it. The freedom of not having to provide a file extension and the flexibility of
package.json comes at a considerable increase in complexity and decrease in performance.
Deno simplifies the algorithm by providing two types of module resolution, relative and URL based:
import * from "https://deno.land/std/testing/asserts.ts";
In addition, the resolution algorithm does not use
package.json file or the
node_modules folder. Instead of
Server-less adoption is at this moment doubling every year. Developers use to split monoliths into microservices. Now we are splitting micro-services into functions. Why? Well, on the one hand, nobody wants to deal with orchestration unless we have too. On the other hand, distributed systems are more flexible and can be changed faster. The bottom line is, applications are becoming systems of smaller and separated parts.
node_modules folder. And many are hardly used at runtime. At the same time, the whole ecosystem depends on a centralized package manager:
Deno brings a distributed approach to package management. Packages can be resolved by URL and catched afterward. Applications are lighter and less dependent on a single and centralized package registry.
When doing backend development, I expect security to work outside the box. The last thing I want to think about is a linter file or node module accessing the network or the file system.
In Deno, internal functions cannot call V8 API's arbitrarily as they do in Node. The communication between Deno's APIs and the JS engine is centralized and unified with messaging based on typed arrays.
Unless specifically allowed, scripts can't access files, the environment, or the network. — deno.land
Scripts executed with Deno can access the file system and network only if the user explicitly specifies it. And even better, permission can be given at file, folder level, or network path level with the —allow flag. This offers developers granular control of read and write actions that happen at runtime.
$ deno --allow-net https://deno.land/std/examples/echo_server.ts
Security by default is a significant upgrade compared to the "trust" policy applied to dependencies one pulls from
npn. With Deno you can run and develop applications with the confidence that they will do what they are expected to.
Deno is how Node would look like if it would be built today. It improves security, simplifies module resolution, and runs Typescript.
I am looking forward to seeing Deno grow beyond merely a scripting runtime and to hearing about the first experiences in production. As long as developers continue disrupting ourselves, we can always expect faster, more straightforward, and more reliable software.
Originally published on bogdanned.com.