Import Maps ile Bağımlılık Yönetimi: Tarayıcı Native Modüller
Import map, modül isimlerini CDN URL'lerine eşleştiren bir tarayıcı özelliği. Node.js çözümleme algoritması, npm install ve bundler olmadan, tek bir JSON bloğuyla CDN'den ESM modülleri yükleyebiliyorsunuz. Hangi proje için mantıklı, nerede yetersiz kalıyor?
Modern JavaScript projelerinin büyük çoğunluğu, bağımlılıkları bir build aracının yönetmesini bekler. Webpack, Rollup veya Vite, node_modules içindeki paketleri alır, birleştirir, chunk'lara böler ve tarayıcıya hazır bir bundle çıkarır. Derleme süresi, config dosyaları, loader'lar, plugin'ler - bunlar proje büyüdükçe sıradan bir gereksinim haline gelir. Ama bu zincir zorunlu değil. Tarayıcılar 2018'den bu yana ES modüllerini (ESM) doğrudan anlıyor; import map (içe aktarma haritası) desteğiyle birlikte ise hangi modül adının hangi URL'e karşılık geldiğini siz belirleyebiliyorsunuz.
Import map, bir JSON bloğu içinde modül isimlerini URL'lere eşleştiren bir tarayıcı özelliği. import { format } from "date-fns" yazdığınızda tarayıcı bu ismi haritada arar, karşılığındaki CDN adresini bulur ve modülü doğrudan oradan çeker. Node.js'in node_modules çözümleme algoritması, npm install, bundler - hiçbiri yok. Yalnızca bir <script type="importmap"> etiketi ve birkaç satır JSON.
Bu yaklaşım her proje için doğru seçim değil. Ağır bağımlılık grafikleri, tree-shaking ihtiyacı veya eski tarayıcı desteği gerektiren projeler için geleneksel bundle pipeline'ı hâlâ daha uygun. Ama belirli koşullar altında, import map hem geliştirme hem üretim ortamını basitleştiren, az config isteyen bir çözüm sunuyor; hangi koşulların bu kararı tutarlı kıldığını anlamak, aracı doğru yerde kullanmak demek.
Tarayıcı ESM Motoru Nasıl Çalışır
<script type="module"> etiketiyle yüklenen bir JavaScript dosyasını tarayıcı, statik analiz ederek tüm import ifadelerini tespit eder. Bu bağımlılıkları paralel HTTP istekleriyle getirir, bağımlı modüllerin kendi bağımlılıklarını da recursive olarak çözer ve modül grafiği tamamlanınca kodu çalıştırır. Bir bundler'ın derleme zamanında yaptığı bu işi tarayıcı runtime'da yapıyor.
Import map bu mekanizmanın üstüne oturur. Çıplak modül tanımlayıcısı, yani göreli yol veya tam URL olmayan bir isim, import map olmadan çalışmaz. import React from "react" yazdığınızda tarayıcı "react" adını nereye bakacağını bilmez; hata verir. Import map bu boşluğu doldurur: isim-URL çiftleri listesi sunar ve tarayıcı tanımlayıcıyı çözerken bu listeye bakar.
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/[email protected]",
"react-dom/client": "https://esm.sh/[email protected]/client"
}
}
</script>
<script type="module">
import { createRoot } from "react-dom/client";
import { useState } from "react";
</script>
Önemli bir kural: <script type="importmap"> bloğu, sayfada herhangi bir modül yüklenmeden önce tanımlanmış olmalı. Standart, sayfada yalnızca bir import map tanımına izin veriyor; birden fazla blok eklediğinizde tarayıcı hata fırlatıyor. Modül, import map tanımlanmadan önce getirilmeye başlandıysa o modül için map geçerli olmuyor.
CDN Seçimi ve ESM Uyumluluğu
Import map ile çalışmanın temel kısıtı şu: CDN'nin ESM formatında paket sunması gerekiyor. npm kayıt defterindeki paketlerin önemli bir bölümü CommonJS (CJS) formatında; tarayıcı bu formatı doğrudan çalıştıramaz. ESM çıktısı veren üç yaygın seçenek var: jsDelivr (+esm son ekiyle), esm.sh ve unpkg.
# jsDelivr - paketin kendi ESM entry'si varsa kullanır
https://cdn.jsdelivr.net/npm/[email protected]/+esm
# esm.sh - CJS paketleri de ESM'e otomatik çevirir
https://esm.sh/[email protected]
# Paketin resmi ESM versiyonunu tercih edin
https://cdn.jsdelivr.net/npm/[email protected]/+esm
esm.sh'ın ayırt edici özelliği, CJS paketleri dönüştürebilmesi. lodash gibi ESM versiyonu olmayan paketler de bu sayede kullanılabilir hale geliyor. Ama dönüşüm her zaman mükemmel değil; özellikle karmaşık dynamic require kalıplarında beklenmedik davranış çıkabiliyor. Paketin resmi ESM versiyonu varsa - date-fns zaten ESM sunuyor, lodash-es tam ESM paketi - dönüşüme güvenmek yerine onu kullanmak daha güvenli.
Sürüm kilitleme burada sizin sorumluluğunuzda. URL içindeki sürüm numarası değiştirilmediği sürece tarayıcı cache'deki eski versiyonu kullanmaya devam eder, bu bir avantaj. Ama güncellemek istediğinizde npm update yerine URL'i elle değiştirmeniz gerekiyor. Az sayıda bağımlılıkla bu yönetilebilir; paket sayısı onlara yaklaştıkça otomasyon ihtiyacı belirginleşiyor.
Import Map Yazım Kuralları
Map'teki girdiler iki tür: tam eşleşme ve yol ön eki. Yol ön eki / ile bitmeli; bu sonrasındaki her şeyin URL'e eklenmesini sağlıyor.
{
"imports": {
"utils/": "/src/utils/",
"components/": "/src/components/",
"react": "https://esm.sh/[email protected]",
"react-dom/client": "https://esm.sh/[email protected]/client",
"date-fns": "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
}
}
import { useState } from "react" tam eşleşmeye düşer. import { createRoot } from "react-dom/client" de tam eşleşme, çünkü "react-dom/client" girdisi ayrıca tanımlı. import Button from "components/Button.js" ise yol ön eki kuralına göre /src/components/Button.js adresine yönlenir. Tarayıcı, birden fazla ön ek eşleşmesi varsa en uzun olanı seçiyor.
Yerel modüller için de import map işe yarıyor. "utils/" girdisiyle /src/utils/ yolunu eşleştirdiğinizde, dosyanın dizin derinliğinden bağımsız olarak her yerden import { formatDate } from "utils/date.js" yazabilirsiniz. Göreli yol hesaplama yok, klasör taşındığında import'ları güncelleme yok.
Geliştirme ve üretim ortamı için farklı URL kullanmak istiyorsanız en temiz yol, sunucu tarafında şablondan üretilen HTML. Her ortam için import map farklı CDN adresleri veya yerel dosya yolları içerebiliyor. Alternatif olarak JavaScript ile dinamik enjeksiyon teknik olarak mümkün, ama modüllerin yüklenmesi öncesinde hazır olması gerektiğinden zamanlama hataları yapılabilir.
Scoped Imports ile Sürüm Çakışması Yönetimi
İki farklı dış paket aynı bağımlılığın farklı sürümlerini istediğinde bundler bunu genellikle her iki sürümü birden paketleyerek çözer. Import map'te bu görevi "scopes" anahtarı üstlenir. Sonucu şöyle okuyun: belirli bir kaynaktan gelen modüller, üst seviye tanım yerine kendi scope'undaki tanımı kullanır.
{
"imports": {
"semver": "https://esm.sh/[email protected]"
},
"scopes": {
"https://esm.sh/[email protected]/": {
"semver": "https://esm.sh/[email protected]"
}
}
}
my-library kaynaklı modüller semver adını aradığında, scope tanımına düşer ve eski sürümü alır. Diğer her şey üst seviyedeki 7.6.0'ı kullanır. Scope belirtimi URL prefix tabanlı, / ile bitmesi zorunlu; aksi halde scope eşleşmez.
Gerçek projede bu ihtiyaç sık çıkmaz. Beşten fazla scope görmeye başladığınızda, import map'i elle yönetmek yerine üretimi otomatikleştiren bir araç ya da bundler'a geçmek daha verimli hale geliyor. Karmaşıklaşan scope ağacı, import map'in avantaj olarak sunduğu basitliği ortadan kaldırıyor.
Tarayıcı Desteği ve Polyfill Seçeneği
Chrome 89, Edge 89, Firefox 108 ve Safari 16.4'ten itibaren import map destekleniyor. Modern tarayıcıların büyük çoğunluğu bu sürümleri çoktan geride bıraktı; hedef kitleniz güncel tarayıcılardan oluşuyorsa polyfill gerekmeyebilir. Internet Explorer tamamen dışarıda; bu arada IE11 desteği gereken bir projede zaten ESM kullanmak da mümkün değil, dolayısıyla import map bu projeksiyon için değerlendirme dışı kalıyor.
Tarayıcı import map'i tanımıyorsa, <script type="importmap"> bloğunu sessizce geçer. Arkasından gelen <script type="module"> içindeki çıplak import tanımlayıcıları çözümlenemez ve sayfa kırılır. es-module-shims kütüphanesi bu farkı kapatır: yaklaşık 8 KB (gzip sonrası) boyutunda ve sayfaya eklendiğinde import map'i kendisi işler.
<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"date-fns": "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
}
}
</script>
<script type="module">
import { format } from "date-fns";
</script>
Modern tarayıcıda polyfill yükleniyor ama tarayıcının native çözümlemesi devreye girdiği için polyfill neredeyse boşa çalışıyor. Eski tarayıcıda tüm modül çözümlemesini devralan polyfill, 8 KB ek ağırlık karşılığında import map özelliğini kullanılabilir kılıyor. Polyfill'in kendisi CDN'den yüklendiği için sonraki ziyaretlerde bu maliyet de cache'e düşüyor.
Hangi Proje Türü için Mantıklı
Prototip ve demo sayfalar en net kullanım alanı. Build adımı olmadan çalışan bir HTML dosyası istiyorsanız, import map doğal seçim. npm init, webpack.config.js, vite.config.js kurulum yükü olmadan, doğrudan CDN'den bağımlılıkları çekip tam modüler JavaScript yazabiliyorsunuz. Deneme hızı yüksek, atıl araç konfigürasyonu sıfır.
Dahili araçlar, dashboard'lar veya az sayıda kullanıcıya açık admin panelleri de iyi uyum sağlıyor. Kullanıcı profili belirli olduğundan eski tarayıcı kaygısı yok; bağımlılıklar nadiren değişiyor ve CDN cache avantajı belirgin: aynı CDN URL'ini başka bir siteden daha önce çeken bir kullanıcı, bu kaynağı ağa göndermeden cache'den alıyor.
Büyük uygulamalar için import map tek başına yeterli değil. CDN'den tam paketi çektiğinizde kullanılmayan export'lar da iner; bundler tree-shaking ile bunu ayıklarken import map ayıklayamıyor. Her modül ayrı bir HTTP isteği anlamına geldiğinden, bağımlılık sayısı arttıkça paralel istek miktarı da artıyor ve bağlantı overhead'i önem kazanıyor. HTTP/2 bunu kısmen hafifletiyor, ama tek iyi optimize edilmiş bundle dosyasının ağ verimliliğini vermiyor.
TypeScript kullanan bir projede import map ek ayar istiyor. TypeScript derleyicisi modül çözümlemesini kendi yapıyor; import map'ten haberi yok. tsconfig.json'da paths ayarını import map ile eşleştirmeniz gerekiyor, aksi halde editörde tip hataları çıkıyor. Bu çift bakım yükü, küçük projeler için tolere edilebilir; orta ve büyük ölçekte ek sürtüşme yaratıyor.
Geliştirme ile Üretim Arasındaki Fark
Geliştirme ortamında avantaj açık: build beklenmez, hot reload karmaşıklığı yok. Dosyayı değiştirin ve yenileyin. Üretimde tablo biraz farklı.
CDN'e bağımlı olmak, dış bağımlılık demek; CDN erişilemez duruma girdiğinde sayfanız da işlevsiz kalıyor. Self-hosted alternatif, CDN modüllerini kendi sunucunuza kopyalamak ve import map URL'lerini bu sunucuya yönlendirmek. Kontrolü elinize alıyorsunuz ama CDN'in küresel cache ağını ve yaygın URL paylaşımının sağladığı cache isabetlerini kaybediyorsunuz.
Import map'i geliştirme ortamında hızlı deneme için kullanmak, üretimde ise bundler'lı pipeline'ı sürdürmek de geçerli bir yaklaşım. İki araç birbirini dışlamıyor; geliştirme hızını import map ile artırırken, üretim çıktısını bundler'ın oluşturduğu optimize paketlere bırakabilirsiniz. Bu kombinasyon özellikle prototipi hızlı doğrulayan, ardından pipeline'a bağlayan ekipler için işe yarıyor.
Import map, "bundle olmadan modern JavaScript" vaadini tarayıcı düzeyinde gerçekleştiriyor. Çıplak modül tanımlayıcılarını CDN URL'lerine bağlayan bu mekanizma sayesinde node_modules ve bundler olmadan tam modüler kod yazılabiliyor. Tarayıcı desteği geniş bir kitleyi kapsıyor; polyfill seçeneğiyle bu kapsam daha da genişletilebiliyor.
Kararı şekillendiren üç değişken var: bağımlılık sayısı, güncelleme sıklığı ve hedef tarayıcı profili. Az sayıda paket, nadiren güncelleme, modern tarayıcı kitlesi: bu üçü bir arada ise import map gereksiz araç katmanından kurtarıyor. Tree-shaking kritik, bağımlılık grafiği derin veya eski tarayıcı desteği zorunluysa bundler hâlâ doğru tercih. İkisi arasındaki sınır net; projenin koordinatları bu sınırın hangi tarafında durduğunu gösterir.