Bedankt, Deno

Ryan Dahl kondigde vorige week aan dat het Deno-team zich aansluit bij Cloudflare. De Deno-runtime zal het komende jaar worden afgebouwd, JSR blijft draaien op de infrastructuur van Cloudflare, en de nieuwe taak van het team is, volgens de post van Cloudflare, het samenvoegen van Deno's celld met workerd.

De aankondiging kwam precies twee jaar nadat Deno 2 werd uitgebracht. Bun sloot tien maanden geleden al aan bij Anthropic. De verleidelijke conclusie is dat het experiment met alternatieve runtimes is afgelopen en dat Node standaard heeft gewonnen. In werkelijkheid heeft het ecosysteem de voordelen gekregen waar Deno voor pleitte, alleen niet vanuit Deno zelf.

Concurrentie is essentieel voor een gezond ecosysteem

Helaas heeft Deno in productie nooit echt een kritische massa bereikt; Node is in 2026 nog net zo dominant als in 2018. Wat Deno wel heeft bereikt, is het revitaliseren en onder druk zetten van de gevestigde orde, en die druk liep niet slechts één kant op.

Deno begon als een radicale breuk. Ryan stond in 2018 op tijdens JSConf EU en somde op wat hij anders zou doen dan Node: deny-by-default permissies, TypeScript als eersteklas input, web-standard API's op de server, uitsluitend ES-modules en één enkel uitvoerbaar bestand. En, heel bewust: geen npm, geen package.json en geen node_modules. Afhankelijkheden waren URL's. De slides waren duidelijk: "Er is geen noodzaak om afhankelijkheden op te sommen."

Velen van ons, die jarenlang met npm en Node hadden gewerkt, waren sceptisch over dat laatste deel (niet over de permissies of TypeScript, die overduidelijk goed waren) en gaven dat destijds ook aan. We hadden dit eerder meegemaakt in het tijdperk van hotlinking, waarin elke pagina jquery.min.js van de server van iemand anders trok en kapot ging zodra het bestand verhuisde of gecompromitteerd werd. Een URL vertelt je waar iets was, niet wat het is. Zodra je erop wilt vertrouwen dat de bytes die je hebt getest ook de bytes zijn die je verzendt, heb je een integriteits-hash nodig en een plek om deze vast te leggen, waarmee je in feite het lockfile opnieuw hebt uitgevonden. Matteo Collina van de Node TSC zei in 2020 dat "er een nieuw left-pad incident op de loer ligt, maar dat er ditmaal geen 'centrale entiteit' is die het kan oplossen." Een deel van dit scepticisme was defensief, maar bleek uiteindelijk terecht.

Deno 1.0 verscheen in 2020 volgens de oorspronkelijke voorwaarden, om vervolgens de volgende vier jaar deze voorwaarden stuk voor stuk in te trekken. Eerst de deps.ts conventie: één bestand dat elke URL opnieuw exporteerde, wat in feite een manifest was onder een andere naam. Daarna lock.json, een integriteits-hash per URL: een lockfile. Daarna import maps, zodat men weer bare specifiers kon schrijven. De huidige documentatie van Deno raadt registries aan boven HTTPS-imports, omdat die laatste "kunnen verschuiven naar verschillende versies over bestanden heen" en men moet "vertrouwen op de host die ze serveert." Dit waren precies de bezwaren uit 2018, nu geformuleerd als richtlijnen.

Toen verscheen Bun. Bun 0.1 werd in juli 2022 uitgebracht met een tegenovergestelde weddenschap: herbouw de toolchain vanaf nul voor snelheid, maar draai bestaande npm-pakketten, lees package.json en fungeer als een directe vervanger voor Node. Ongeveer zes weken later publiceerde Deno "Big Changes Ahead for Deno", waarin npm-specifiers werden aangekondigd, een toezegging werd gedaan om 80–90% van de npm-pakketten binnen drie maanden werkend te krijgen, en een nieuw doel werd gesteld: "de snelste JavaScript-runtime worden." Deno schreef deze koerswijziging toe aan feedback van gebruikers; de timing suggereert dat Bun een groot aandeel had. Deno 2, in oktober 2024, voltooide dit proces met volledige Node- en npm-compatibiliteit. Vier jaar nadat het was gelanceerd als het alternatief voor het Node-ecosysteem, was Deno een andere manier geworden om datzelfde ecosysteem te draaien.

Dat was de juiste beslissing, maar het was ook een erkenning dat het ecosysteem niet overnacht een decennium aan afhankelijkheden zou verlaten. Cloudflare's workerd, dat in 2022 open source werd gemaakt, ontwikkelde om dezelfde reden een eigen Node-compatibiliteitslaag: daar zitten immers de pakketten.

Node moest uiteindelijk reageren. Sinds 2018 heeft het een globale fetch, WebSocket, Web Streams en Web Crypto geïmplementeerd; een stabiele test runner en --watch; --env-file en single-executable builds; een stabiel permissiemodel; standaard type stripping; node:sqlite; en require(esm). De bijdragers van Node (vaak vrijwilligers) hebben het zware werk verricht om te luisteren en grote wijzigingen door te voeren, en zij verdienen daar veel credits voor. Ik geloof echter niet dat dit op dezelfde tijdlijn zou zijn gebeurd zonder dat drie andere runtimes veel van deze ideeën als eerste introduceerden. Een volwassen open source-project heeft zelden een tekort aan goede ideeën; het heeft een gebrek aan urgentie, en concurrenten leveren die urgentie.

Het werk aan standaarden kwam voort uit diezelfde druk: WinterCG werd in 2022 gevormd omdat verschillende serieuze runtimes de vraag "wat is de minimale gemeenschappelijke API?" praktisch maakten. Nu, als Ecma TC55, zou het moeten overleven, ongeacht welke runtimes zich vervolgens consolideren.

Package managers en registries

Gedurende het grootste deel van zijn geschiedenis was het JavaScript-pakketecosysteem een monocultuur: één client, één registry, en een API-contract dat zo informeel was dat het effectief vijftien jaar lang ongedocumenteerd bleef. Vrijwel elke verbetering sindsdien kwam voort uit iemand die weigerde de status quo te accepteren.

  • Yarn (2016) stond erop dat installaties deterministisch moesten zijn en gaf ons de lockfile, workspaces en een offline cache; npm 5 introduceerde het volgende jaar package-lock.json.
  • pnpm liet zien dat node_modules niet honderden megabytes aan gedupliceerde bestanden hoefde te zijn.
  • Bun maakte installeren zo snel dat de gevestigde partijen hun eigen prestaties moesten herzien.
  • Deno betoogde dat je de broncode van een afhankelijkheid zou moeten kunnen lezen voordat je deze overneemt. Hoewel URL-imports niet standhielden, deed dat idee dat wel.

Het duidelijkste recente voorbeeld zijn de install scripts. pnpm en Bun stopten met het standaard uitvoeren hiervan; vlt deed dat nooit. Dit argument was gedurende mijn hele tijd bij npm intern aangevoerd, zonder dat het ooit de standaard werd. In juni van dit jaar, na een reeks supply-chain worms, kondigde GitHub aan dat npm 12 deze scripts ook niet meer standaard zou uitvoeren. Er was niets veranderd aan het idee zelf in al die jaren; er waren simpelweg genoeg andere package managers die het hadden geïmplementeerd waardoor npm het niet langer als radicaal kon beschouwen. De druk werkt twee kanten op: wij hebben catalogi overgenomen van pnpm, en pnpm's namedRegistries hebben onze named-registry aliases overgenomen.

Aan de kant van de registries verdient JSR erkenning. Het maakte andere weddenschappen dan wij: publiceer TypeScript-broncode, vereis ESM en genereer documentatie uit code. We waren het niet eens met delen daarvan, maar we voegden toch jsr: ondersteuning toe aan vlt omdat onze gebruikers daarom vroegen. Het overleeft de afbouw omdat het, voor zover ik kan zien, goedkoop is om te draaien, de API gedocumenteerd is en Cloudflare een reden had om het in stand te houden.

Wat verandert er nu?

De twee populairste onafhankelijke, door durfkapitaal gefinancierde JavaScript-runtimes leven nu in bedrijven wiens kernactiviteit iets anders is. Jarred Sumner zei dat de relatie van het Bun-team met het Claude Code-team "waarschijnlijk vergelijkbaar zal zijn met de relatie tussen Google Chrome en V8," met meer onafhankelijkheid. De post van Cloudflare maakt duidelijk dat de roadmap van workerd die van Cloudflare is. Dat is hoe JavaScriptCore en V8 altijd zijn gefinancierd, maar het is nog steeds een verandering.

Of er een community-fork van de Deno-runtime ontstaat, zal ons vertellen of het ecosysteem een secure-by-default, standards-first runtime genoeg waardeert om te betalen voor een versie die niet aan een cloud-provider is gekoppeld. En de integratie van celld in workerd biedt nieuwe opties op een laag waar dat voorheen nauwelijks mogelijk was: een zelf-hostbare, open implementatie van Workers & Durable Objects, geleid door Ryan en Bert Belder.

JavaScript heeft verzekeringen op bijna elk niveau: drie productie-engines, diverse runtimes en diverse package managers. Wanneer één laag consolideert, zoals nu gebeurt bij de runtime-laag, blijven de andere overeind. De onrust die dit mogelijk maakt, is ook waar mensen het meest over klagen. "JavaScript-moeheid" is al tien jaar een lopende grap: elke week een nieuw framework, elke maand een nieuwe bundler en nóg een package manager (waar ik me bewust van ben dat ik aan heb bijgedragen). Maar niemand hoefde Node te verlaten om fetch, een permissiemodel of TypeScript zonder build-stap te krijgen, en niemand hoeft meer van package manager te wisselen om install-scripts standaard uitgeschakeld te hebben. Deze zaken verschenen in de tools die mensen al gebruikten, omdat iemand anders ze als eerste implementeerde en de gevestigde partij moest antwoorden. De projecten wonnen niet altijd, maar de ideeën meestal wel, en eindgebruikers profiteerden in beide gevallen.

Aan Ryan, Bert, Bartek en de rest van het Deno-team (verleden en heden): jullie gezamenlijke inspanningen hebben ons gedeelde ecosysteem verrijkt. Bedankt!