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 menünün stil sayfası, araç çubuğu bileşenine bağlı simge resimleri veya JavaScript yapıştırıcısına bağlı WebAssembly modülü. 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 yaklaşımlardan biri, 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);
Bir paketleyici eklentisi, tanıdığı bir uzantıya veya bu tür bir açık özel şemaya (yukarıdaki örnekte asset-url:
ve js-url:
) sahip bir içe aktarma bulduğunda, başvurulan öğeyi derleme grafiğine ekler, nihai hedefe kopyalar, öğenin türü için geçerli optimizasyonları gerçekleştirir ve çalışma zamanında 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 yaşamayabilirsiniz. Ancak, en azından geliştirme sırasındaki karmaşıklığı azaltmak için 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ına ayırmak için statik URL'ye sahip bir kalıba özel işlem uygular.import('./some-static-url.js')
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 bir göreli 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. Sonuç olarak fetch('./module.wasm')
, amaçlanan http://example.com/src/module.wasm
yerine http://example.com/module.wasm
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 destekliyor:
- Webpack 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ü manuel olarak yüklemezsiniz. Bunun yerine, araç zinciri tarafından yayınlanan JavaScript yapıştırıcısını içe aktarırsınız. Aşağıdaki araç zincirleri, açıklanan new URL(...)
desenini sizin için arka planda yayınlayabilir.
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ıldığında, paketleyicilerin ilişkili Wasm dosyasını otomatik olarak bulabilmesi için çıkışta new URL(..., import.meta.url)
kalıbı kullanılır.
Bu seçeneği, -pthread
işareti ekleyerek WebAssembly iş parçacıkları ile de 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 İşleyici aynı şekilde dahil edilir ve hem paketleyiciler hem de 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 teklif, 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.
WebAssembly ile Rust'ta iş parçacıkları kullanmak istiyorsanız durum 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 bunu wasm-bindgen-rayon bağdaştırıcısı ile birleştirerek Web'de çalışan oluşturabilirsiniz. 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 paketleyiciler tarafından bulunabilir ve dahil edilebilir.
Gelecekteki özellikler
import.meta.resolve
Özel bir import.meta.resolve(...)
görüşmesi, gelecekte yapılacak olası bir iyileştirmedir. Bu, ek parametreler olmadan belirteci geçerli modüle göre daha basit bir şekilde çözmenize olanak tanır:
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ı API'lerine bağlı olmayan statik bir söz dizimi olduğundan paketleyiciler için de daha güçlü bir sinyal olur.
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.
İddiaları 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üm gerekecek.
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 öneriler, 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.