Uygulamalarımız büyüdükçe, onu dosyalar halinde bölmek isteriz. Bu dosyalar modül olarak isimlendirilir. Bir modül genellikle bir sınıf ya da kullanıÅlı fonksiyonları barındıran bir kütüphane içerir.
Uzun bir süredir JavaScriptâte dil seviyesinde bir modül sözdizimi bulunmuyordu. Bu bir sorun deÄildi, çünkü baÅlangıçta program parçaları (scripts) küçük ve basitti. Dolayısıyla modüler yapıya ihtiyaç yoktu.
Ancak zamanla yazılan programlar karmaÅıklaÅınca, JavaScript topluluÄu kodu modüller Åeklinde organize etmenin çeÅitli yollarını buldu.
ÃrneÄin:
- AMD â en eski modül sistemlerinden biri, baÅlangıçta require.js kütüphanesince gerçekleÅtirildi.
- CommonJS â Node.js sunucusu için yaratılan modül sistemi.
- UMD â geniÅ kapsamlı bir modül sistemi, AMD ve CommonJS ile uyumlu.
Artık tüm bu sistemlerin kullanımı yavaÅ yavaÅ azalmaktadır ancak yine de eski programlarda karÅımıza çıkabilmektedirler. Dil seviyesindeki modül sistemi 2015 yılında standartlaÅtı ve o zamandan beri kademeli olarak evirilip, bugün baÅlıca tarayıcılar ve Node.js tarafından desteklenir hale geldi.
Modül nedir?
Modül sadece bir dosyadır, tek bir program. Bu kadar basit.
export ve import yönergeleri modüller arasında iÅlevselliÄin yer deÄiÅtirmesini saÄlar:
exportanahtar kelimesi tanımlandıkları dosyanın dıÅında da ulaÅılmak istenen deÄiÅken ve fonksiyonları etiketler.importbaÅka modüldeki deÄiÅken ve fonksiyonların içeri aktarılmasına imkan tanır.
ÃrneÄin, bir fonksiyonu dıÅarı aktaran bir sayHi.js dosyası varsa:
// ð sayHi.js
export function sayHi(user) {
alert(`Merhaba, ${user}!`);
}
â¦Daha sonra baÅka bir dosya bunu içeri aktarıp, kullanabilir:
// ð main.js
import {sayHi} from './sayHi.js';
alert(sayHi); // fonksiyon...
sayHi('John'); // Merhaba, John!
Bu derste dilin kendisine odaklanacaÄız, ama tarayıcıyı gösterim ortamı olarak kullanacaÄımız için modüllerin tarayıcıda nasıl çalıÅtıÄına bakalım.
Modülleri kullanabilmek için, <script> etiketinin type niteliÄini <script type="module">, Åeklinde ayarlamalıyız:
export function sayHi(user) {
return `Hello, ${user}!`;
}<!doctype html>
<script type="module">
import {sayHi} from './say.js';
document.body.innerHTML = sayHi('John');
</script>Tarayıcı içeri aktarılanları otomatik olarak getirir, deÄerlendirir ve programı çalıÅtırır.
Temel modül özellikleri
Sıradan programlara göre modüllerde ne farklıdır?
Hem tarayıcı hem de sunucu taraflı JavaScript için geçerli temel özellikler vardır.
Daima âuse strictâ
Modüller daima strict yapıdadır. ÃrneÄin, deklare edilmemiÅ bir deÄiÅkene deÄer atama iÅlemi hata verir.
<script type="module">
a = 5; // error
</script>
Bunu burada tarayıcıda görüyoruz ancak aynı durum tüm modüller için geçerlidir.
Modül seviyesinde kapsam
Her modülün kendi üst seviye kapsamı vardır. DiÄer bir deyiÅle, bir modülün üst seviyedeki deÄiÅken ve fonksiyonları, diÄer programlarda görünmez.
AÅaÄıdaki örnekte, iki program içeri aktarılıyor ve hello.js user.js de deklare edilen user deÄiÅkenini kullanmaya çalıÅıp, hata veriyor:
alert(user); // no such variable (each module has independent variables)let user = "John";<!doctype html>
<script type="module" src="user.js"></script>
<script type="module" src="hello.js"></script>Modüller dıÅarıdan ulaÅılmasını istediÄini export ile dıÅa aktarmalı, ve import ile ihtiyaç duyduÄunu içe aktarmalıdır.
Dolayısıyla user.js modülünü index.html yerine aÅaÄıda gösterildiÄi gibi doÄrudan hello.js dosyasına aktarmalıyız:
import {user} from './user.js';
document.body.innerHTML = user; // Johnexport let user = "John";<!doctype html>
<script type="module" src="hello.js"></script>Tarayıcıda, Her bir <script type="module"> için de baÄımsız üst seviye kapsam bulunmaktadır:
<script type="module">
// The variable is only visible in this module script
let user = "John";
</script>
<script type="module">
alert(user); // Error: user is not defined
</script>
EÄer gerçekten tarayıcı içi global bir deÄiÅkene ihtiyaç duyuyorsak, bu deÄiÅkeni (user) açık olarak window nesnesine atayıp, window.user ile ona ulaÅabiliriz. Ancak bu iÅleme gerçekten makul bir sebep olduÄunda baÅvurulmalıdır.
Modülün kodu içe aktarıldıÄı ilk yerde deÄerlendirilir.
EÄer aynı modül birden fazla yerde içe aktarılırsa, içindeki kod sadece ilk seferinde çalıÅtırılır. Daha sonra bu kodun çıktıları tüm içe aktarılan yerlere verilir.
Bunun önemli sonuçları vardır. Bunu örnekler üzerinden görelim.
İlk olarak, eÄer bir modülü çalıÅtırmak bir mesajı göstermek Åeklinde bir yan etkiye sebep oluyorsa, bu modülün çok kez içe aktarılması onun yalnızca ilk seferinde tetiklenmesi gerçeÄini deÄiÅtirmez:
// ð alert.js
alert("Modül deÄerlendirildi!");
// Aynı modülü diÄer dosylarda içe aktar
// ð 1.js
import `./alert.js`; // Modül deÄerlendirildi!
// ð 2.js
import `./alert.js`; // (hiçbir Åey)
Pratikte, üst seviyedeki modül kodu çoÄunlukla baÅlatma iÅlemi için kullanılır. Veri yapılarını üretir, bunlara baÅlangıç deÄerleri verir ve tekrar kullanılabilir olmalarını istiyorsak da bunları dıÅa aktarırız.
Åimdi daha ileri bir örnek.
Diyelim bir modül bir nesneyi dıÅa aktarıyor:
// ð admin.js
export let admin = {
name: "John"
};
EÄer bu modül birden fazla dosyada içe aktarılırsa, bu modül sadece ilk seferinde deÄerlendirilir, admin nesnesi yaratılır ve sonra içe aktarıldıÄı tüm yerlere gönderilir.
Nesneyi içe aktaran tüm dosyalar aynı admin nesnesini alırlar:
// ð 1.js
import {admin} from './admin.js';
admin.name = "Pete";
// ð 2.js
import {admin} from './admin.js';
alert(admin.name); // Pete
// Hem 1.js hem de 2.js aynı nesneyi içe aktardı
// 1.js yapılan deÄiÅiklikler 2.js dosyasına da yansır
Tekrar edecek olursak, modül yalnızca bir kez çalıÅtırılır. DıÅa aktarımlar gerçekleÅtirilir ve bunlar içe aktarıldıkları bu yerlerde paylaÅılır. Dolayısıyla eÄer bir Åey admin nesnesini deÄiÅtirirse, diÄer modüller bu deÄiÅikliÄi görecektir.
Böyle bir özellik konfigürasyon gerektiren modüller için harikadır. Gereken özellikleri ilk içe aktarımda ayarlayıp, daha sonraki içe aktarmalar için modülü hazır hale getiririz.
ÃrneÄin, admin.js modülü bazı iÅlevleri saÄlayabilir ancak bunları gerçekleÅtirebilmek için dıÅarıdan bazı bilgileri alması gerekebilir:
// ð admin.js
export let admin = { };
export function sayHi() {
alert(`Hizmete hazır, ${admin.name}!`);
}
Uygulamamızın ilk programı olan init.js dosyasında, admin.name için deÄer atıyoruz. Bu iÅlemden sonra admin.js dosyasının kendisi de dahil, fonksiyonu çaÄıranlar bu deÄeri görebileceklerdir:
// ð init.js
import {admin} from './admin.js';
admin.name = "Pete";
// ð other.js
import {admin, sayHi} from './admin.js';
alert(admin.name); // Pete
sayHi(); // Hizmete hazır, Pete!
import.meta
import.meta nesnesi kullanılmakta olan modül hakkında bilgileri içerir.
İçeriÄi ortama baÄlıdır. Tarayıcıda programın URL bilgisini ya da eÄer HTML içindeyse ilgili web sayfasının URL bilgisini içerir:
<script type="module">
alert(import.meta.url); // script URL (satır içi program için html sayfasının URL bilgisi)
</script>
Ãst seviye "this"in deÄeri undefined dır.
Bu detay bir bilgidir ancak eksik bir Åey bırakmamak adına bundan söz etmeliyiz.
Bir modülde üst seviye thisin deÄeri, modül olmayan programdaki bir global nesnenin tersine, tanımsızdır (undefined):
<script>
alert(this); // window
</script>
<script type="module">
alert(this); // undefined
</script>
Tarayıcıya özgü özellikler
Sıradan bir script etiketi içinde yer alan programlara göre bu tagın type="module" niteliÄine sahip olduÄu durumda ilgili programın tarayıcıya özgü bazı farklılıkları vardır.
Bu makaleyi ilk defa okuyorsan ya da JavaScriptâi tarayıcıda kullanmıyorsan bu konuyu atlamak isteyebilirsin.
Modül programları ertelenir
Modül programları daima defer niteliÄi (Komut Dosyaları (Scripts): async, defer bölümünde anlatıldı) ile aynı etkiyi yaratarak hem harici hem de satır içi programlar için ertelenir.
DiÄer bir deyiÅle:
- harici modül programları
<script type="module" src="...">HTMLânin iÅlenmesini engellemez. - modül programları HTML belgesi tamamen hazır olana kadar bekler.
- göreli sıralama korunur: belgede önde yer alan program daha önce çalıÅtırılır.
Bir yan etki olarak, modül programları altlarındaki HTML elemanlarını daima görebilir.
ÃrneÄin:
<script type="module">
alert(typeof button); // object: program aÅaÄıdaki düÄme elemanını görebilir
// modüller ertelendiÄinden, program tüm sayfa yüklendikten sonra çalıÅır
</script>
<script>
alert(typeof button); // Hata: düÄme elemanı tanımsızdır, program aÅaÄısındaki elemanları göremez
// sıradan programlar sayfanın tamamı iÅlenmeden hemen çalıÅır
</script>
<button id="button">Button</button>
Lütfen dikkat edin: ikinci program aslında ilkinden önce çalıÅır! Dolayısıyla önce undefined sonra object görürüz.
Bu modüllerin çalıÅtırılması belgenin iÅlenmesi tamamlanana kadar ertelendiÄi içindir. Sıradan programlar hemen çalıÅır ve dolayısıyla çıktısını önce görürüz.
Modülleri kullanırken, HTML belgesinin JavaScript uygulamasının hazır olmasından önce görünebileceÄinin farkında olmalıyız. Bazı fonksiyonlar henüz çalıÅmayabilir. Kullanıcıları bu durumdan haberdar etmek için sayfaya bir âyüklenme göstergesiâ (loading indicators) koyabiliriz.
Async satır içi programlarda çalıÅır
Async niteliÄi <script async type="module"> hem satır içi hem de harici programlarda kullanılabilir. Async programlar diÄer programlardan veya HTML belgesinden baÄımsız olarak, içe aktarılan modüller iÅlenir iÅlenmez çalıÅmaya baÅlar.
ÃrneÄin, aÅaÄıdaki programın async niteliÄi vardır, dolayısıyla baÅka bir Åeyi beklemesi gerekmez.
İçe aktarma iÅlemini gerçekleÅtirir (./analytics.js dosyasına ulaÅır) ve hazır olduÄunda (HTML belgesinin yüklenmesi bitmemiÅ veya baÅka bir program askıda bekliyor olsa bile) çalıÅır.
Bu iÅlevsellik için iyidir. Böylelikle sayaçlar, reklamlar veya belge düzeyinde olay dinleyicilerinden baÄımsız olarak programımız çalıÅabilir.
<!-- tüm destek dosyalarına ulaÅılır (analytics.js), ve program çalıÅır -->
<!-- belgesi ya da diÄer <script> etiketlerini beklemez -->
<script async type="module">
import {counter} from './analytics.js';
counter.count();
</script>
Harici programlar
Harici modüllerin iki belirgin farkı vardır:
-
Aynı
srcye sahip harici programlar yalnızca bir kez çalıÅır:<!-- my.js programına ulaÅılır ve yalnızca bir kez çalıÅtırılır. --> <script type="module" src="my.js"></script> <script type="module" src="my.js"></script> -
Farklı bir alan adından ulaÅılan harici programlar CORS üstbilgisini gerektirir. BaÅka bir deyiÅle, eÄer modüle baÅka bir alan adından ulaÅılıyorsa, bu eriÅime izin verildiÄini belirtmek açısından uzak sunucu bir
Access-Control-Allow-Origin: *üstbilgisini saÄlamalıdır (*yerine ulaÅılan alan adının ismi kullanılabilir).<!-- another-site.com Access-Control-Allow-Origin saÄlamalıdır--> <!-- aksi halde, program çalıÅmayacaktır --> <script type="module" src="http://another-site.com/their.js"></script>Bu gereklilik güvenliÄi artırıcı bir unsurdur.
Yalın modüllere izin verilmez
Tarayıcıda, programlarda (HTMLâde deÄil) importa göreli ya da mutlak URL eÅlik etmelidir. Bu Åekilde bir yola sahip olmayan ve yalın modül olarak isimlendirilen bu programın kullanımı hata verir.
ÃrneÄin, bu import geçersizdir:
import {sayHi} from 'sayHi'; // Hata, "yalın" modül
// './sayHi.js' Åeklinde olmalıdır
Node.js ve sarmalama araçları (bundle tools) gibi bazı ortamlar modülleri bulmak için kendi yöntemleri ve bunların ince ayarı için kendi çengelleri (hooks) olduÄundan, yalın modüllerin kullanımına izin verir. Ancak tarayıcılar henüz yalın modülleri desteklememektedir.
Uyumluluk, ânomoduleâ
Eski tarayıcılar type="module" niteliÄini desteklemez. Bilinmeyen tipteki programlar göz ardı edilir. Bu tarayıcılar için nomodule niteliÄi ile tedbir alınabilir:
<script type="module">
alert("Runs in modern browsers");
</script>
<script nomodule>
alert("Modern browsers know both type=module and nomodule, so skip this")
alert("Old browsers ignore script with unknown type=module, but execute this.");
</script>
EÄer sarmalama araçlarını kullanırsak, modüller birlikte toplanacaÄı için bunların import/export ifadeleri özel sarmalayıcı çaÄrıları ile yer deÄiÅtirir. Dolayısıyla, oluÅan dosya type="module" ifadesini gerektirmez ve bu dosyayı sıradan bir <script> etiketinin içine koyabiliriz:
<!-- Webpack bir araç ile bundle.js dosyası oluÅturduÄumuzu varsayarsak -->
<script src="bundle.js"></script>
Sarmalama araçları
Gerçek hayatta tarayıcı modülleri nadiren ham halleriyle kullanılır. Genellikle bunları Webpack gibi özel bir araç ile bir araya getirip, sarmalarız ve üretim sunucusuna aktarırız.
Sarmalama kullanmanın yararlarından biri modüllerin çözümleneceÄi konusunda kontrol saÄlaması, yalın modüllerin ve CSS/HTML modülleri gibi daha fazlasının kullanımına olanak tanımasıdır.
Sarmalama araçları aÅaÄıdakileri yapar:
- HTMLâde
<script type="module">etiketi içerisine konan ana modülü alır. - Bu modülün baÄımlılıklarını analiz eder: içe aktarmalar ve bunların içe aktarmaları, vb.
- Tüm modülleri içeren tek bir dosya (ya da ayarlanabilen birden fazla dosya) oluÅturur, doÄal
importçaÄrılarını sarmalama fonksiyonları ile deÄiÅtirir. Bu sayede HTML/CSS modülleri gibi özel modüller de desteklenmiÅ olur. - Süreç sırasında diÄer dönüÅümler ve optimizasyonlar da uygulanabilir:
- UlaÅılamayan kodlar kaldırılır.
- Kullanılmayan dıÅa aktarmalar kaldırılır (âaÄaç silkelemeâ).
- GeliÅtirme esnasında kullanılan
consolevedebuggerifadeler kaldırılır. - Modern, deneysel JavaScript sözdizimi Babel ile benzer iÅlevselliÄe sahip eskisi ile deÄiÅtirilebilir.
- Sonuçta oluÅan dosya küçültülür (boÅluklar kaldırılır, deÄiÅkenler daha kısa isimler ile deÄiÅtirilir, vs.)
Bununla birlikte, doÄal modüller de kullanılabilir. Dolayısıyla burada Webpack kullanmayacaÄız: bunu siz daha sonra yapılandırabilirsiniz.
Ãzet
Ãzetlersek, esas kavramlar:
- Modül bir dosyadır.
import/exportifadelerinin çalıÅabilmesi için, tarayıcıların, pek çok farklılıÄa iÅaret eden<script type="module">etiketini kullanması gerekir:- varsayılan olarak ertelenir.
- Async satır içi programlarda çalıÅır.
- Harici programlar CORS üstbilgisine ihtiyaç duyar.
- Mükerrer harici program dosyaları görmezden gelinir.
- Modüllerin kendi yerel üst seviye kapsamları vardır ve iÅlevselliÄi
import/exportyoluyla deÄiÅtirir. - Modüller daima
use strictkullanır. - Modül kodu yalnızca bir kez çalıÅtırılır. DıÅa aktarımlar bir kez yaratılır ve içe aktarılan yerlerle paylaÅılır.
Dolayısıyla, genellikle, modülleri kullandıÄımızda, her modül saÄladıÄı iÅlevselliÄi dıÅa aktarır. Daha sonra bu iÅlevselliÄi ihtiyaç duyulan yerde import ile içe aktarırız. Tarayıcı bu program dosyalarını otomatik bir Åekilde yükler ve deÄerlendirir.
Ãretim aÅamasında performans ve diÄer nedenlerden ötürü modülleri bir araya getirmek için sıklıkla Webpack gibi sarmalama aracı kullanılır.
Bir sonraki bölümde daha fazla modül örneÄi ve içe/dıÅa aktarımların nasıl yapılabileceÄini göreceÄiz.
Yorumlar
<code>kullanınız, birkaç satır eklemek için ise<pre>kullanın. EÄer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)