TypeScript mit Deno: Native Unterstützung ohne Konfiguration
AI generated
type
TypeScript
TypeScript mit Deno
Native Unterstützung ohne Build-Konfiguration

Deno führt .ts-Dateien direkt aus, ohne tsc, ohne ts-node und ohne node_modules. Wer aus dem Node.js-Ökosystem kommt, muss dafür ein paar Annahmen über Type-Checking, Module und Sicherheit neu justieren.

9 Min. Lesezeit Deno Runtime

1. Warum Deno TypeScript ohne Umwege ausführt

Deno wurde von Ryan Dahl, dem ursprünglichen Node.js-Schöpfer, mit der Erkenntnis gebaut, dass ein Großteil der serverseitigen JavaScript-Welt inzwischen in TypeScript schreibt. Statt TypeScript als nachträgliches Zusatzpaket zu behandeln, ist der Transpiler fester Bestandteil der Runtime selbst.

Jede Datei mit der Endung .ts, .tsx oder .mts wird beim Ausführen automatisch erkannt und ohne separaten Kompilierschritt gestartet. Es gibt keine tsconfig.json-Pflicht, keinen ts-node-Loader und keine Registrierung eines Transpilers auf der Kommandozeile.

Das senkt die Einstiegshürde für neue Projekte spürbar, verschiebt aber auch Verantwortung: Wer echtes Type-Checking vor der Auslieferung will, muss das bewusst als eigenen Schritt einplanen, weil deno run es standardmäßig nicht tut.

Für Teams, die aus einem Node.js-Umfeld mit ts-node oder tsx kommen, entfällt dadurch eine ganze Kategorie von Konfigurationsproblemen: Loader-Registrierung, Pfadauflösung und Interop zwischen ESM und CommonJS sind in Deno von Anfang an einheitlich gelöst statt projektspezifisch zusammengestückelt.

2. Erste Schritte: deno run ohne Vorkonfiguration

Ein neues Skript beginnt ohne npm init, ohne package.json und ohne Installationsschritt. Eine einzelne Datei mit typisierten Funktionen lässt sich direkt über deno run starten, inklusive Autovervollständigung in der IDE, sobald der Deno-Language-Server aktiv ist.

Berechtigungen werden explizit über Flags erteilt, etwa --allow-net für Netzwerkzugriff oder --allow-read für Dateisystemzugriff. Ohne diese Flags schlägt jeder entsprechende Aufruf zur Laufzeit mit einer klaren Fehlermeldung fehl, nicht erst still beim Deployment.

Für den täglichen Gebrauch reicht deno run --allow-net=api.example.com script.ts völlig aus, um ein typisiertes Skript mit eingeschränktem Netzwerkzugriff zu starten, ganz ohne vorherige Projektstruktur.


// greet.ts
interface Greeting {
  name: string;
  loud?: boolean;
}

function greet({ name, loud = false }: Greeting): string {
  const message = `Hallo, ${name}!`;
  return loud ? message.toUpperCase() : message;
}

console.log(greet({ name: "Team", loud: true }));

// Ausführen: deno run greet.ts

3. Der Compiler unter der Haube: Type-Stripping statt Type-Checking

Beim einfachen deno run entfernt Deno lediglich die Typannotationen aus dem Quellcode, ähnlich wie es Node.js seit Version 22 mit dem experimentellen Strip-Modus tut. Es findet dabei keine semantische Prüfung statt: fehlerhafte Typen werden schlicht ignoriert.

Für vollwertiges Type-Checking existiert der separate Befehl deno check, der den echten TypeScript-Compiler-Kern lädt und alle Fehler meldet, die auch tsc --noEmit finden würde. Dieser Schritt gehört in jede CI-Pipeline, wird aber lokal bewusst übersprungen, um die Startzeit niedrig zu halten.

Diese Trennung ist der wichtigste Unterschied zu klassischen ts-node-Setups: Geschwindigkeit beim Entwickeln, aber ein expliziter, separater Prüfschritt vor dem Merge oder Release.

In der Praxis bewährt sich ein deno check-Aufruf als Pre-Commit-Hook oder als eigener CI-Job vor dem Testlauf, damit Typfehler nicht erst in der Produktion auffallen, wenn eine falsch typisierte Funktion mit unerwarteten Werten aufgerufen wird.

4. Import Maps und URL-Imports statt node_modules

Deno importiert Module standardmäßig über vollständige URLs oder über npm:- und jsr:-Specifier, statt sie in einem lokalen node_modules-Ordner zu erwarten. Heruntergeladene Module landen in einem globalen, inhaltsadressierten Cache und werden projektübergreifend geteilt.

Import Maps in deno.json übersetzen kurze Bezeichner in vollständige Spezifizierer, sodass Importe im Code trotzdem lesbar bleiben, statt lange URLs in jeder Datei zu wiederholen.

Für bestehende npm-Pakete, die nicht ESM-nativ sind, funktioniert der npm:-Specifier meist transparent, inklusive Auflösung von CommonJS-Interop im Hintergrund.

Versionsangaben direkt im Specifier, etwa npm:zod@^3.23.0, ersetzen die Semver-Range aus package.json und machen jede Abhängigkeit an der Importstelle sofort sichtbar, statt sie in einer separaten Datei nachschlagen zu müssen.


// deno.json (Ausschnitt)
{
  "imports": {
    "std/": "https://deno.land/std@0.224.0/",
    "zod": "npm:zod@^3.23.0"
  }
}

// main.ts
import { z } from "zod";
import { parse } from "std/csv/mod.ts";

const Schema = z.object({ id: z.number(), name: z.string() });

5. deno.json und deno.jsonc: zentrale Konfigurationsdatei

Statt package.json, tsconfig.json und .eslintrc getrennt zu pflegen, bündelt deno.json Compiler-Optionen, Import Maps, Lint-Regeln, Formatierungseinstellungen und benannte Tasks in einer Datei.

Der Abschnitt compilerOptions übernimmt einen Großteil der aus tsconfig.json bekannten Felder wie strict oder lib, wirkt aber nur auf deno check, nicht auf die Ausführung selbst. Nicht unterstützte Felder wie module oder outDir werden schlicht ignoriert, da Deno weder ein eigenes Modulsystem transpiliert noch klassische Ausgabeverzeichnisse erzeugt.

Tasks unter tasks ersetzen npm-Scripts: deno task dev startet definierte Befehle genauso wie npm run dev, ohne dass dafür ein zusätzliches Tool nötig wäre.


// deno.json
{
  "compilerOptions": {
    "strict": true,
    "lib": ["deno.window"]
  },
  "tasks": {
    "dev": "deno run --watch --allow-net main.ts",
    "test": "deno test --allow-read"
  }
}

6. Das Permissions-Modell und was es für TypeScript-Code bedeutet

Deno startet jeden Prozess standardmäßig ohne Zugriff auf Dateisystem, Netzwerk, Umgebungsvariablen oder Subprozesse. Jede Erweiterung muss explizit als Flag angegeben werden, was das Prinzip geringster Rechte direkt auf der Ebene der Runtime durchsetzt.

Für TypeScript-Bibliotheken bedeutet das, dass Typdefinitionen allein keine Aussage über tatsächliche Seiteneffekte machen: Eine Funktion kann typkorrekt sein und trotzdem beim Aufruf an fehlenden Berechtigungen scheitern, wenn die Flags nicht gesetzt sind.

In der Praxis lohnt es sich, Berechtigungen so eng wie möglich zu granulieren, etwa --allow-read=./config statt eines pauschalen --allow-read, um versehentlichen Zugriff auf sensible Pfade auszuschließen.

Für Bibliotheken, die von Dritten eingebunden werden, empfiehlt sich zusätzlich eine klare Dokumentation der benötigten Flags, damit Nutzer nicht durch Ausprobieren herausfinden müssen, welche Berechtigungen ein Paket tatsächlich benötigt.

7. Testing mit Deno.test und TypeScript

Der eingebaute Testrunner deno test benötigt keine zusätzliche Bibliothek wie Jest oder Vitest. Assertions kommen aus der Standardbibliothek unter std/assert und sind bereits vollständig typisiert.

Testdateien werden typischerweise als *_test.ts oder unter tests/ abgelegt und automatisch erkannt. Snapshot-Tests, Coverage-Reports über deno test --coverage und paralleles Ausführen sind ohne weitere Konfiguration verfügbar.

Weil Testdateien ebenfalls dem Permissions-Modell unterliegen, lassen sich Tests, die Netzwerk- oder Dateisystemzugriff simulieren, gezielt mit den passenden Flags isolieren.


// greet_test.ts
import { assertEquals } from "std/assert/mod.ts";
import { greet } from "./greet.ts";

Deno.test("greet formats name correctly", () => {
  assertEquals(greet({ name: "Team" }), "Hallo, Team!");
});

Deno.test("greet respects loud flag", () => {
  assertEquals(greet({ name: "Team", loud: true }), "HALLO, TEAM!");
});

8. Von deno compile bis Deno Deploy: TypeScript ausliefern

deno compile bündelt ein TypeScript-Programm samt Runtime in eine einzige ausführbare Datei für die Zielplattform, ganz ohne separat installierte Deno-Installation auf dem Zielsystem. Das eignet sich besonders für CLI-Tools, die als Binary verteilt werden sollen.

Für Webdienste bietet Deno Deploy eine Edge-Runtime, die TypeScript-Handler direkt aus dem Repository ausführt, inklusive automatischer Skalierung über global verteilte Regionen hinweg.

Beide Wege teilen sich denselben Type-Stripping-Mechanismus wie deno run, weshalb ein vorheriger deno check-Lauf in der CI-Pipeline vor dem Deployment dringend empfehlenswert bleibt.


# Kompiliert main.ts zu einer eigenständigen Executable
deno compile --allow-net --output mein-tool main.ts

./mein-tool

9. Migration von Node.js: npm-Specifier und Interop

Eine vollständige Neuschreibung ist für die Migration selten nötig. Der npm:-Specifier erlaubt es, bestehende npm-Abhängigkeiten direkt zu importieren, während projektinterner Code schrittweise auf native Deno-APIs umgestellt wird.

Node-spezifische APIs wie fs, path oder process stehen über das node:-Präfix zur Verfügung, was Portierungen von Node.js-Skripten erheblich vereinfacht, ohne jede Zeile neu schreiben zu müssen.

Für den umgekehrten Weg unterstützt Node.js seit Version 22 experimentell das direkte Ausführen von TypeScript-Dateien über Type-Stripping, was die Kluft zwischen beiden Runtimes bei der reinen TS-Ausführung zunehmend verkleinert.

Ein realistischer Migrationspfad beginnt selten mit dem gesamten Bestand: Neue CLI-Tools, Edge-Funktionen oder isolierte Skripte eignen sich als erster Schritt, während der bestehende Node.js-Kern zunächst unverändert weiterläuft.


// Node-APIs und npm-Pakete gemeinsam nutzen
import { readFileSync } from "node:fs";
import { z } from "npm:zod@^3.23.0";

const raw = readFileSync("./config.json", "utf-8");
const Config = z.object({ port: z.number() });
const config = Config.parse(JSON.parse(raw));
Merkmal Deno Node.js + ts-node Bun
TypeScript-Unterstützung Nativ, ohne Zusatzpaket Nur über ts-node/tsx als Zusatzpaket Nativ, eigener Transpiler
Type-Checking beim Start Deaktiviert, separat via deno check Abhängig von ts-node-Konfiguration Deaktiviert, kein eingebauter Checker
Package-Management URL-/npm-Imports, kein node_modules nötig npm mit node_modules npm-kompatibel, eigener Installer
Sicherheitsmodell Explizite Permissions (--allow-read etc.) Kein eingebautes Sandboxing Kein eingebautes Sandboxing
Kompilierung zu Binary deno compile erzeugt Single-Executable Nicht nativ, externe Tools nötig bun build --compile verfügbar

Mironsoft

TypeScript-Migration, Typsicherheit und Team-Onboarding

JavaScript-Codebasis ohne Typsicherheit, aber keine Zeit für eine Rundum-Migration?

Wir migrieren bestehende JavaScript-Projekte schrittweise zu TypeScript, richten strikte Compiler-Einstellungen sauber ein und bringen Teams mit Code-Reviews und Style-Guides auf denselben Typsicherheits-Stand.

Migrations-Fahrplan

Schrittweise JS-zu-TS-Migration ohne Big-Bang-Risiko planen und umsetzen.

Strict-Mode-Einführung

tsconfig.json, ESLint-Regeln und CI-Checks für dauerhafte Typsicherheit aufsetzen.

Team-Onboarding

Entwickler mit Workshops und Code-Reviews in TypeScript-Best-Practices einarbeiten.

10. Zusammenfassung

TypeScript mit Deno

Kein Build-Schritt

Type-Stripping direkt zur Laufzeit

Sicherheits-Sandbox

Explizite Permissions statt Vollzugriff

npm-Kompatibilität

npm:-Specifier für bestehende Pakete

Single Binary

deno compile für Executables

11. FAQ: TypeScript mit Deno

1Braucht Deno eine tsconfig.json?
Nein. Deno funktioniert ohne jede Konfigurationsdatei. Optional lassen sich Compiler-Optionen im compilerOptions-Block von deno.json setzen, die dann für deno check gelten.
2Prüft deno run die Typen wirklich?
Nein, deno run entfernt nur die Typannotationen, ohne sie zu validieren. Echtes Type-Checking läuft ausschließlich über den separaten Befehl deno check.
3Kann ich npm-Pakete in Deno verwenden?
Ja, über den npm:-Specifier lassen sich die meisten npm-Pakete direkt importieren, inklusive automatischer Auflösung von CommonJS-Interop im Hintergrund.
4Was ist der Unterschied zwischen deno.json und package.json?
deno.json bündelt Compiler-Optionen, Import Maps, Lint- und Format-Regeln sowie Tasks in einer Datei, während package.json in Node.js primär Abhängigkeiten und Scripts verwaltet.
5Wie funktioniert das Permissions-Modell bei TypeScript-Bibliotheken?
Typdefinitionen sagen nichts über tatsächliche Seiteneffekte aus. Eine Funktion kann typkorrekt sein und trotzdem zur Laufzeit an fehlenden Flags wie --allow-net scheitern.
6Kann ich Deno-Code als eigenständige Executable verteilen?
Ja, deno compile bündelt Programm und Runtime in eine einzige ausführbare Datei, die auf der Zielplattform ohne separate Deno-Installation läuft.
7Wie teste ich TypeScript-Code mit Deno ohne zusätzliche Bibliothek?
Der eingebaute Testrunner deno test nutzt Assertions aus std/assert und erkennt Dateien wie *_test.ts automatisch, inklusive Coverage-Reports und paralleler Ausführung.
8Lohnt sich eine Migration von Node.js zu Deno?
Für bestehende Projekte oft nicht als Komplettumzug. Realistischer ist ein schrittweiser Einsatz über den npm:-Specifier für neue Tools, CLIs oder Edge-Funktionen.
9Unterstützt Deno auch JSX und TSX-Dateien?
Ja, .tsx-Dateien werden ohne Zusatzkonfiguration erkannt und transpiliert, die JSX-Factory lässt sich über compilerOptions in deno.json anpassen, etwa für React oder Preact.
10Kann Node.js inzwischen auch TypeScript direkt ausführen?
Seit Node.js 22 existiert ein experimenteller Type-Stripping-Modus, der Typannotationen ähnlich wie Deno entfernt, jedoch ohne eingebauten Type-Checker oder Permissions-Modell. Für produktiven Einsatz bleibt ein separater tsc-Lauf in der CI-Pipeline deshalb weiterhin sinnvoll.