JavaScript'ten çeşitli öğe türlerini nasıl içe aktaracağınızı ve paketleyeceğinizi öğrenin.
Bir web uygulaması üzerinde çalıştığınızı varsayalım. Bu durumda, yalnızca JavaScript modülleriyle değil, aynı zamanda diğer her türlü kaynakla (Web Çalışanları da JavaScript'dir ancak normal modül grafiğinin bir parçası değildir), resimler, stil sayfaları, yazı tipleri, WebAssembly modülleri ve diğerleriyle de uğraşmanız gerekebilir.
Bu kaynakların bazılarının referanslarını doğrudan HTML'ye dahil etmek mümkündür ancak genellikle mantıksal olarak yeniden kullanılabilir bileşenlerle birleştirilir. Örneğin, JavaScript bölümüne bağlı özel bir açılır liste, araç çubuğu bileşenine bağlı simge resimleri veya JavaScript yapışkanına bağlı WebAssembly modülü için stil sayfası. Bu gibi durumlarda, kaynakları doğrudan JavaScript modüllerinden referansla almak ve ilgili bileşen yüklendiğinde (veya yüklenirse) dinamik olarak yüklemek daha uygundur.
Ancak büyük projelerin çoğunda, ek optimizasyonlar ve içeriklerin yeniden düzenlenmesini (ör. paketleme ve küçültme) gerçekleştiren derleme sistemleri bulunur. Kodu çalıştırıp yürütmenin sonucunu tahmin edemezler. Ayrıca JavaScript'teki her olası dize değişmez değerini tarayamaz ve bunun bir kaynak URL'si olup olmadığı hakkında tahminde bulunamazlar. Peki JavaScript bileşenleri tarafından yüklenen bu dinamik öğeleri nasıl "görebilir" ve derlemeye dahil edebilirsiniz?
Paketleyicilerde özel içe aktarma işlemleri
Yaygın olarak kullanılan bir yaklaşım, statik içe aktarma söz dizimini yeniden kullanmaktır. Bazı paketleyicilerde biçim, dosya uzantısına göre otomatik olarak algılanabilir. Diğerlerinde ise eklentilerin aşağıdaki örnekteki gibi özel bir URL şeması kullanmasına izin verilir:
// regular JavaScript import
import { loadImg } from './utils.js';
// special "URL imports" for assets
import imageUrl from 'asset-url:./image.png';
import wasmUrl from 'asset-url:./module.wasm';
import workerUrl from 'js-url:./worker.js';
loadImg(imageUrl);
WebAssembly.instantiateStreaming(fetch(wasmUrl));
new Worker(workerUrl);
Paketleyici eklentisi, tanıdığı bir uzantı veya böyle açık bir özel şema (yukarıdaki örnekte asset-url:
ve js-url:
) bulunan bir içe aktarma işlemi bulduğunda, referans verilen öğeyi derleme grafiğine ekler, nihai hedefe kopyalar, öğenin türüne uygun optimizasyonlar gerçekleştirir ve çalışma zamanı boyunca kullanılacak nihai URL'yi döndürür.
Bu yaklaşımın avantajları: JavaScript içe aktarma söz dizimini yeniden kullanmak, tüm URL'lerin statik ve geçerli dosyaya göreli olmasını sağlar. Bu da derleme sistemi için bu tür bağımlılıkları kolayca bulmanızı sağlar.
Ancak bu yöntemin önemli bir dezavantajı vardır: Tarayıcılar bu özel içe aktarma şemalarını veya uzantılarını nasıl işleyeceğini bilmediği için bu tür kodlar doğrudan tarayıcıda çalışamaz. Tüm kodu kontrol ediyorsanız ve geliştirme için zaten bir paketleyici kullanıyorsanız bu sorun olmayabilir. Ancak, en azından geliştirme sırasında, JavaScript modüllerini doğrudan tarayıcıda kullanmak giderek daha yaygın hale geliyor. Küçük bir demo üzerinde çalışan bir kullanıcının, üretimde bile bir paketleyiciye ihtiyacı olmayabilir.
Tarayıcılar ve paketleyiciler için evrensel kalıp
Yeniden kullanılabilir bir bileşen üzerinde çalışıyorsanız bu bileşenin, doğrudan tarayıcıda kullanılsın veya daha büyük bir uygulamanın parçası olarak önceden derlenmiş olsun, her iki ortamda da çalışmasını istersiniz. Çoğu modern paketleyici, JavaScript modüllerinde aşağıdaki kalıbı kabul ederek buna olanak tanır:
new URL('./relative-path', import.meta.url)
Bu kalıp, neredeyse özel bir söz dizimiymiş gibi araçlar tarafından statik olarak algılanabilir. Ancak doğrudan tarayıcıda da çalışan geçerli bir JavaScript ifadesidir.
Bu kalıp kullanıldığında, yukarıdaki örnek şu şekilde yeniden yazılabilir:
// regular JavaScript import
import { loadImg } from './utils.js';
loadImg(new URL('./image.png', import.meta.url));
WebAssembly.instantiateStreaming(
fetch(new URL('./module.wasm', import.meta.url)),
{ /* … */ }
);
new Worker(new URL('./worker.js', import.meta.url));
İşleyiş şekli Bu konuyu ayrıntılı olarak inceleyelim. new URL(...)
kurucusu, ilk bağımsız değişken olarak göreli bir URL alır ve bunu ikinci bağımsız değişken olarak sağlanan mutlak bir URL'ye göre çözer. Bizim durumumuzda ikinci bağımsız değişken, mevcut JavaScript modülünün URL'sini veren import.meta.url
bağımsız değişkenidir. Bu nedenle, ilk bağımsız değişken, bu URL'ye göre herhangi bir yol olabilir.
Dinamik içe aktarma ile benzer avantaj ve dezavantajlara sahiptir. import(...)
'ü import(someUrl)
gibi rastgele ifadelerle kullanmak mümkün olsa da paketleyiciler, derleme zamanında bilinen bir bağımlılığı ön işleme almak ve aynı zamanda dinamik olarak yüklenen kendi parçasını oluşturmak için statik URL'ye sahip bir kalıba özel işlem uygular.
Benzer şekilde, new URL(...)
'ü new URL(relativeUrl, customAbsoluteBase)
gibi rastgele ifadelerle kullanabilirsiniz. Ancak new URL('...', import.meta.url)
kalıbı, paketleyicilerin ön işlem yapması ve ana JavaScript'in yanına bir bağımlılık eklemesi için net bir sinyaldir.
Belirsiz göreli URL'ler
Paketleyicilerin neden diğer yaygın kalıpları (ör. new URL
sarmalayıcıları olmadan fetch('./module.wasm')
) algılayamadığını merak ediyor olabilirsiniz.
Bunun nedeni, içe aktarma ifadelerinden farklı olarak tüm dinamik isteklerin geçerli JavaScript dosyasına göre değil, dokümanın kendisine göre çözülmesidir. Aşağıdaki yapıya sahip olduğunuzu varsayalım:
index.html
:
html <script src="src/main.js" type="module"></script>
src/
main.js
module.wasm
module.wasm
dosyasını main.js
dosyasından yüklemek istiyorsanız fetch('./module.wasm')
gibi göreli bir yol kullanmak cazip gelebilir.
Ancak fetch
, yürütüldüğü JavaScript dosyasının URL'sini bilmez. Bunun yerine, URL'leri dokümana göre çözer. Bunun sonucunda fetch('./module.wasm')
, istenen http://example.com/src/module.wasm
yerine http://example.com/module.wasm
öğesini yüklemeye çalışır ve başarısız olur (veya daha da kötüsü, amaçladığınızdan farklı bir kaynağı sessizce yükler).
Göreli URL'yi new URL('...', import.meta.url)
içine sarmalayarak bu sorunu önleyebilir ve sağlanan URL'nin, herhangi bir yükleyiciye iletilmeden önce geçerli JavaScript modülünün URL'sine göre çözülmesini sağlayabilirsiniz (import.meta.url
).
fetch('./module.wasm')
yerine fetch(new URL('./module.wasm', import.meta.url))
yazarsanız beklenen WebAssembly modülü başarıyla yüklenir ve paketleyicilere derleme sırasında bu göreli yolları bulmaları için bir yol sağlanır.
Araç desteği
Paketleyiciler
Aşağıdaki paketleyiciler new URL
şemasını zaten desteklemektedir:
- Web Paketi v5
- Toplama (Genel öğeler için @web/rollup-plugin-import-meta-assets ve özellikle İşçiler için @surma/rollup-plugin-off-main-thread eklentileri aracılığıyla elde edilir.)
- Parcel v2 (beta)
- Vite
WebAssembly
WebAssembly ile çalışırken genellikle Wasm modülünü elle yüklemek yerine araç zincirinin yayınladığı JavaScript yapışkanını içe aktarırsınız. Aşağıdaki araç zincirleri, altında açıklanan new URL(...)
kalıbını sizin için oluşturabilir.
Emscripten aracılığıyla C/C++
Emscripten'i kullanırken aşağıdaki seçeneklerden birini kullanarak JavaScript yapıştırıcısını normal bir komut dosyası yerine ES6 modülü olarak yayınlamasını isteyebilirsiniz:
$ emcc input.cpp -o output.mjs
## or, if you don't want to use .mjs extension
$ emcc input.cpp -o output.js -s EXPORT_ES6
Bu seçenek kullanılırken çıkış, gelişmiş altındaki new URL(..., import.meta.url)
kalıbını kullanır. Böylece paketleyiciler, ilişkili Wasm dosyasını otomatik olarak bulabilir.
Bu seçeneği, -pthread
işareti ekleyerek WebAssembly iş parçacıklarında da kullanabilirsiniz:
$ emcc input.cpp -o output.mjs -pthread
## or, if you don't want to use .mjs extension
$ emcc input.cpp -o output.js -s EXPORT_ES6 -pthread
Bu durumda, oluşturulan Web İşçisi aynı şekilde eklenecek ve aynı zamanda paketleyiciler ve tarayıcılar tarafından bulunabilir.
wasm-pack/wasm-bindgen aracılığıyla Rust
WebAssembly için birincil Rust araç zinciri olan wasm-pack'in de çeşitli çıkış modları vardır.
Varsayılan olarak, WebAssembly ESM entegrasyon önerisine dayalı bir JavaScript modülü yayınlar. Bu öneri, yazının yazıldığı sırada hâlâ deneme aşamasındadır ve çıkış yalnızca Webpack ile birlikte kullanıldığında çalışır.
Bunun yerine, wasm-pack'ten --target web
aracılığıyla tarayıcı uyumlu bir ES6 modülü yayınlamasını isteyebilirsiniz:
$ wasm-pack build --target web
Çıkışta açıklanan new URL(..., import.meta.url)
kalıbı kullanılır ve Wasm dosyası, paketleyiciler tarafından da otomatik olarak bulunur.
Rust ile WebAssembly iş parçacıklarını kullanmak istiyorsanız hikaye biraz daha karmaşıktır. Daha fazla bilgi edinmek için kılavuzun ilgili bölümüne göz atın.
Özet olarak, istediğiniz iş parçacığı API'lerini kullanamazsınız. Ancak Rayon kullanıyorsanız Web'de çalışan oluşturabilmesi için wasm-bindgen-rayon bağdaştırıcısı ile birleştirebilirsiniz. wasm-bindgen-rayon tarafından kullanılan JavaScript yapıştırıcısı, new URL(...)
kalıbını da içerir. Bu nedenle, işçiler de keşfedilebilir ve paketleyiciler tarafından dahil edilebilir.
Gelecekte kullanıma sunulacak özellikler
import.meta.resolve
Özel bir import.meta.resolve(...)
görüşmesi, gelecekte yapılacak olası bir iyileştirmedir. Bu sayede, ek parametreler olmadan belirteci geçerli modüle göre daha basit bir şekilde çözebilirsiniz:
new URL('...', import.meta.url)
await import.meta.resolve('...')
Ayrıca import
ile aynı modül çözümleme sisteminden geçeceği için içe aktarma haritaları ve özel çözümleyicilerle daha iyi entegre olur. URL
gibi çalışma zamanındaki API'lere bağlı olmayan statik bir söz dizimi olduğundan, paketleyiciler için de daha güçlü bir sinyal olacaktır.
import.meta.resolve
, Node.js'te deneme olarak uygulanmış olsa da web'de nasıl çalışması gerektiğiyle ilgili bazı çözüme ulaştırılmamış sorular hâlâ var.
Onayları içe aktarma
İçe aktarma iddiaları, ECMAScript modülleri dışındaki türlerin içe aktarılmasına olanak tanıyan yeni bir özelliktir. Şu anda yalnızca JSON ile sınırlıdır:
foo.json:
{ "answer": 42 }
main.mjs:
import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42
Paketleyiciler tarafından da kullanılabilir ve şu anda new URL
kalıbıyla kapsanan kullanım alanlarını değiştirebilirler ancak içe aktarma beyanlarında türler her duruma göre eklenir. Şu anda yalnızca JSON'u kapsayan bu modüller yakında CSS modüllerini de içerecek. Ancak diğer öğe türleri için daha genel bir çözüme ihtiyaç duyulacak.
Bu özellik hakkında daha fazla bilgi edinmek için v8.dev özellik açıklamalarına göz atın.
Sonuç
Gördüğünüz gibi, web'e JavaScript dışındaki kaynakları dahil etmenin çeşitli yolları vardır ancak bunların çeşitli dezavantajları vardır ve çeşitli araç zincirlerinde çalışmaz. Gelecekteki teklifler, bu tür öğeleri özel söz dizimi ile içe aktarmamıza olanak tanıyabilir ancak henüz bu aşamaya gelmedik.
O zamana kadar new URL(..., import.meta.url)
kalıbı, tarayıcılarda, çeşitli paketleyicilerde ve WebAssembly araç zincirlerinde halihazırda çalışan en umut verici çözümdür.