JavaScript'i Karmaşıklıktan Kurtarma Karmaşıklığı
Programlama öğrenmeye başladıktan sonra yazılım dilleri öğrenirken tek bir dilde kalmadım. Basic, Pascal, C derken hep polyglot bir yaklaşımım oldu. Bu profesyonel hayatta beni çok kez iyi noktalara taşıdı. İş hayatımda Microsoft teknolojileri kullanırken yazılım meetuplarında bir araya geldiğim insanlar beni Node.js'in çıkış tarihine kadar PHP, ondan sonra da JavaScript kullanıcısı kimliğim ile tanıdılar. Bugün halen yoğun olarak Go kullansam da tahminen topluluktaki arkadaşlarım 7/24 Deno yazan bir frontendçi olduğumu düşünüyor. Çünkü toplulukta bir JavaScript evangelist'i gibi takılıyorum.
Bu yazı ise açık bir JavaScript ve Deno eleştirisi olacak. İroniktir, 2015'te bir benzerini PHP için yapmıştım, arşivden bulabilirsiniz. Bugün npm'in defalarca güvenlik sorunu yaşadığı, insanların standart kütüphanesizlikten dolayı sarıldığı axios'un supply chain saldırısı yediği bir ortamda bu yazıyı yazmanın uygun olduğunu düşündüm.
Toparlamak gerekirse yazılım alanında gerçekleştirdiğim entelektüel faaliyetlerim genellikle JavaScript'e odaklı. Ve şu anda bir gazeteci/yorumcu edasıyla açık bir fikri takip yapmak için bu yazıyı kaleme almaya başladım.
Deno 1.0 - The Phantom Menace
Herkes gibi benim de ES6/ES2015'ten bu yana JavaScript dünyasına getirdiğim en büyük eleştiri, araçların birbirleriyle uyumsuzluğu oldu. node_modules cehenneminden, sürekli araya giren transpiler'lardan ve compiler'lardan, JavaScript'te dahi yazılmayan onlarca aracın standartları belirlemesinden bıkmıştık: webpack, rollup, vite, turbo, jest, babel, TypeScript... Adeta yerel bir devopsçuluk yapıyorduk sadece projeyi ayağa kaldırmak için.
Kendi adıma bugün Vite + Nitro'nun yaptığı şeyi 2-3 kere baştan keşfetmeye çalıştım 2015'de sey ve 2018'de darty gibi projelerle.
Karmaşaya rağmen topluluğun bir üyesi olarak bu araçların sahiplerinin bize verdikleri bir söz vardı. "Şu anda V8 geriden geliyor, ES2015 standartlarına tüm araçlar uyunca bize gerek dahi kalmayacak", ben de buna inanıyordum. Çünkü JavaScript, birçok dilin aksine bir ekibin ya da bir ismin değil kamunun sahipliğindeydi, her araç gelip geçiyordu önemli olan ilerleyişti. .NET'den konuşuyor olsak Microsoft'un çıkarlarının önceliğinden bahsedebilirdik ama JavaScript "bizim"di işte. Araçların getirdiği çeşitlilik de ekosisteminin genişliğinden kaynaklanıyor — her türlü ihtimali deneyip evrimsel olarak en doğrusunun ayakta kalması kaotik ama canlı bir doğal seçilim oluşturuyordu.
Ryan Dahl ve "10 Things I Regret About Node.js"
Ryan Dahl önce Node.js ile server-side JavaScript'i yaygınlaştırdı. Sonra JavaScript'in yukarıda bahsettiğim karmaşasına, bugün halen Deno'nun web sitesinde slogan olan "Uncomplicate JavaScript" yaklaşımıyla cevap verdi. Yaptıkları bence hâlâ underrated kalıyor. Bugün bile JavaScript'in trademark'ı için hukuki bir mücadele veriyor.
2018'de JSConf EU sahnesine çıkıp "10 Things I Regret About Node.js" konuşmasını yaptığında manifestosu netti: Node.js'i tasarlarken öngöremediği ve ekosisteme darboğaz yaşatan şeyleri sıfırdan rafine etmek. node_modules karmaşası, merkezi olmayan permission modeli, package.json'a sıkı bağımlılık gibi hatalarına karşı retrospektif bir değerlendirmede bulunup "bu sefer doğru yapacağım" dedi ve Deno'yu tanıttı. YouTube kanalımda Deno 1.0'ı incelediğimiz yayın var, ne kadar iyimsermişim 🙈
Çünkü Deno'nun vaatleri açıktı ve ortalama bir geliştiricinin dertlerinin kök sebeplerine reçete gibiydi: TypeScript birinci sınıf vatandaş olacaktı. URL-based import'lar sayesinde merkezi bir paket yöneticisine gerek kalmayacaktı. Granüler permission sistemi güvenliği sağlayacaktı. Web API'lara sırt dayanılacak, temiz, tutarlı, modern bir standart kütüphane sunulacaktı. Go'da, Rust'ta gördüklerimizin JavaScript'te de mümkün olduğuna inanmıştı/inandırtmıştı Ryan. Duru, deterministik, toolchain'in ayak bağı olmadığı bir ekosistem bizi bekliyordu.
Güneşli Günlere Doğru
Deno'nun ilk sürümüyle birlikte bu vaat gerçekleşmeye de başladı. CommonJS'den ESM'e geçtik. Node.js'i bile yıllardır ayak dirediği ESM geçişinde Deno zorladı. Unified toolchain cennet gibiydi. Codebase'e yüklenen eslint, prettier, jest bir gecede kaldırıldı. Deno bir vite ekosistemi kadar, Bun kadar insanlarca takdir edilmedi. Deno bence bir devrimdi, üstelik amaç insanların bilgisayarına girmek dahi değildi. Popülist yaklaşmadılar.
Kendi adıma Deno'yu idealist bulduğum ve kendini çok anlatamadığını düşündüğüm için kendi katılımcı olduğum topluluklarda Deno'nun sesi olmaya çalıştım. Herhalde 10'dan fazla konuşmam ve YouTube yayınım olmuştur. JStanbul yerel meetup grubumuzla birlikte yüzlerce, belki binlerce kişiye Deno'yu anlatmışımdır.
Bu eforlarımı ilettiğim Deno ekibine "bizi official meetup grubu olarak tanıtır mısınız" dediğimde "böyle bir programımız yok ama istersen T-Shirt göndeririz" gibi yanıtlar aldım. Benim derdim merch değil ki, kendi paramla alıyorum :) Ama bu yaklaşıma rağmen fikre inanıyordum, konu kişisel değildi. Sanırım iflah olmaz bir idealist ve Star Trek hayranıyım. Atılgan mürettebatı gibi moderniteyi, daha iyi olmayı aramanın kendisi beni çekiyor. JavaScript ekosistemi için de doğruyu, kullanıcıları için daha iyisini arayan, Ryan gibi arayanları takip ediyordum.
Kendi projelerimde Deno'yu tercih ettim. eser/stack monorepo'mu Deno üzerine kurdum. Formatter, linter, test runner - hepsi built-in ve tutarlı. Node.js ekosisteminde bunların her biri için ayrı bir araç seçip konfigüre etmeniz gerekiyordu. Deno'da her şey kutudan çıkıyordu.
Deno 2.0 - Attack of the Clones
Node.js klonlarının ... pardon Bun'ın 2022'de sahneye çıkışı dengeleri değiştirdi. Bun bugün "vite" gibi bir bundler olarak ortaya çıkmış, 1.0'a giderken rotasını kırıp Node.js uyumluluğunu bozmayan, sadece daha hızlı olmaya odaklanmıştı. Bun, iddiasına göre npm paketlerini sorunsuz çalıştırıyor, Node.js API'larını implement ediyor ve bunu inanılmaz bir performansla yapıyordu. Piyasa bunu ödüllendirdi.
O dönem şirketleşen Deno'nun da cevabı Deno 2 oldu. Ve bu cevap, projenin yönünü temelden değiştirdi.
Teker teker saymayayım package.json desteği geri geldi. node: specifier'ları ile Node.js standart kütüphanesi ve npm paketlerini geriye doğru desteklemek yol haritasına girdi. Bunlar pragmatik, adoption odaklı kararlardı ve ticari açıdan mantıklıydı. Deno artık bir şirket olarak ayakta kalmalıydı ve bunun için kullanıcı tabanını büyütmesi gerekiyordu.
Şunu da teslim etmek gerek: bu runtime rekabetinin ekosisteme katkısı oldu. Yukarıda da değindim, Deno'nun varlığı Node.js'i standartlara uymaya zorladı. Sonra Bun gelip Deno'yu node-compat'a zorladı. Her seferinde kazanan, aslında daha geniş JavaScript ekosistemi oldu. Ama bu süreçte Deno, zorlayan olmaktan zorlanan olmaya geçti.
Dolayısıyla pragmatizmi ve rakiplerce dengelenen, gerçeklik zeminine çekilen bir Deno çıktısını eleştirmiyorum. Deno 1.0 da node-compat moduyla çıkabilirdi. Buradaki eleştirim bu modernizasyonun liderliğinin bırakılmış olmasına. Bunu da bir sonraki bölümde inceleyeceğim. Zira geldiğimiz noktada Deno ve Bun "Node.js klonları".
Deno 3.0 - Revenge of the Node?
Deno 2 ile birlikte fark ettiğim şey şuydu: Deno artık kendi API yüzeyini genişletmek yerine, Node.js'in API yüzeyini implement etmeye odaklanıyordu. Node.js'in maintainer'ları her yeni API eklediğinde — bir logger interface, yeni bir diagnostics API, her neyse... Deno ve Bun arkadan gelip bunu uyumlu hale getirmek zorunda kalıyordu - ki gelecekte de bunu yapmaya devam edecekler. Kısacası Node.js genişliyor, kararları alan Node.js ve diğerleri yalnızca kovalıyor. Ortada JavaScript'in "karmaşadan kurtulması" için yeni bir şey üreten yok. Herkes mevcut ekosistemden o kadar memnun ki amaçları yalnızca kendi runtime'ı kullanılsın, bu yönde pragmatik davranalım istiyor. JavaScript idealizmi, modernizmi öldü.
NPM'in arka arkaya sorun yaşattığı dönemlerde JSR ortaya çıktı, PNPM dışında elini taşın altına koymadı. Ekim'den beri NPM cli'sinin JSR desteği için neredeyse kendi adıma çaba veriyorum: https://github.com/npm/npm-package-arg/pull/214
Geçenlerde de benzer bir pull request deneyimini Deno projesiyle yaşadım. Bilenler bilir Deno. API'larını sevmiyor ve JSR'e bir kütüphane olarak taşınması gerektiğini düşünüyorum/destekliyorum. Yine de node-compat'la Node.js API'larını kullanmaktansa Deno'nun Deno.* API'larını kullanmak daha Deno-doğal bir yaklaşım. Şöyle bir PR açmıştım: https://github.com/denoland/deno/pull/33049
Deno Maintainer'ı tarafından Node.js compatibility layer'ını kullanmam önerildikten sonra ben de PR'daki öneriyi takip edip bunu Issue olarak tartışmaya açmayı denedim. İddiam şuydu: Deno, Node.js'in tasarım hatalarını düzeltmek üzere doğdu, projenin "reel-politik" anlamı bu, varlığının açıklanan sebebi. Deno'nun kendi linter'ında process global'inin kullanımını engelleyen bir kural var. Bugünse ironik bir biçimde maintainer'lar eksik bir API için "node-compat'daki process.argv0'ını kullan" diye konuya yaklaşıyorlar.
Issue'yu da açtım ve resim artık daha netti: https://github.com/denoland/deno/issues/33051
Deno tarafı der ki...
-
no-process-globalslint kuralı bir sonraki sürümde kaldırılacak. Yani Browser'daki JavaScript'le sunucu JavaScript'inin farklılaşmamasını isteyen Ryan'ın ilkeleri artık geçerli değil. Deno'da artık Node.js'inprocessglobal'inin kullanımı yanlış bir pratik değil. -
"We will not be adding new Deno APIs to cover what's already available in
node:*APIs" yani artık Deno'nun standart kütüphanesi Node.js'in standart kütüphanesi -
"So just use
process.argv0" yani artık devrimin sonuna geldik.
Bu ifadeler benim hissiyatımdan öteye geçip artık maintainer'ların yönlendirdiği açıkça kullanıcıları yönlendirdikleri bir strateji. O strateji de bizi modernizasyonun ölümüne götürüyor.

Bu olan biteni Ryan Dahl'ın Deno 3'ün Python 2.7 ve Python 3 geçişinde olduğu "değişikliklerin geldiği"ni, hatta benim de altında "umarım Deno.* API'larından kurtuluyoruzdur, bunların JSR üzerinde olması mükemmel olur" yanıtladığım tweetlerle birlikte okumak isterim.
Olmaması gereken
Revenge of the Sith'deki Padme Amidala (spoiler olmasın ama kendisini bu bölümün sonunda iyi bir şey beklemiyor) nasıl "this is how democracy dies, with thunderous applause" demişse ben de topluluğun bir parçası ve bu modernizasyon için katkıda bulunmaya çabalayan biri olarak o konumda hissediyorum kendimi.
Bu yazıyı yazarken Web API'lara native geçişimiz sağlanmadığı için, halen basit HTTP işleri için dahi insanların kullandığı axios bir supply chain saldırısı aldı. NPM kendi paketlerimi güncellemek için bana yüksek güvenlikli bir banka gibi davranıyor, ama nafile... Onlarca OTP, MFA hiçbir işe yaramıyor. Çünkü yamalı bohçaya dönen bir ekosistemi yara bantlarıyla ayakta tutmaya çalışıyoruz.
Deno'nun orijinal vizyonu tam olarak bunu çözüyordu: Web API'leri birinci sınıf vatandaş yap, gereksiz dependency'lerden kurtul, postinstall script'ler default olarak çalışmasın, permission modeli her şeyi kontrol etsin. Deno dünyasında fetch zaten vardı, sağolsun ki Node.js'de olmasının nedenlerinden biri de Deno'nun bunu sağlamasıydı, axios'a ihtiyaç yoktu, ve bir paket postinstall ile makinenize RAT yükleyemezdi çünkü ağ erişimi explicit permission gerektiriyordu.
Ben Deno 3.0'la da zaten 5 sene önce Ryan'ın hata olarak gördüğü, benim artık içinde bulunmak istemediğim toolchain gerçekliğinde mi yaşamaya devam etmeliyim? Bun ile Deno'nun farkı kalmıyorsa, ben neden Deno'yu destekliyorum?
Şimdi aynı Deno bizi npm ekosistemine geri sürüklüyor. Aynı node_modules, aynı dependency zincirleri... Bugünkü axios saldırısı, Deno'nun ilk günden çözmeye çalıştığı sorunların ne kadar gerçek ve acil olduğunu gösteriyor. Ve ironik olan şu: Deno bu sorunları çözmekten vazgeçip o ekosisteme uyum sağlamaya çalışıyor.
Obi-Wan: "You were the chosen one! It was said that you would destroy the Sith, not join them! Bring balance to the Force, not leave it in darkness!"