Deno 2.0 boostet deine FrontEnd-Entwicklung!

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

Banner

Es ist Anfang Oktober 2024 und Zeit für eine neue Version von Deno. Wir haben uns bereits vor einiger Zeit in einem Techup mit Deno befasst. Doch bisher hat Deno nicht wirklich den grossen Durchbruch geschafft. Dies soll sich jedoch mit der neuen Version ändern. Aus diesem Grund schauen wir uns heute an, was sich alles geändert hat.

Schaut man sich die Ankündigung von Deno 2 an, erkennt man sofort, dass es das Ziel hat, Webentwicklung zu vereinfachen. Und das, obwohl alles immer komplexer und dadurch komplizierter wird. Besonders wir Entwickler merken dies immer häufiger, denn bevor man überhaupt mit dem eigentlichen Arbeiten beginnen kann, müssen aufwändige Konfigurationen vorgenommen werden. Genau hier setzt Deno an: Es bietet eine moderne, all-in-one, zero-config Toolchain für die JavaScript- und TypeScript-Entwicklung.

Mit nativem TypeScript-Support, eingebauten Webstandards wie Promises, fetch und ES Modules sowie einem umfassenden Werkzeugkasten (inklusive Formatter, Linter, Type Checker und Test-Framework) setzt Deno 2 den Fokus darauf, das Tool noch besser skalierbar zu machen und es nahtlos in die bestehende JavaScript-Infrastruktur zu integrieren – und das, ohne die Einfachheit und Sicherheit zu opfern, die Deno-Nutzer schätzen.

Rückblick

Doch nun noch einmal als kleiner Reminder, was im letzten Techup Thema war, denn dies liegt nun auch schon wieder zwei Jahre zurück.

Bei Deno handelt es sich um eine Runtime für JavaScript und TypeScript. Sie wurde 2018 von Ryan Dahl, dem Erfinder von Node.js, angekündigt und im Jahr 2020 in Version 1.0 veröffentlicht. Sein Ziel war es, mit Deno Designfehler von Node.js zu beheben.

Eines der grössten Probleme war die Nutzung von Async/Await, da Promises erst viel später eingeführt wurden. Auch Sicherheit spielte zur Anfangszeit von Node.js keine Rolle. Das zentralisierte, privat kontrollierte npm-Repository führte zu potenziell unsicheren Abhängigkeiten, und der node_modules-Ordner musste für jedes Projekt neu erstellt werden, was zu grossen Datenmengen führte. Das veraltete CommonJS-Modulsystem war im Vergleich zu ES Modules unübersichtlich und aufgrund seiner Verbreitung nicht mehr entfernbar.

Deno löste diese Probleme durch verschiedene Verbesserungen, indem es integrierte Tools wie Package Manager, Compiler, Formatter und Linter („Batteries Included“) mitlieferte. Sicherheit war nach dem Motto “Secure by Default” implementiert, indem nur explizit erlaubte Zugriffe Berechtigungen erhalten. Deno unterstützt TypeScript nativ ohne zusätzliche Konfiguration und verwendet geprüfte Standardmodule ohne externe Abhängigkeiten, was die Sicherheit erhöht. Es setzt auf ES Modules und verwendet ein dezentrales Modulsystem, bei dem Module über URLs importiert werden. Mit Top-Level Await wird asynchrones Programmieren vereinfacht, da await nicht mehr in eine async-Funktion eingebettet werden muss. Zudem können Web-APIs wie fetch direkt ohne zusätzliche Pakete genutzt werden.

Auch die allgemeine Architektur wurde modernisiert, indem Deno in Rust und JavaScript geschrieben wurde. Statt libuv wird hier Tokio verwendet.

Deno unterstützt TypeScript nativ ohne zusätzliche Konfiguration und verwendet geprüfte Standardmodule ohne externe Abhängigkeiten, was die Sicherheit erhöht. Es setzt auf ES Modules und verwendet ein dezentrales Modulsystem, bei dem Module über URLs importiert werden. Mit Top-Level Await wird asynchrones Programmieren vereinfacht, da await nicht mehr in eine async-Funktion eingebettet werden muss. Zudem können Web-APIs wie fetch direkt ohne zusätzliche Pakete genutzt werden.

Verbesserung bestehender Features

Vor wir uns die neuen Spannenden Themen anschauen hier eine kurze Liste, mit Verbesserungen aus dem bestehenden Funktionsumfang von Deno

  • deno fmt kann nun auch HTML, CSS und YAML formatieren
  • deno lint enthält jetzt Node-spezifische Regeln und Schnellkorrekturen
  • deno test unterstützt nun das Ausführen von Tests, die mit node:test geschrieben wurden
  • deno task kann jetzt package.json-Skripte ausführen
  • Die HTML-Ausgabe von deno doc hat ein verbessertes Design und eine bessere Suchfunktion
  • deno compile unterstützt jetzt Code-Signierung und Icons unter Windows
  • deno serve kann HTTP-Server parallel über mehrere Kerne hinweg ausführen
  • deno init kann nun Bibliotheken oder Server vorab konfigurieren
  • deno jupyter unterstützt jetzt die Ausgabe von Bildern, Grafiken und HTML
  • deno bench unterstützt kritische Abschnitte für präzisere Messungen
  • deno coverage kann Berichte jetzt im HTML-Format ausgeben

Neue Featues

Backwards-compatible

Eines der neuen Features ist die vollständige Rückwärtskompatibilität mit Node und npm. Zuvor war es grundsätzlich bereits möglich, Deno als Alternative zu Node.js zu nutzen, doch im Alltag war es sehr schwierig, Deno vollständig in bestehende Projekte zu integrieren. Viele Tools, die in der Node-Welt Standard sind – wie zum Beispiel Prettier zum Formatieren von Code oder npm-Skripte zur Automatisierung von Aufgaben – liessen sich nicht nahtlos mit Deno verwenden.

Mit Deno 2 hat sich das grundlegend geändert. Die neue Version ermöglicht es, Deno problemlos in bestehende Node-Projekte zu integrieren und schrittweise dessen moderne, All-in-One-Toolchain zu nutzen, ohne die gewohnte Infrastruktur aufgeben zu müssen. Deno versteht jetzt die Strukturen eines Node-Projekts, wie die package.json, npm Workspaces und node_modules, und erlaubt es, bekannte Tools wie deno fmt oder deno install direkt anzuwenden. Dadurch können Abhängigkeiten blitzschnell installiert oder Code formatiert werden, ohne auf externe Tools wie Prettier angewiesen zu sein.

Dazu gehört auch die direkte Verwendung von npm. Das Gute daran ist, dass man die übliche package.json-Datei und den node_modules-Ordner nicht mehr benötigt, wodurch man sich eine Menge Overhead sparen kann. Dank des npm:-Specifiers kann dies nun direkt im Code oder in einer deno.json-Datei verwendet werden.

Ein einfachers Beispiel mittels direktem import:

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

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

example01

Das Besondere: Deno installiert das Paket im globalen Cache, sodass weder eine separate Konfigurationsdatei noch der bekannte node_modules-Ordner nötig sind. Auf diese Weise kann man Programme mit npm-Abhängigkeiten in einer einzigen Datei schreiben.

Oder sollte man ein deno.json File verwenden:

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

Und kann dann in der Klasse direkt wie überlich verwendet werden:

1
2
3
import chalk from "chalk";

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

Deno 2 ermöglicht den Zugriff auf über 2 Millionen npm-Module, darunter auch komplexere Pakete wie gRPC, Prisma, ssh2 oder duckdb. Selbst fortgeschrittene Funktionen wie native Node-API-Addons werden unterstützt, was Deno zu einer äusserst flexiblen Wahl für moderne JavaScript- und TypeScript-Projekte macht. Dadurch können nun auch moderne JavaScript-Frameworks wie Next.js, Astro, Remix, Angular oder SvelteKit problemlos verwendet werden.

Hier ist ein Beispiel um eine nextJS-App direkt mittels deno zu erstellen:

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

Daraus ergibt sich dann die folgende gewohnte Struktur:

 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

Packetmanager

Zudem hat sich der Funktionsumfang erweitert, da Deno nun auch als leistungsstarker Paketmanager verwendet werden kann. Dabei gibt es vor allem drei Standardbefehle.

Zuerst gibt es deno install, womit Abhängigkeiten schnell installiert und verwaltet werden können. Dabei spielt es keine Rolle, ob ein Projekt eine package.json verwendet oder nicht – Deno passt sich flexibel an. Sollte dennoch eine package.json vorhanden sein, wird während der Ausführung von deno install ein node_modules-Ordner angelegt – wie in einem klassischen JavaScript-Projekt. Zudem ist die Geschwindigkeit von Deno im Vergleich zu anderen Paketmanagern erheblich verbessert. Bei einem sogenannten “kalten Cache” – also wenn Abhängigkeiten zum ersten Mal heruntergeladen werden müssen – ist Deno 15 % schneller als npm. Bei einem “warmen Cache”, wenn bereits installierte Pakete verwendet werden, ist Deno sogar 90 % schneller. Es wurde zudem angekündigt, dass es in diesem Bereich weiterhin Verbesserungen geben soll.

Mit deno add können neue Pakete einfach zu einer package.json oder deno.json hinzugefügt werden. Dabei wird automatisch die aktuelle Version des Pakets installiert und die Abhängigkeit in der entsprechenden Konfigurationsdatei vermerkt. Dies erleichtert das Management der Abhängigkeiten in grösseren Projekten und sorgt dafür, dass alle benötigten Module sauber dokumentiert sind.

Auf ähnliche Weise ermöglicht deno remove das Entfernen von Paketen aus der package.json oder deno.json. Das Paket wird nicht nur aus der Konfigurationsdatei gelöscht, sondern auch aus dem node_modules-Ordner oder dem globalen Cache entfernt, sodass das Projekt von unnötigen Abhängigkeiten befreit wird.

JSR

Bevor wir uns dem nächsten neuen Feature widmen, schauen wir uns kurz an, was JSR überhaupt ist. JSR wurde im März 2024 ebenfalls von Deno vorgestellt. Dabei handelt es sich um ein neues Paket-Repository für JavaScript und TypeScript. Das Ziel ist es, natürlich die Schwächen von npm zu beseitigen.

Warum dies überhaupt ein Thema ist, liegt an der ständigen Weiterentwicklung im Bereich von JavaScript.

Seit 2009 hat sich in der JavaScript-Welt einiges verändert: Der Standard für die Modularisierung hat sich von CommonJS zu ES-Modulen (ESM) verschoben, was eine modernere und effizientere Handhabung von Modulen ermöglicht. Zudem hat TypeScript enorm an Popularität gewonnen und bietet Entwicklern die Möglichkeit, JavaScript mit statischer Typisierung zu schreiben, was die Codequalität und Wartbarkeit verbessert. Darüber hinaus sind neben Node.js neue Laufzeitumgebungen wie Deno, Bun und Cloudflare Workers entstanden, die innovative Features und Standards unterstützen und die Vielfalt der JavaScript-Entwicklung weiter bereichern.

Die wichtigsten Punkte:

  • Optimiert für TypeScript: Entwickler können TypeScript-Code direkt veröffentlichen, ohne vorherige Transpilation.

  • Unterstützt nur ES-Module: Fördert die Nutzung des aktuellen JavaScript-Modulstandards.

  • Kompatibel mit verschiedenen Laufzeiten: Funktioniert mit Deno, Node.js, Bun, Cloudflare Workers und anderen.

  • Kostenlos und Open Source: JSR ist unter der MIT-Lizenz verfügbar und lädt zur Community-Beteiligung ein.

Exkurs — ESM vs CJS

Hier ein kleiner Exkurs, um nochmal die Vorteile für die Verwendung von ESModules aufzuzeigen.

Syntax und Lesbarkeit

Die ESM-Syntax ist klarer und leichter zu lesen, da import und export nativ sind, im Gegensatz zur Funktionssyntax von require() und 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"));

Statische Analyse

Bei ESM können Tools (wie Webpack) nur die benötigten Funktionen (hier add) laden und ungenutzten Code (z.B. subtract) weglassen, während bei CommonJS das gesamte Modul geladen wird.

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'; // Nur "add" wird importiert
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'); // Hier wird das ganze Modul geladen
console.log(add(5, 3));

Asynchrones Laden

ESM wird direkt im Browser unterstützt und kann asynchron geladen werden, was bei CommonJS nicht möglich ist.

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
// Hier gibt es kein Browser-Beispiel, da CommonJS synchron geladen wird und normalerweise transpiliert werden muss.
const greet = require('./module.js');
console.log(greet("Alice"));

Modul-Bereiche (strict mode)

In ESM gibt es keine this-Bindung auf globaler Ebene und es läuft automatisch im strict mode, was die Fehleranfälligkeit reduziert.

ESModules (ESM):

1
2
3
4
// module.mjs (strict mode ist standardmässig aktiv)
export const greet = function () {
  console.log(this); // undefined, da kein `this` im Modul-Bereich existiert
};

CommonJS (CJS):

1
2
3
4
5
// module.js (nicht automatisch strict mode)
const greet = function () {
  console.log(this); // Zeigt auf `global` oder `module.exports`
};
module.exports = greet;

Exports als echte Objekte

Bei ESM sind Exports echte, geteilte Bindungen. Änderungen im Modul werden sofort reflektiert. Bei CJS hingegen werden nur Kopien der Werte exportiert, daher sind Änderungen nicht sichtbar.

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, Änderung ist sichtbar

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, Änderung ist nicht sichtbar

Standardbibliothek — “std”

Auch an der Standardbibliothek von Deno hat sich seit dem ersten Release vor 4 Jahren einiges geändert. Bei den ausgewählten Modulen handelt es sich um gründlich überprüfte Module, die eine grosse Anzahl von Funktionen, wie Datenmanipulation, Web-bezogene Logik und JS-spezifische Funktionen, mitbringen. Diese Bibliothek steht auch bei JSR zur Verfügung und kann somit in anderen Projekten verwendet werden.

Beispiele für Module in der Deno Standardbibliothek und ihre Entsprechungen auf npm:

Deno Standardbibliothek Modul Entsprechendes npm-Paket
@std/testing jest
@std/expect chai
@std/cli minimist
@std/collections lodash
@std/fmt chalk
@std/net get-port
@std/encoding rfc4648

Für eine vollständige Liste der verfügbaren Pakete besuche: https://jsr.io/@std.

Private npm registries

Dies funktioniert nun so, wie auch bei Node und npm. Dafür ist einfach ein .npmrc-File notwendig.

Workspaces and monorepos

Durch die Verwendung von Workspaces für einzelne Ordner/Packages ist es möglich, das Projekt als Monorepo aufzubauen. Dazu muss in der obersten Ebene eine deno.json- oder package.json-Datei angelegt werden, die die einzelnen Workspaces enthält. Anschliessend kann jedes dieser Workspaces seine eigene deno.json-Datei haben.

Initialer Zustand, nach dem Checkout aus dem Beispiel 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

Die Defition der einzelnen Workspaces sieht auf obersten Ebene dann wie folgt aus:

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

Führt man nun ein deno install aus um alle Dependencies zu installieren, wird ein deno.lock und ein node_modules Ordner auf obersten Ebene angelegt.

 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

Da Deno bisher wöchentliche Bugfix-Releases und alle sechs Wochen Minor-Releases veröffentlicht hat, ist es besonders für grössere Enterprise-Firmen schwer, diesem Rhythmus zu folgen. Aus diesem Grund plant Deno, ab Version 2.1 eine LTS-Version bereitzustellen. Dieser LTS-Kanal erhält für sechs Monate kritische Bugfixes.

Deno für Enterprise

Für Teams, die erweiterten Support benötigen, hat Deno das Deno for Enterprise Programm eingeführt. Dieses bietet:

  • Priorisierten Support
  • Direkten Zugang zu den Deno-Ingenieuren
  • Garantierte Reaktionszeiten
  • Priorität für Ihre Feature-Anfragen

Fazit

Ich muss sagen, nach dem ich alleine mal die neue Version und dessen Features angeschaut habe, sieht es danach aus, dass es den Use Case für Deno enorm erhöht. Besonders die Integration in bestehende Projekte die zuvor auf Node.js und npm gesetzt haben. Durch die Möglichkeit weiterhin package.json und auch deno.json zu nutzen, kann ein weicher Übergang zu deno erreicht werden.

Doch in nächster Zeit werde ich es auf jeden Fall probieren es in bestehende Projekte einzubringen um zu sehen, ob das wirklich alles so wie geplant funktioniert. Die Hürden, die sonst bei der Einführung neuer Technologien üblich sind, sind durch diese nahtlose Integration deutlich geringer. Es hängt natürlich vom jeweiligen Projektteam ab, ob Deno komplett ersetzt wird oder Schritt für Schritt eingeführt wird.

Die Einführung von LTS-Versionen finde ich besonders für Enterprise-Kunden hervorragend. In grossen Unternehmen kann man nicht immer sofort auf die neuesten Versionen umsteigen, daher bietet die LTS-Unterstützung die nötige Stabilität und Sicherheit. Und macht es auf die Art und Weise auch möglich Deno wirklich in mehr Projekten zu verwenden.

Insgesamt bin ich sehr optimistisch, was die Zukunft von Deno angeht. Die Verbesserungen in Deno 2 machen es zu einer noch attraktiveren Option für moderne JavaScript- und TypeScript-Projekte. Ich freue mich darauf, die Vorteile der neuen Version voll auszuschöpfen und bin gespannt, wie sich Deno weiterentwickeln wird.

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.