zumbrunn.com

30 Jahre Javascript auf dem Server

Javascript auf dem Server, oder ServerJS, wie die Community es später nennen sollte, hätte eigentlich von Anfang an eine Erfolgsgeschichte sein müssen. Als Netscape 1996 Javascript server-seitig als LiveWire einführte, lagen sie in vielen Punkten bemerkenswert richtig.

Die Vision war elegant: dieselbe Sprache sowohl auf dem Client als auch auf dem Server verwenden. Man konnte Javascript schreiben, das ein Formularfeld im Browser validierte, und dann dasselbe Javascript verwenden, um dieselben Daten auf dem Server zu validieren und zu verarbeiten. Das war nicht bloss praktisch, sondern konzeptionell kraftvoll. Das mentale Modell war einheitlich. Die Fähigkeiten waren übertragbar, der Code konnte geteilt werden.

LiveWire war Pionier bei Ideen, die Jahrzehnte später zur Standardpraxis werden sollten. Es verfolgte automatisch den benutzerspezifischen Zustand (über das Client-Objekt), bildete Datenbankzugriffe auf Javascript-Objekte und ihre Eigenschaften ab (über cursor.next()-Iteration) und konnte HTML-Markup dynamisch auf dem Server erzeugen. Die Möglichkeit, Server-Logik direkt in Seiten einzubetten, sollte später in PHP, ASP, JSP und schliesslich in modernen Frameworks wie Next.js und Fresh wieder auftauchen. Netscape verstand, dass Entwickler HTML dynamisch erzeugen und Zustände über Anfragen hinweg aufrechterhalten mussten, mit enger Integration des Datenbankzugriffs. Diese Fähigkeiten bauten sie von Anfang an in LiveWire ein.

Besonders beeindruckend war, dass LiveWire erkannte, dass server-seitiger Code kompiliert und gecacht werden musste und nicht bei jeder Anfrage neu interpretiert werden sollte. Der LiveWire-Compiler verwandelte Javascript in Bytecode, den der Server effizient ausführen konnte. Das war ausgereiftes Denken für 1996.

Doch trotz all seiner technischen Weitsicht beging Netscape einen verhängnisvollen strategischen Fehler. Anstatt ihre server-seitige Javascript-Implementierung als offenen Standard freizugeben, der auf jedem Webserver wie Apache oder IIS laufen konnte, behielten sie ihn exklusiv für ihren eigenen Netscape Enterprise Server — im Wesentlichen versuchten sie, das Web zu besitzen. Anstatt daran teilzunehmen, wollten sie das Unternehmen sein, das seine Zukunft dominieren würde.

Aber das Web entwickelte sich nach anderen Regeln, basierend auf Prinzipien der Offenheit und Zugänglichkeit. Die Technologien, die es definieren sollten — HTML, HTTP und Javascript selbst — setzten sich durch, weil sie offene Standards waren, die jeder implementieren konnte. Indem Netscape LiveWire proprietär hielt, verhinderte es, dass Javascript auf dem Server von Anfang an Fuss fasste und dominierte, wie es im Browser der Fall war.


Der 150.000-fache Geschwindigkeitszuwachs in 30 Jahren

Netscapes erste Inkarnation von Javascript auf dem Server erkannte die Notwendigkeit schnellerer Codeausführung und fügte einen Build-Schritt hinzu, der Javascript in Bytecode kompilierte. Doch die damaligen Webentwickler wollten Web-Seiten modifizieren und ausliefern können, ohne vorher neu bauen zu müssen. Moderne Javascript-Laufzeitumgebungen wie Deno und Bun erben die grossen Leistungsgewinne, die über die Jahrzehnte erzielt wurden und diese Herausforderung beseitigten.

Zwischen 1995 und heute hat sich die Ausführungsgeschwindigkeit von Javascript um etwa 150.000 Mal verbessert. Das ist kein Tippfehler. Javascript-Engines wurden durch Software-Optimierung rund 430 Mal schneller, und Hardware wurde in der single-threaded Ausführungsgeschwindigkeit rund 350 Mal schneller. Der kumulative Effekt ist verblüffend: 430 × 350 = über 150.000 Mal schneller.

Der Durchbruch kam mit der Just-In-Time-Kompilierung. Anstatt Quellcode Zeile für Zeile zu interpretieren, übersetzten JIT-Compiler Javascript während der Programmausführung in echten Maschinencode. Dieser Fortschritt entfachte einen harten Wettbewerb zwischen Googles V8-Javascript-Engine, Mozillas SpiderMonkey und Apples JavascriptCore mit der Nitro-Engine, wobei alle immer ausgefeiltere Optimierungen hinzufügten. Obwohl das im Wesentlichen ein Browserkrieg war, wurde Javascript auf dem Server zu einem der grössten Nutzniesser.


Samen für ein aufkeimendes Ökosystem

Obwohl Brendan Eich Javascript in nur wenigen Tagen im Jahr 1995 schuf, um es schnell in den Browser einzubauen, waren die server-seitigen Ambitionen von Anfang an da. Eich stellte sich eine Sprache vor, die sowohl im Browser als auch auf dem Server laufen konnte und Entwicklern ermöglichte, Code einmal zu schreiben und ihn über den gesamten Web-Stack hinweg einzusetzen. Das war kein Nachgedanke oder späterer Schwenk — es war Teil der ursprünglichen Designphilosophie.

LiveWires proprietäre Bindung verhinderte, dass sich ein vollständiges Ökosystem mit Werkzeugen, Bibliotheken, Community-Unterstützung und Distributionsmechanismen entwickeln konnte. Verschiedene Javascript-Projekte auf dem Server verfolgten in den folgenden Jahren unterschiedliche einzigartige Ansätze, um das zu umgehen.

WebCrossing ging dieses Problem mit einer eigenständigen architektonischen Vision an: einer integrierten Plattform, bei der Javascript und Datenbankspeicherung von Anfang an eng miteinander verbunden waren. WebCrossing bot eine Web-Anwendungsplattform mit eingebauten NoSQL-Datenbankfähigkeiten und Javascript als Skriptsprache. Das war seiner Zeit wirklich voraus. Die Idee einer Javascript-nativen Plattform mit integrierter Datenpersistenz kommt der modernen «Full-Stack-Javascript»-Bewegung um Jahre zuvor. WebCrossing löste den Mangel an einem Werkzeug- und Bibliotheksökosystem durch extreme Monolithizität. Es machte nicht nur die Datenbankdaten als durchsuchbaren Objektbaum in Javascript direkt zugänglich, sondern ermöglichte direkten Skriptzugriff auf einen vollständigen Stack einschliesslich SMTP, POP, IMAP, NNTP, FTP, XML-RPC und HTTP selbst, alles in derselben Binary integriert und direkt skriptierbar, mit allen Diensten, die mit demselben Inhaltsbaum interagierten. WebCrossing zeigte, dass Entwickler vollständige Web-Anwendungen in Javascript erstellen konnten, ohne separate Datenbanksysteme integrieren, externe Dienste in Anspruch nehmen oder komplexe Deployment-Pipelines verwalten zu müssen. Aber wie LiveWire war WebCrossing eine proprietäre Plattform mit einer kleinen Community. Es bewies, dass das Konzept funktionierte, konnte aber nicht die Netzwerkeffekte erzeugen, die für eine breite Akzeptanz notwendig gewesen wären — weil es proprietär war.

Ein sehr anderer architektonischer Ansatz wurde vom Helma-Projekt verfolgt, das Javascript auf der Java Virtual Machine laufen liess. Diese Strategie hatte echte Vorzüge. Die JVM war ausgereift, kampferprobt und wurde von einem riesigen Enterprise-Ökosystem unterstützt. Wenn man Javascript auf der JVM laufen lassen konnte, konnte man theoretisch Javas Threading-Modell, seine umfangreiche Standardbibliothek, seine Enterprise-Werkzeuge und seine professionelle Legitimität nutzen. Noch wichtiger bot die JVM etwas Entscheidendes: Leistung durch HotSpot JIT-Kompilierung. Es ging nicht nur darum, auf Javas Ökosystem zuzugreifen — sondern darum, ernsthafte Laufzeitoptimierung für Javascript-Code zu erhalten.

Aufgebaut auf Mozillas Rhino-Engine bot Helma multi-threaded server-seitiges Javascript mit Object-Relational-Mapping-Fähigkeiten, Datenbankintegration und einem vollständigen Web-Anwendungsframework. Es war architektonisch solide, technisch beeindruckend und wirklich produktionsreif. Entwickler konnten Javascript-Code schreiben, der auf Java-Bibliotheken zugriff, Javas Nebenläufigkeitsprimitive verwendete und auf jedem JVM-kompatiblen Server deployt wurde.

Helma kompilierte Javascript in Java-Bytecode, der dann durch den HotSpot-Compiler der JVM lief. Das bedeutete, dass Helma von denselben JIT-Kompilierungs-Durchbrüchen profitierte, die später V8 revolutionär machen sollten. HotSpot profilierte den laufenden Bytecode, identifizierte Hot Paths und kompilierte sie zu optimiertem Maschinencode. Für Server-Workloads, bei denen dieselben Code-Pfade wiederholt ausgeführt werden, war diese Optimierung transformativ. Helma war keine langsame interpretierte Lösung. Es hatte ernsthafte Leistungsoptimierung durch einen der ausgereiftesten JIT-Compiler, die es gab.

Helmas Architektur machte maximalen Gebrauch von Javascripts Prototyp-Objektvererbung und implementierte ein sauberes Model-View-Control-System, das man in separaten Dateien halten konnte. Javascript-Prototypen repräsentierten Daten und Geschäftslogik, die über Helmas eingebautes Object-Relational-Mapping auf die Datenbank abgebildet wurden (Model). Diese Javascript-Prototypen hatten auch Skin-Templates (Views), die HTML-Komponenten mit Platzhaltern und der Fähigkeit waren, Makros aufzurufen, sowie Prototyp-Methoden (Controls), die «Actions» waren, die Anfragen oder die Makro-Handler verarbeiteten und von innerhalb der Views aufrufbar waren. So hatte Helma eine sehr klare und explizite MVC-ähnliche Struktur, rund fünf Jahre bevor Ruby on Rails das Konzept populär machte.

Die e4xd-Version von Helma lud die Views als E4X-Objekte. E4X, oder ECMAScript for XML, wurde 2004 als ECMA-357 standardisiert. Es ermöglichte Entwicklern, XML-Literale direkt in Javascript-Code zu schreiben und XML als nativen Datentyp statt als zu parsende Strings zu behandeln. Helmas E4X-Unterstützung war wohl JSX überlegen, das 10 Jahre später in React implementiert wurde und im Wesentlichen dasselbe Problem löste. Beide gaben die Möglichkeit, eingebettetes Markup direkt im Code zu haben, aber JSX erfordert einen Build-Schritt. Man muss JSX in reguläres Javascript transpilieren, bevor es laufen kann. E4X brauchte das nicht. Es war ein natives Javascript-Sprachfeature, in die Laufzeitumgebung integriert. Kein Build-Schritt, kein Transpiler, keine Konfiguration. Die Rhino-Javascript-Engine verstand XML-Literale genauso wie String-Literale oder Array-Literale. E4X in Helma funktionierte hervorragend zum Zusammenstellen und Rendern von HTML auf dem Server — etwas, wozu Next.js mit JSX 10 Jahre später zurückkehrte.

Während XML sich hervorragend als HTML-Vorlagen eignete, war es nicht ideal für Datentransport und Interoperabilität. Dafür hatte Javascript eine andere native Antwort in Form der Javascript Object Notation. Douglas Crockford schlug 2002 vor, JSON als leichtgewichtigen Datenaustauschformat-Standard anzuerkennen. Er erkannte, dass Javascripts native Objektsyntax ein effizientes, menschenlesbares, sprachagnostisches Format für die Server-Client-Kommunikation bot. Es war Javascript-nativ, was bedeutete, dass das Parsen und Erzeugen von JSON in Javascript keine externen Bibliotheken oder komplexe Transformationen erforderte. Bei der Verwendung von Javascript auf dem Server war das ganz offensichtlich und eine natürliche Ergänzung. Die Akzeptanz in anderen Umgebungen war mehr ein Kampf bergauf, da XML als universelles Datenformat vom Thron gestossen werden musste. Als jede andere Sprache JSON-Parser und -Generatoren implementierte, begannen Server, die in jeder Sprache geschrieben waren, mit Javascript-Clients über JSON zu kommunizieren. Es wurde das universelle Datenformat für Web-APIs.


Das Lösen von Abhängigkeiten lässt das Ökosystem keimen

Als jQuery 2006 die Verwendung von Closures im Browser populär machte, begann die Helma-Community damit zu experimentieren, Javascript-Bibliotheksmodule als Teil des Modulladeprozesses in Closures einzuwickeln — ein Konzept, das das Helma-Team in einem Architektur-Framework auf niedrigerer Ebene vollständig übernahm, das zu RingoJS wurde. Dieser Modulsystem-Ansatz trug später zur Formalisierung von CommonJS bei. Als Mozillas Kevin Dangoor 2009 einen Blog-Post schrieb, der zur ServerJS-Interoperabilität aufrief, meldete sich die Helma-Community, unter anderen, bei ihm, und eine Gruppe bildete sich, die zusammenarbeitete, um die CommonJS-Modulspezifikation zu definieren.

Da noch kein natives Modulsystem als Teil des Javascript-Sprachstandards vorhanden war — was erst etwa sechs Jahre später kommen sollte — ermöglichte CommonJS Javascript-Umgebungen den Aufbau eines Ökosystems nativer Bibliotheken, ähnlich dem, was für Helma in Form von Java-Bibliotheken existierte. Die require()-Funktion und das module.exports-Muster wurden zum Standard im gesamten server-seitigen Javascript-Ökosystem.

Den deutlichsten Bedarf an einem ordentlichen nativen Modulsystem hatte Node.js, ein Projekt, das Ryan Dahl im November 2009 auf der JSConf EU erstmals vorstellte. Node.js wählte CommonJS als sein Modulsystem. Nodes Bedarf an Dependency-Handling und einem Modulsystem war dringlicher, weil sein Ziel war, Javascript zu einer erstklassigen allgemeinen Server-Sprache zu machen, nicht bloss zu einem Web-Framework. Als Registry von CommonJS-Modulen und um sie mit all ihren Abhängigkeiten automatisch herunterzuladen, implementierte Isaac Schlueter NPM. Je mehr Pakete in der Registry auftauchten, desto einfacher wurde es, komplexe Anwendungen durch Kombination vorhandener Pakete aufzubauen, anstatt alles von Grund auf neu zu schreiben. Die «Dafür gibt es ein Paket»-Kultur entstand. Kommandozeilenargumente parsen? Es gibt ein Paket. HTTP-Anfragen stellen? Es gibt ein Paket. E-Mail-Adressen validieren? Es gibt ein Paket. Die Hürde zum Aufbau anspruchsvoller Anwendungen sank dramatisch. Das Ökosystem explodierte exponentiell. Bis 2015 hatte NPM Hunderttausende von Paketen. Bis 2020 über eine Million.


Der Schwenk von JVM zu Node

Vor 2010 war die JVM der unbestrittene König der Javascript-Server-Entwicklung — ausgereift, schnelle JIT, massive Bibliotheken. Das Erscheinen von Node markiert einen Wendepunkt, nach dem ServerJS nicht mehr am leistungsstärksten in der JVM war, sondern ausserhalb davon.

Node entstand in dem Moment, in dem mehrere Innovationen zusammenflossen. Googles V8-Engine machte JavaScript plötzlich schnell durch JIT-Kompilierung. JSON war zum universellen Datenformat geworden. CommonJS bot ein vernünftiges Modulsystem mit NPMs explodierendem Ökosystem. Node kombinierte das mit dem Wechsel von multi-threaded zu single-threaded mit einer Event-Loop für nicht-blockierendes I/O. Plötzlich war es möglich, performante Web-Anwendungen ausserhalb der JVM zu schreiben. JavaScript wandelte sich fast über Nacht von dem, was Mainstream-Entwickler als «Browser-Spielzeug» abgetan hatten, zu einer ernsthaften Server-Plattform auch ausserhalb der JVM.

Nodes Übernahme von nicht-blockierendem I/O war stark von nginx inspiriert, das bereits bewiesen hatte, dass ein single-threaded, ereignisgesteuertes Modell mit libev massive Nebenläufigkeit äusserst effizient handhaben konnte. Anstatt einen Thread zu blockieren, während man auf I/O wartet, registriert man einen Callback und macht weiter. Wenn das I/O abgeschlossen ist, wird der Callback ausgeführt. Die Event-Loop wird zum Orchestrator: Sie verarbeitet Ereignisse, führt Callbacks aus und blockiert niemals. Ein einziger Thread kann Tausende von gleichzeitigen Verbindungen handhaben, weil er niemals wartet — er arbeitet immer nützlich oder schläft effizient, bis das nächste Ereignis eintrifft. Während nginx die Effektivität des Modells für die Bereitstellung statischer Inhalte bewiesen hatte, war die Anwendung auf Anwendungslogik — wo man nicht nur Dateien bereitstellt, sondern komplexe Geschäftslogik mit Datenbankabfragen und API-Aufrufen ausführt — anders. Javascript war eine Sprache, in der Callbacks natürlich waren, wo asynchrone Muster idiomatisch waren, wo das ereignisgesteuerte Modell wie die richtige Art zu programmieren anfühlte. Mit Node war ServerJS unglaublich effizient bei I/O-intensiver Arbeit (der Hauptaufgabe von Web-Servern), sodass es massive Nebenläufigkeit mit geringem Speicherbedarf handhaben konnte.

Im folgenden Jahrzehnt dominierte Node Javascript auf dem Server fast ausschliesslich. In dieser Zeit reifte die Sprache selbst dramatisch. ES6, 2015 veröffentlicht und offiziell ES2015 genannt, brachte Klassen, Pfeilfunktionen, let und const für Block-Scoping, Destrukturierung, Template-Literale und Promises als erstklassige asynchrone Primitive. 2016 brachte WebAssembly Gleitkommaverhalten (f32 und f64), Speichersicherheit und Code-Wiederverwendung über Bibliotheken in Rust, C, C++ und Go, unter anderem. Async/await kam 2017 und löste endlich das Promise-Chain-Problem, das Entwickler geplagt hatte. Die folgenden Jahre fügten Top-Level-Await, private Felder, optionales Chaining und Nullish Coalescing hinzu — jeweils eine kleine, aber sinnvolle Verbesserung der Entwicklererfahrung. Und standardisierte Module, Import- und Export-Syntax, boten eine native Möglichkeit, Code zu organisieren, obwohl die Adoption Jahre hinterherhinkten, da das Ökosystem weiterhin CommonJS verwendete, ohne das Bedürfnis zu verspüren, zu wechseln.


Die perfekte TypeScript-Laufzeitumgebung

Javascripts dynamische Natur — die Tatsache, dass Variablen jeden Typ halten und den Typ zur Laufzeit ändern können — war sowohl eine Stärke als auch eine Schwäche. Sie machte die Sprache flexibel und einfach zu erlernen, aber sie machte grosse Codebasen schwer zu warten. Refactoring war beängstigend, weil man nie sicher sein konnte, was man kaputt machen würde. IDEs konnten keine gute Autovervollständigung oder Refactoring-Werkzeuge bereitstellen, weil sie nicht wussten, welche Typen Variablen hielten. Mit der Einführung von TypeScript kam eine Lösung: schrittweise Typisierung. Man konnte Typ-Annotationen zum Javascript-Code hinzufügen, und der TypeScript-Compiler würde diese Typen zur Kompilierzeit überprüfen und Fehler vor der Laufzeit abfangen. Aber Typisierung war optional, man konnte sie inkrementell einführen und Typen zu den Teilen der Codebasis hinzufügen, wo sie den grössten Wert brachten. Die Werkzeuge waren ausgezeichnet. IntelliSense bot präzise Autovervollständigung. Refactoring-Werkzeuge konnten Variablen und Funktionen sicher über eine gesamte Codebasis umbenennen. Bis 2018 hatte sich TypeScript im Ökosystem von optional zu unverzichtbar gewandelt, aber es war nachträglich angeschraubt worden. Man brauchte einen Kompilierungsschritt, Konfigurationsdateien, Typdefinitionen für jedes Paket und sorgfältige Verwaltung der Grenze zwischen getipptem und ungetipptem Code. Es funktionierte, aber es fühlte sich nach einem Workaround an, nicht nach einem erstklassigen Feature.

Bei JSConf EU 2018 stellte Ryan Dahl in seinem Vortrag «10 Things I Regret About Node.js» Deno vor, das TypeScript zu einem erstklassigen Feature machen und andere Schwächen von Node ansprechen sollte. Dahl identifizierte spezifische Designentscheidungen, die 2009 sinnvoll waren, aber ein Jahrzehnt später ihr Alter zeigten. Das Sicherheitsmodell war eine davon. Node.js gab Programmen standardmässig vollen Zugriff auf das Dateisystem und das Netzwerk. Es gab kein Sandboxing, kein Berechtigungssystem. Es gab noch kein TypeScript. Node machte intensiven Gebrauch von Callbacks und verwendete Promises nicht von Anfang an. Denos Designprinzipien sollten Nodes Einschränkungen direkt ansprechen.

In Deno wurde Sicherheit zur Standardhaltung statt eines Nachgedankens. Deno-Programme laufen standardmässig in einer Sandbox. Wenn Code Dateien lesen, Netzwerkanfragen stellen oder auf Umgebungsvariablen zugreifen muss, müssen solche Berechtigungen explizit erteilt werden. Deno führt TypeScript direkt aus. Man schreibt .ts-Dateien, und Deno behandelt die Kompilierung transparent. Typüberprüfung ist in die Laufzeitumgebung eingebaut. Das Modulsystem wechselte ausschliesslich zu ES-Modulen, mit web-standardkonformer Import-Syntax und URL-basierten Importen. Anstatt NPMs zentralisierter Registry lädt Deno Module direkt von URLs. Man kann von jedem HTTP-Server, jedem CDN, jeder Quelle importieren, die JavaScript-Dateien bereitstellt. Abhängigkeiten werden nach dem ersten Download lokal gecacht, aber es gibt kein package.json, kein node_modules-Verzeichnis, keinen Versionsauflösungsalgorithmus. Zusätzlich zur Unterstützung URL-basierter Importe lancierte das Deno-Team 2024 JSR, die JavaScript Registry. Im Gegensatz zu einer Registry von Tarballs wie NPM ist JSR von Grund auf TypeScript-nativ. Pakete veröffentlichen ihren Quellcode direkt, Typinformationen sind eingebaut, und Dokumentations- und Typabdeckungs-Scores werden automatisch berechnet. Entscheidend ist, dass JSR-Pakete über Laufzeitumgebungen hinweg funktionieren: Node, Bun und Deno gleichermassen. Es ist so konzipiert, dass es eine Registry für das gesamte ServerJS-Ökosystem ist, nicht nur ein Deno-spezifischer Ersatz für NPM.

Deno wird mit eingebauten Werkzeugen wie einem Code-Formatierer, einem Linter, einem Test-Runner und einem Dokumentationsgenerator ausgeliefert — alles direkt im einzelnen Deno-Executable enthalten. Das Fresh-Framework, das auf Deno aufgebaut ist, verwendet eine Islands-Architektur. Die Islands-Architektur in Fresh bedeutet, dass Seiten standardmässig null JavaScript an den Client senden. Der Server rendert HTML und sendet es an den Browser. Wenn man Interaktivität braucht — ein Dropdown-Menü, ein Formular mit Validierung, ein Echtzeit-Update — markiert man diese Komponente als Island. Nur diese Islands senden JavaScript. Der Rest der Seite ist statisches HTML. Das bietet die Leistungs- und SEO-Vorteile von server-seitigem Rendering mit der Interaktivität von client-seitigem JavaScript, aber nur dort, wo man es wirklich braucht.

Fresh integriert Preact, eine leichtgewichtige React-Alternative, für sein Komponentenmodell und bietet JSX-Syntax, Komponentenkomposition und Hooks, aber mit einer kleineren Laufzeitumgebung und besserer Leistung. Der Routing-Ansatz des Frameworks ist dateibasiert, was Next.js als intuitiver als konfigurationsbasiertes Routing bewiesen hat. Wie in Remix reduziert die Kolokation von Datenladen und Komponenten die Komplexität. SvelteKit hatte bewiesen, dass komponentenbasiertes Progressive Enhancement der goldene Mittelweg zwischen client-seitigem und server-seitigem Rendering sein kann — was Freshs Islands-Konzept beeinflusste.


Neue Höhen am Horizont

Deno und Fresh synthetisieren alle Lektionen aus 30 Jahren ServerJS-Erfahrung und bieten meiner Meinung nach derzeit die insgesamt beste Laufzeitumgebung und das beste Web-Framework für Javascript auf dem Server. Natürlich haben andere Laufzeitumgebungen je nach spezifischer Nische eines Projekts ihre legitimen Anwendungsfälle. Schliesslich können wir jetzt aus einer erstaunlich langen Liste verfügbarer Javascript-Laufzeitumgebungen wählen.

Node.js bleibt der Enterprise- und Legacy-Standard mit unübertroffener Ökosystem-Tiefe; Deno glänzt bei Sicherheit, TypeScript-First-Entwicklung und modernen Defaults; Bun dominiert, wenn rohe Geschwindigkeit und All-in-one-Werkzeuge entscheidend sind; Cloudflare Workers und andere WinterCG-Edge-Laufzeitumgebungen brillieren für global verteilte Anwendungen mit geringer Latenz. Für die Einbettung von JavaScript in benutzerdefinierte Anwendungen können Entwickler zwischen V8 (hochleistungsfähig, weit verbreitet), GraalJS (ausgezeichnete Java-Interoperabilität auf der JVM), QuickJS (extrem leichtgewichtig), Boa (reines Rust, experimentell), Hermes (mobiloptimiert) oder SpiderMonkey und JavaScriptCore für spezifische Integrationsanforderungen wählen. Desktop-Apps werden von Electron (Chromium + Node) gut bedient. Das ist eine verrückte Menge an Laufzeitoptionen, an die keine andere Sprache heranreicht. Während Java die ehrenwerte Erwähnung bekommen würde, ist der Zweitplatzierte hinsichtlich der Anzahl der Laufzeitimplementierungen tatsächlich eng mit den Javascript-Umgebungen verwandt: WebAssembly und AssemblyScript.

WebAssembly (WASM) ist ein binäres Instruktionsformat, das für das Laufen mit nahezu nativer Geschwindigkeit in Web-Browsern und JavaScript-Laufzeitumgebungen entwickelt wurde. Es ist kein Ersatz für JavaScript, sondern eine Ergänzung. WASM-Module kompilieren aus Sprachen wie Rust, C++ oder AssemblyScript zu portablem Bytecode, der in einer Sandbox-Umgebung ausgeführt wird. Die Leistungsmerkmale sind dramatisch: Kryptografische Operationen laufen 10-30 Mal schneller, numerische Berechnungen nähern sich nativen C-Geschwindigkeiten, und speicherintensive Operationen vermeiden den Garbage-Collection-Overhead vollständig.

Das Integrationsmuster ist elegant. JavaScript übernimmt Anwendungslogik, Benutzerinteraktion und Orchestrierung. WASM übernimmt die leistungskritischen Operationen, die JavaScript nicht effizient erledigen kann. Eine Web-Anwendung könnte JavaScript für UI-Rendering und State-Management verwenden, während sie die Bildverarbeitung an ein WASM-Modul delegiert, das aus Rust kompiliert wurde. Ein Datenanalyse-Tool könnte JavaScript für die API und Visualisierung verwenden, während es statistische Berechnungen in WASM ausführt. Die Grenze zwischen den beiden ist explizit und beabsichtigt.

AssemblyScript senkte die Hürde für diese Integration erheblich. Vor AssemblyScript bedeutete die Verwendung von WASM, Rust oder C++ zu lernen, ihre Build-Toolchains zu verstehen und das Impedanzproblem zwischen diesen Sprachen und JavaScript zu managen. AssemblyScript bietet eine TypeScript-ähnliche Syntax, die direkt zu WebAssembly kompiliert. JavaScript-Entwickler können Code schreiben, der vertraut aussieht — mit Klassen, Funktionen, Typ-Annotationen — und ihn zu WASM kompilieren, ohne das mentale Modell des JavaScript-Ökosystems zu verlassen. Es ist nicht so leistungsfähig wie handoptimiertes Rust, aber viel schneller als JavaScript und dramatisch zugänglicher.

Kryptografische Operationen sind ein klarer Fall, wo der Rückgriff auf WASM sinnvoll ist. Hashing, Verschlüsselung und Signaturverifizierung sind CPU-intensiv und sicherheitskritisch. Das Ausführen dieser in WASM bietet sowohl Geschwindigkeit als auch Isolation. Bild- und Videoverarbeitung, numerisches Computing und wissenschaftliche Simulationen sind weitere Bereiche, in denen WASM glänzt. Die Leistungssteigerung beträgt oft das 20-fache oder mehr.

In Deno laden WASM-Module mit derselben ES-Modulsyntax wie JavaScript: import { process } from "./image.wasm";. Denos Sicherheitsmodell erstreckt sich auf WASM-Code — Module laufen in derselben Sandbox mit demselben Berechtigungssystem. WASM ist also ein erstklassiger Bürger des Ökosystems, gut auf der Laufzeitebene integriert.


Die nächsten zehn Jahre dürften interessant werden

Nach dreissig Jahren ist ServerJS in der besten Verfassung, die es je hatte. Die Laufzeitumgebungen sind schnell, die Werkzeuge ausgereift, und das Ökosystem konvergiert eher als zu fragmentieren. Was ich am aufmerksamsten beobachte, ist die KI-Schicht, denn Javascript hat echte Vorteile für Anwendungen, die KI-Agenten und Sprachmodelle eng integrieren. Die Eigenschaften, die Javascript für I/O-intensive Web-Server geeignet machten, erweisen sich als ebenso geeignet für die entstehende KI-Anwendungsschicht. Grosse Sprachmodelle antworten als Token-Streams, nicht als einzelne Payloads — und Streaming ist in Javascript seit Nodes frühen Tagen idiomatisch. JSON, das Javascript als nativen Datentyp behandelt, ist die Lingua franca von LLM-APIs. Async/await, das die Sprache jahrelang verfeinert hat, passt natürlich zum Latenzprofil der Modellinferenz.

Während Entwickler Anwendungen aufbauen, die mehrere KI-Modelle orchestrieren, Tool-Aufrufe verketten und Ergebnisse in Echtzeit an Benutzer streamen, wird Javascript zur dominanten Klebesprache des KI-Stacks. Das Vercel AI SDK, LangChain.js und ein wachsendes Ökosystem von ServerJS-nativen KI-Bibliotheken spiegeln das wider. Die Kombination von Denos sicheren Defaults, Freshs Progressive-Enhancement-Modell und reifender Browser-/Edge-Inferenz via WebGPU + Wasm (die jetzt 60–85% der nativen Leistung für quantisierte Modelle auf geeigneter Hardware erreicht) ist besonders überzeugend. Während Python für Kernmodellforschung und schwere Training-Workloads dominant bleibt, ist JavaScript zunehmend die Sprache der KI-Auslieferung — die Schicht, die Modelle in nutzbare, reaktionsschnelle Produkte verwandelt.

Ein weiterer Bereich, der es wert ist, beobachtet zu werden, ist AssemblyScript als deterministisches Kompilierungsziel. Indem es JavaScript/TypeScript auf eine vorhersagbare Teilmenge einschränkt, die zu sauberem WebAssembly kompiliert, bietet es eine potenzielle Brücke, um JavaScript-ähnliche Entwicklererfahrung in Umgebungen zu bringen, die strikte Determinismus fordern — insbesondere für Smart Contracts. Obwohl noch eine Nische, könnte die Kombination aus TypeScript-Vertrautheit, nahezu nativer Leistung und Reproduzierbarkeit interessante neue Grenzen öffnen, wenn Blockchains die AssemblyScript-und-WASM-Kombination für Smart Contracts breiter unterstützen.

Oracles sind eine verwandte und ebenso vielversprechende Richtung. Smart Contracts sind von Natur aus deterministisch und isoliert — sie können die Aussenwelt nicht von sich aus erreichen. Oracles überbrücken diese Lücke, indem sie reale Daten auf die Blockchain bringen: Preise, Wetter, Sportergebnisse, API-Antworten. Projekte wie XEQMLabs und DarkFi erkunden Privacy-First-Oracle-Infrastruktur, bei der der Abfrageinhalt, die Identität des Anfragers und der Zweck standardmässig privat bleiben — was die Nutzung von Oracles viel populärer machen könnte. Ein Oracle-Knoten muss von Web-APIs abrufen, JSON-Antworten parsen und transformieren, Fehler und Randfälle behandeln und ein verifiziertes Ergebnis liefern — das ist eine ServerJS-Stellenbeschreibung. Chainlink Functions macht das explizit: Es führt JavaScript-Code auf einem dezentralisierten Oracle-Netzwerk aus und lässt Entwickler Standard-JS schreiben, das von jedem HTTP-Endpunkt abruft und das Ergebnis on-chain leitet. Das gesamte Web-API-Ökosystem wird Smart Contracts über eine Schicht zugänglich, die Entwickler bereits schreiben können. Die Determinismusanforderung führt zurück zu AssemblyScript: Mehrere Oracle-Knoten müssen unabhängig voneinander zum selben Ergebnis gelangen für den Konsens — was reproduzierbare Ausführung erfordert. AssemblyScripts WASM-kompilierendes TypeScript-Subset bietet Oracle-Logik, die sowohl entwicklerfreundlich als auch nachweislich deterministisch ist. Denos Sandbox-Modell passt auch natürlich zu den Sicherheitsanforderungen von Oracle-Knoten und macht es unkompliziert, genau zu beschränken, auf welche Netzwerk-Endpunkte und Ressourcen der Oracle-Code zugreifen darf.

Während diese Bausteine weiter reifen — bessere GPU-Integration, stärkere Agenten-Beobachtbarkeit und gangbare deterministische Wege wie AssemblyScript — wird JavaScript zunehmend die interaktiven und Orchestrierungsschichten für Smart Contracts, Oracles und KI definieren.

Die nächsten zehn Jahre dürften sehr interessant werden.