Cloudflare Workers'ın modül kayıt defteri Node.js uyumluluğu için yeniden yazıldı
Cloudflare, workerd'deki modül kayıt defterini daha hızlı, standartlara daha uygun ve Node.js ile daha uyumlu hale getirmek için yeniden yazdı. Node.js API'leri varsayılan olarak etkinleştirildi, sıkıştırılmış paket boyutu sınırı kaldırıldı ve tüm planlarda 64 MiB'a kadar uygulama dağıtımı mümkün hale geldi. Yeni kayıt defteri new_module_registry bayrağıyla kullanılabiliyor; URL tabanlı belirteçler, import.meta desteği, tembel derleme, paylaşılan önbellek ve daha tutarlı hatalar sunuyor.
Cloudflare Workers'ta modül kayıt defteri yeniden yazıldı
Cloudflare, Workers çalışma zamanının açık kaynak çekirdek bileşeni workerd içindeki modül kayıt defterini yeniden yazdı. Cloudflare Blog'a göre yeni uygulama daha hızlı, standartlara daha uygun ve Node.js'in modül kayıt defteriyle daha uyumlu. Çalışma zamanında tüm bu modül işlemlerini yöneten sisteme modül kayıt defteri deniyor; ESM, CommonJS ve WebAssembly ise Worker kodunda içe aktarılabilen modül türleri arasında yer alıyor.
Cloudflare son yıllarda Node.js çalışma zamanı API'lerine yönelik desteğini genişletti. Blog yazısına göre Workers çalışma zamanı artık sunucusuz bağlamda kullanılabilecek kararlı Node.js API'lerini destekliyor ve bu API'ler varsayılan olarak etkin. Ayrıca sıkıştırılmış paket boyutu sınırı kaldırıldı; Cloudflare artık tüm planlarda 64 MiB'a kadar Node.js uygulamasının dağıtılmasına izin veriyor.
Yeni kayıt defteri nasıl etkinleştirilir?
Yeni modül kayıt defteri, Worker'da new_module_registry uyumluluk bayrağı etkinleştirilerek kullanılabiliyor. Blog yazısına göre bu bayrak için henüz varsayılan bir açılma tarihi yok; bu nedenle eski ya da yeni fark etmeksizin Worker'lar için otomatik olarak devreye girmiyor ve bayrağın açıkça eklenmesi gerekiyor.
Bayrak etkinleştirildiğinde import.meta.url, import.meta.main ve import.meta.resolve() çalışıyor. Modül belirteçleri sorgu dizeleri ve parçalar dahil gerçek URL'ler olarak ayrıştırılıp çözümleniyor. node: yerleşikleri nasıl erişilirse erişilsin aynı modül örneğine çözümleniyor. İçe aktarma nitelikleri with { type: 'json' } sözdizimiyle doğrulanıyor. Bir ES modülü üzerindeki require() Node.js'in require(esm) kurallarını izliyor. Hatalar, hangi yükleme yolu tetiklediğinden bağımsız olarak tutarlı sınıflar ve mesajlar kullanıyor. Modüller ilk içe aktarıldığında, statik veya dinamik olarak, tembel derleniyor. WebAssembly modülleri de kaynak fazı içe aktarımlarını destekliyor.
Worker kodu nasıl paketleniyor?
Bir Worker Cloudflare'e dağıtıldığında wrangler veya Vite, Worker'ın kodunu bir ya da birden çok modüle paketliyor; bunlar wrangler deploy çalıştırıldığında Cloudflare'e yükleniyor. Varsayılan olarak Wrangler bu kodun neredeyse tamamını tek bir modül betiğinde paketliyor ve arka planda esbuild çalıştırıyor. Cloudflare Vite eklentisi kullanıldığında ise Vite 8 kodu esbuild yerine Rolldown ile paketliyor. Rolldown içe aktarımları ve npm bağımlılıklarını çözümlüyor, gerektiğinde CommonJS'i ESM'e dönüştürüyor ve bir giriş modülü ile kod bölme yoluyla oluşturulan ek parçalar üretiyor.
Bir Worker'da bir Node.js API'si içe aktarıldığında varsayılan olarak workerd içine yerleştirilmiş bir modül içe aktarılıyor; bu kod polyfill olarak paketlenmiyor. Wasm, metin ve ikili modüller de Workers çalışma zamanına ayrı dosyalar olarak sağlanıyor.
Eski kayıt defterinin sınırları
Orijinal kayıt defteri belirteçleri URL olarak değil, dosya sistemi tarzı yollar olarak çözümlüyordu. Bu nedenle import.meta.url'ü uygulamak için temiz bir yol yoktu. Göreli içe aktarımlar new URL() ile aynı çözümleme kurallarını izlemiyordu. node: ve cloudflare: gibi protokoller özel durumlu dize önekleri olarak ele alınıyordu. Orijinal kayıt defteri ayrıca belirli bir modül hiç içe aktarılmasa bile tüm Worker paketini önceden derliyordu ve her V8 izolatı için ayrı, özel bir her şeyin kopyasını tutuyordu. Cloudflare, yükü CPU çekirdeklerine dağıtmak için aynı Worker'ın birden çok V8 izolat replikasını çalıştırıyor.
Yeni kayıt defteri belirteç biçimi olarak URL'lerden başlıyor; tembelliği ve önbellek paylaşımını ilk günden tasarıma dahil ediyor. Mevcut kayıt defteri uygulaması kaldırılmıyor ve şu anda dağıtılmış Worker'lar her zamanki gibi çalışmaya devam edecek.
import.meta davranışı
import.meta.main yalnızca Worker'ın giriş noktası olarak yapılandırılmış modül için true değerini alıyor; diğer tüm modüller false alıyor. import.meta.resolve() bir belirteci içe aktarmadan geçerli modüle göre çözümlüyor. Bu işlev Node.js ve tarayıcılarda olduğu gibi saf bir dize dönüşümü; çözümlenen URL'nin gerçek bir modüle karşılık gelip gelmediğini kontrol etmiyor. Hiç URL olarak ayrıştırılamayan bir belirteç için null döndürmek yerine TypeError fırlatıyor.
Blog yazısına göre import.meta.resolve() yüzde kodlamasını new URL() ile aynı şekilde normalleştiriyor ancak zaten yüzde kodlanmış karakterleri çözmüyor. Göreli içe aktarımlar artık new URL(specifier, base) ile aynı şekilde çözümleniyor. Tam URL'ler de yalnızca göreli yollar değil, belirteç olarak çalışıyor.
Tarayıcıların kullandığı modül kimliği kurallarına göre farklı sorgu dizesi veya parçaya sahip bir belirteç, aynı kaynak koda işaret etse bile gerçekten farklı bir modül örneği olarak işleniyor. Aynı belirteci aynı sorgu dizesiyle tekrar içe aktarmak yine aynı örneği veriyor.
İçe aktarma nitelikleri ve require(esm)
Orijinal modül kayıt defteri uygulaması, spesifikasyonu ihlal ederek içe aktarma niteliklerini sessizce yok sayıyordu. Şu anda etkin olan tek içe aktarma niteliği türü json, çünkü ilgili TC39 önerilerinden Stage 4'e ulaşan tek öneri bu. text ve bytes tanınıyor ancak özel bir hatayla reddediliyor. Artık type dışında herhangi bir nitelik anahtarı da yok sayılmak yerine kesin bir hata oluyor.
ES modülü olan bir şey require() edildiğinde kayıt defteri Node.js'in require(esm) davranışını izliyor. Modülde 'module.exports' adlı bir dize adlı dışa aktarım varsa require() bu değeri döndürüyor; aksi takdirde modülün ad alanı nesnesini döndürüyor. workerd'ün kendi node: yerleşikleri, CommonJS tarzı bir API'yi varsayılan dışa aktarımda saran ES modülleri olarak uygulanmış durumda.
require() edilen modülde veya modül grafiğindeki herhangi bir şeyde üst düzey await varsa require() bloke etmek ya da yarım kalmış bir şey döndürmek yerine hata fırlatıyor. Bu, Node.js'in kendi ERR_REQUIRE_ASYNC_MODULE kısıtlamasıyla eşleşiyor ve üst düzey await kontrolü içe aktarma sırasından bağımsız olarak geçerli. Node.js'in require(esm) desteğinden önce gelen bir paketleyicinin çıktısını require() ediyorsanız ve bu çıktı truthy bir __cjsUnwrapDefault dışa aktarımı belirteci ayarlıyorsa, bu her iki kuralın da önüne geçerek varsayılan dışa aktarımı döndürüyor.
Hata tutarlılığı ve WebAssembly kaynak fazı
Çözümleme statik içe aktarma, dinamik import() veya require() yoluyla başarısız olsun, aynı sınıfta ve aynı mesaj şeklinde hata alınıyor. "Module not found" düz bir Error; hiç URL olarak ayrıştırılamayan bir belirteç ise Node.js'in kendi ERR_INVALID_MODULE_SPECIFIER hatasıyla eşleşen bir TypeError. V8'in çözemediği döngüsel bir bağımlılık da her zaman düz bir Error oluyor, asla TypeError değil.
Artık derlenmiş ancak örneklenmemiş bir WebAssembly modülü biçimi kaynak fazı içe aktarımları kullanılarak doğrudan içe aktarılabiliyor. Kaynak fazı içe aktarımları dilin yeni bir özelliği olduğundan şu anda yalnızca WebAssembly için çalışıyor. Bu içe aktarımları başka herhangi bir modül türünde denemek, Node.js ve diğer çalışma zamanlarının davranışıyla eşleşen bir SyntaxError fırlatıyor.