Deno 2.0 boosts your FrontEnd Development!

20.11.2024Ricky Elfner
News Deno Developer Experience Node.js API Development Integration javascript

Banner

It’s the beginning of October 2024 and time for a new version of Deno. We already looked at Deno in a Techup with Deno some time ago. But so far, Deno hasn’t really made the big breakthrough. However, this should change with the new version. That’s why we’re taking a look at what’s changed today.

If you look at the announcement of Deno 2, you immediately realize that its goal is to simplify web development. And that, although everything is becoming more and more complex and therefore more complicated. Especially we developers notice this more and more often, because before you can even start with the actual work, complex configurations have to be made. This is exactly where Deno comes in: It offers a modern, all-in-one, zero-config toolchain for JavaScript and TypeScript development.

With native TypeScript support, built-in web standards like Promises, fetch and ES Modules, and a comprehensive toolbox (including formatter, linter, type checker, and test framework), Deno 2 focuses on making the tool even more scalable and seamlessly integrating it into the existing JavaScript infrastructure - without sacrificing the simplicity and security that Deno users appreciate.

Review

But now again as a small reminder, what was the topic in the last Techup, because that was two years ago.

Deno is a runtime for JavaScript and TypeScript. It was announced in 2018 by Ryan Dahl, the inventor of Node.js, and released in version 1.0 in 2020. His goal was to fix design flaws in Node.js with Deno.

One of the biggest problems was the use of Async/Await, as Promises were not introduced until much later. Security also played no role in the early days of Node.js. The centralized, privately controlled npm repository led to potentially insecure dependencies, and the node_modules folder had to be recreated for each project, resulting in large amounts of data. The outdated CommonJS module system was confusing compared to ES Modules and could no longer be removed due to its widespread use.

Deno solved these problems through various improvements by including built-in tools such as package manager, compiler, formatter and linter (“Batteries Included”). Security was implemented according to the motto “Secure by Default” by only granting permissions to explicitly allowed accesses. Deno supports TypeScript natively without additional configuration and uses tested standard modules without external dependencies, which increases security. It relies on ES Modules and uses a decentralized module system where modules are imported via URLs. Top-Level Await simplifies asynchronous programming because await no longer needs to be wrapped in an async function. In addition, web APIs such as fetch can be used directly without additional packages.

The general architecture has also been modernized by writing Deno in Rust and JavaScript. Tokio is used here instead of libuv.

Deno supports TypeScript natively without additional configuration and uses tested standard modules without external dependencies, which increases security. It relies on ES Modules and uses a decentralized module system where modules are imported via URLs. Top-Level Await simplifies asynchronous programming because await no longer needs to be wrapped in an async function. In addition, web APIs such as fetch can be used directly without additional packages.

Improvement of existing features

Before we look at the exciting new topics, here is a short list of improvements from the existing Deno functionality

  • deno fmt can now also format HTML, CSS and YAML
  • deno lint now includes Node-specific rules and quick fixes
  • deno test now supports running tests written with node:test
  • deno task can now run package.json scripts
  • The HTML output of deno doc has an improved design and a better search function
  • deno compile now supports code signing and icons on Windows
  • deno serve can run HTTP servers in parallel across multiple cores
  • deno init can now pre-configure libraries or servers
  • deno jupyter now supports the output of images, graphics and HTML
  • deno bench supports critical sections for more accurate measurements
  • deno coverage can now output reports in HTML format

New Features

Backwards-compatible

One of the new features is full backwards compatibility with Node and npm. Previously, it was basically already possible to use Deno as an alternative to Node.js, but in everyday life it was very difficult to fully integrate Deno into existing projects. Many tools that are standard in the Node world - such as Prettier for formatting code or npm scripts for automating tasks - could not be used seamlessly with Deno.

That has fundamentally changed with Deno 2. The new version makes it possible to seamlessly integrate Deno into existing Node projects and gradually leverage its modern, all-in-one toolchain without having to abandon the familiar infrastructure. Deno now understands the structures of a Node project, such as the package.json, npm workspaces, and node_modules, and allows you to use familiar tools such as deno fmt or deno install directly. This allows dependencies to be installed or code formatted at lightning speed without relying on external tools like Prettier.

This also includes the direct use of npm. The good thing is that you no longer need the usual package.json file and the node_modules folder, which saves you a lot of overhead. Thanks to the npm: specifier, this can now be used directly in the code or in a deno.json file.

A simpler example using direct import:

1
2
3
import chalk from "npm:chalk@5.3.0";

console.log(chalk.blue("Hello, world!"));

example01

The special thing: Deno installs the package in the global cache, so neither a separate configuration file nor the well-known node_modules folder is required. This way you can write programs with npm dependencies in a single file.

Or you should use a deno.json file:

1
2
3
4
5
6
// deno.json
{
  "imports": {
    "chalk": "npm:chalk@5.3.0"
  }
}

And can then be used directly in the class as usual:

1
2
3
import chalk from "chalk";

console.log(chalk.blue("Hello, world!"));

Deno 2 provides access to over 2 million npm modules, including more complex packages like gRPC, Prisma, ssh2, or duckdb. Even advanced features like native Node API addons are supported, making Deno an extremely flexible choice for modern JavaScript and TypeScript projects. This means that modern JavaScript frameworks such as Next.js, Astro, Remix, Angular or SvelteKit can now also be used without any problems.

Here is an example of how to create a nextJS app directly using deno:

1
deno run -A npm:create-next-app@latest .

This then results in the following familiar structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.
├── README.md
├── app
│   ├── favicon.ico
│   ├── fonts
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── next-env.d.ts
├── next.config.mjs
├── node_modules
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── tailwind.config.ts
└── tsconfig.json

Package Manager

In addition, the range of functions has been expanded, as Deno can now also be used as a powerful package manager. There are mainly three standard commands.

First there is deno install, which allows you to quickly install and manage dependencies. It doesn’t matter whether a project uses a package.json or not - Deno adapts flexibly. If a package.json exists, a node_modules folder will be created during the execution of deno install - just like in a classic JavaScript project. In addition, the speed of Deno has been significantly improved compared to other package managers. With a so-called “cold cache” - i.e. when dependencies have to be downloaded for the first time - Deno is 15% faster than npm. With a “warm cache”, when already installed packages are used, Deno is even 90% faster. It has also been announced that there will be further improvements in this area.

With deno add, new packages can easily be added to a package.json or deno.json. The current version of the package is automatically installed and the dependency is noted in the corresponding configuration file. This makes it easier to manage dependencies in larger projects and ensures that all required modules are cleanly documented.

Similarly, deno remove allows you to remove packages from the package.json or deno.json. The package is not only deleted from the configuration file, but also from the node_modules folder or the global cache, freeing the project from unnecessary dependencies.

JSR

Before we move on to the next new feature, let’s take a quick look at what JSR is all about. JSR was also introduced by Deno in March 2024. It is a new package repository for JavaScript and TypeScript. The goal is, of course, to eliminate the weaknesses of npm.

Why this is even an issue is due to the constant development in the field of JavaScript.

A lot has changed in the JavaScript world since 2009: The standard for modularization has shifted from CommonJS to ES modules (ESM), which enables more modern and efficient handling of modules. In addition, TypeScript has gained enormous popularity and offers developers the opportunity to write JavaScript with static typing, which improves code quality and maintainability. In addition to Node.js, new runtimes such as Deno, Bun and Cloudflare Workers have emerged that support innovative features and standards and further enrich the diversity of JavaScript development.

The most important points:

  • Optimized for TypeScript: Developers can publish TypeScript code directly without prior transpilation.

  • Supports ES Modules Only: Encourages the use of the current JavaScript module standard.

  • Compatible with different runtimes: Works with Deno, Node.js, Bun, Cloudflare Workers and others.

  • Free and Open Source: JSR is available under the MIT license and invites community participation.

Digression — ESM vs CJS

Here is a small excursus to show again the advantages of using ESModules.

Syntax and readability

The ESM syntax is clearer and easier to read because import and export are native, as opposed to the function syntax of require() and module.exports.

ESModules (ESM):

1
2
3
4
5
6
// module.mjs
export const greet = (name) => `Hello, ${name}!`;

// main.mjs
import { greet } from './module.mjs';
console.log(greet("Alice"));

CommonJS (CJS):

1
2
3
4
5
6
7
// module.js
const greet = (name) => `Hello, ${name}!`;
module.exports = greet;

// main.js
const greet = require('./module.js');
console.log(greet("Alice"));

Static analysis

With ESM, tools (like Webpack) can load only the functions they need (here add) and omit unused code (e.g. subtract), while with CommonJS the entire module is loaded.

ESModules (ESM):

1
2
3
4
5
6
7
// math.mjs
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// main.mjs
import { add } from './math.mjs'; // Only "add" is imported
console.log(add(5, 3));

CommonJS (CJS):

1
2
3
4
5
6
7
8
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
module.exports = { add, subtract };

// main.js
const { add } = require('./math.js'); // Here the whole module is loaded
console.log(add(5, 3));

Asynchronous loading

ESM is supported directly in the browser and can be loaded asynchronously, which is not possible with CommonJS.

ESModules (ESM):

1
2
3
4
5
<!-- index.html -->
<script type="module">
  import { greet } from './module.mjs';
  console.log(greet("Alice"));
</script>

CommonJS (CJS):

1
2
3
// There is no browser example here because CommonJS is loaded synchronously and usually needs to be transpiled.
const greet = require('./module.js');
console.log(greet("Alice"));

Module areas (strict mode)

In ESM there is no this binding at the global level and it automatically runs in strict mode, which reduces error-proneness.

ESModules (ESM):

1
2
3
4
// module.mjs (strict mode is active by default)
export const greet = function () {
  console.log(this); // undefined, since there is no `this` in the module area
};

CommonJS (CJS):

1
2
3
4
5
// module.js (not automatically strict mode)
const greet = function () {
  console.log(this); // Points to `global` or `module.exports`
};
module.exports = greet;

Exports as real objects

With ESM, exports are real, shared bindings. Changes in the module are reflected immediately. With CJS, on the other hand, only copies of the values are exported, so changes are not visible.

ESModules (ESM):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// module.mjs
export let counter = 0;
export function increment() {
  counter++;
}

// main.mjs
import { counter, increment } from './module.mjs';
console.log(counter); // 0
increment();
console.log(counter); // 1, change is visible

CommonJS (CJS):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// module.js
let counter = 0;
function increment() {
  counter++;
}
module.exports = { counter, increment };

// main.js
const { counter, increment } = require('./module.js');
console.log(counter); // 0
increment();
console.log(counter); // 0, change is not visible

Standard Library — “std”

A lot has also changed in the Deno standard library since the first release 4 years ago. The selected modules are thoroughly tested modules that provide a large number of functions, such as data manipulation, web-related logic and JS-specific functions. This library is also available from JSR and can therefore be used in other projects.

Examples of modules in the Deno standard library and their npm counterparts:

Deno Standard Library Module Corresponding npm package
@std/testing jest
@std/expect chai
@std/cli minimist
@std/collections lodash
@std/fmt chalk
@std/net get-port
@std/encoding rfc4648

For a complete list of available packages visit: https://jsr.io/@std.

Private npm registries

This now works the same way as with Node and npm. All you need is a .npmrc file.

Workspaces and monorepos

By using workspaces for individual folders/packages, it is possible to structure the project as a monorepo. To do this, a deno.json or package.json file must be created at the top level, which contains the individual workspaces. Each of these workspaces can then have its own deno.json file.

Initial state after checkout from the example repo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.
├── README.md
├── package-lock.json
├── package.json
└── packages
    ├── add
    │   ├── index.test.ts
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    ├── cli
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    └── subtract
        ├── index.ts
        ├── package.json
        └── tsconfig.json

4 directories, 13 files

The definition of the individual workspaces then looks like this at the top level:

1
2
3
4
5
6
7
{
  "workspaces": [
    "packages/add",
    "packages/subtract",
    "packages/cli"
  ]
}

If you now run a deno install to install all dependencies, a deno.lock and a node_modules folder will be created at the top level.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.
├── README.md
├── deno.lock
├── node_modules
│   ├── @dsherret
│   │   ├── add -> ../../packages/add
│   │   └── subtract -> ../../packages/subtract
│   ├── @types
│   │   └── node -> ../.deno/@types+node@20.16.11/node_modules/@types/node
│   ├── chalk -> .deno/chalk@5.3.0/node_modules/chalk
│   └── typescript -> .deno/typescript@5.6.3/node_modules/typescript
├── package-lock.json
├── package.json
└── packages
    ├── add
    │   ├── index.test.ts
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    ├── cli
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    └── subtract
        ├── index.ts
        ├── package.json
        └── tsconfig.json

12 directories, 14 files

LTS

Since Deno has previously released weekly bug fix releases and minor releases every six weeks, it is difficult, especially for larger enterprise companies, to keep up with this rhythm. For this reason, Deno plans to provide an LTS version starting with version 2.1. This LTS channel will receive critical bug fixes for six months.

Deno for Enterprise

For teams that need advanced support, Deno has introduced the Deno for Enterprise program. This offers:

  • Prioritized support
  • Direct access to Deno engineers
  • Guaranteed response times
  • Priority for your feature requests

Conclusion

I have to say, after looking at the new version and its features on my own, it looks like it’s greatly increasing the use case for Deno. Especially the integration into existing projects that were previously based on Node.js and npm. By being able to continue using package.json and also deno.json, a smooth transition to deno can be achieved.

But I will definitely try to incorporate it into existing projects in the near future to see if it really works as planned. The hurdles that are otherwise common when introducing new technologies are significantly lower thanks to this seamless integration. Of course, it depends on the respective project team whether Deno is completely replaced or introduced step by step.

I find the introduction of LTS versions to be excellent, especially for enterprise customers. In large companies, you can’t always switch to the latest versions immediately, so LTS support provides the necessary stability and security. And in this way it also makes it possible to actually use Deno in more projects.

Overall, I am very optimistic about the future of Deno. The improvements in Deno 2 make it an even more attractive option for modern JavaScript and TypeScript projects. I look forward to taking full advantage of the new version and am excited to see how Deno evolves.

This techup has been translated automatically by Gemini

Ricky Elfner

Ricky Elfner – Denker, Überlebenskünstler, Gadget-Sammler. Dabei ist er immer auf der Suche nach neuen Innovationen, sowie Tech News, um immer über aktuelle Themen schreiben zu können.