JavaScript kullanıyorsanız, ister ön-yüz ister sunucu tarafı olsun, modül kullanımına rastlamış ve kullanmışsınızdır. “import” ve “require” ifadeleri ne işe yarar, farklı kullanım şekillerinin birbirinden farkı nedir, gibi soruları merak ediyorsanız okumaya devam edin. JavaScript’e nispeten yeni bulaşmış biri olarak, bu soruların cevabını sadece bulmakla kalmayıp anlamaya da çalıştım ve bu süreci istifadenize sunmak istedim.
unsplash-logoFotoğraf Unsplash’ta Richard Ciraulo’ya aittir.
Detaylara girmeden evvel terminolojiyi oturtmakta fayda var, çünkü JavaScript dünyasında kavram karmaşasına çok sık rastlanıyor.
Bizi ilgilendiren iki tür modül var, kronolojik olarak:
JS modülleri nispeten yeni bir özellik. Geç gelen bir özellik olduğundan, öncesinde boşluğunu dolduracak başka modül API’leri çıkmış. Modüller, yukarıda açıkladığım gibi, Node.js’te daha önce de vardı - fakat native olarak değil.
Her dosya bir modül diye tasavvur edebilirsiniz. Öncelikle modüllere neden ihtiyaç olduğunu düşünelim. İster bir ön-yüz (react, angular, vue veya vanilla (düz) js) ister bir sunucu uygulaması olsun (nodejs), kod parçalarını birbirinden ayırmak kodun okunabilirliğini artıran en önemli husustur. Bütün uygulamanızı 10.000 satırlık tek bir dosya olarak tutmak pek akıl kârı değil.
Genelde sunucu uygulamaları yaptığımdan örneklerimi Node.js ile veriyorum. Basit tutalım, örneğimiz;
Node.js ile ES modülü oluşturmak isterseniz temelde iki yönteminiz var:
.mjs
yapmak.js
olup, package.json dosyasında type
alanını module
yapmak.İkinci yöntemi uygulayan bir örnek:
Örneği çalıştırmak için dosyaları bir klasöre indirip, node index.js
komutunu kullanabilirsiniz.
Modülden iki değer “export” ediliyor, biri DEFAULT_SLEEP_MILLISECONDS
diğeri de sleep
fonksiyonu.
Modülü kullanacak olan kod (index.js
) istediği değerleri “import” ediyor ve böylece bu değerlere aynı isimlerle erişebiliyoruz.
Bu örnekte “named export” kullandık, yani export’larımıza birer isim verdik.
Bir de “default” export’lar var:
Bir modülü import ederken eğer süslü parantez ({}
) kullanmazsanız, o modüldeki default
export’u import etmiş oluyorsunuz.
İsim çakışmalarını engellemek için, import ettiğiniz değerlere yeni isimler verebilirsiniz. Mesela:
import {DEFAULT_SLEEP_MILLISECONDS, sleep as sleepMilliseconds} from './utils.js'
Bu sayede, sleep
metodunu, sleepMilliseconds
ismiyle kullanabileceksiniz.
İsim karmaşasının önüne geçmek için bir yöntem daha var, o da import’ları bir modül nesnesine bağlamak:
import * as utils from './utils.js'
Böylece, fonksiyona utils.sleep()
şeklinde ulaşabiliyorsunuz ve import edilen isimlerin çakışma ihtimalini sıfırlamış oluyorsunuz.
Basit örneğimizi native olmayan modüllerle oluşturacak olursak:
Tıpkı native modüllerden geri kalmayacak biçimde, kafamız yeterince karışmamış gibi, CJS modüllerinde de farklı export türleri var.
Bir diğer kullanım ise module.exports
değerini yeni bir değerle ezmek.
Bu kullanım için aşağıdaki örneğe bakalım:
exports
aslında module.exports
‘a işaret eden bir referans olduğundan, bu iki kullanımın etkisi aynı.
exports
referansını daha iyi anlayabilmek için, aşağıdaki modülü inceleyelim.
Bu modül sanki export ediyor gibi görünse de, aslında hiçbir şeyi export etmiyor.
function sleep(milliseconds) {
return new Promise(resolve =>
setTimeout(resolve, milliseconds)
);
}
exports = sleep;
Bu modülü require
ettiğinizde, elinize bir adet {}
geliyor.
Sleep metodunu export edememiş oluyorsunuz.
Bir de şöyle bir kullanıma rastlayabilirsiniz: module.exports = exports = falanfilan;
.
Bunun module.exports = falanfilan;
‘a üstünlüğü nedir derseniz, aşağıdaki türden hatalar yapma ihtimalinizi azaltıyor:
const DEFAULT_SLEEP_MILLISECONDS = 1000;
function sleep(milliseconds) {
return new Promise(resolve =>
setTimeout(resolve, milliseconds || DEFAULT_SLEEP_MILLISECONDS)
);
}
module.exports = {
DEFAULT_SLEEP_MILLISECONDS: DEFAULT_SLEEP_MILLISECONDS
};
exports.sleep = sleep; // Geçmiş olsun, bu modülü "require" eden kod sleep'e ulaşamayacak.
Bu modülü require
ettiğinizde, sleep
fonksiyonunun gelmediğini göreceksiniz.
Çünkü module.exports
‘a yeni bir değer atadık, fakat exports
hala eski değere işaret ediyor.
İşte bu hatalara düşmemek ve modül dosyasında farklı noktalarda exports’a bir şeyler ekleyebilmek için, module.exports
‘a yeni bir değer atarken, exports
‘u da güncellemek gerekiyor.
Bu da module.exports = exports = ...
kullanımını doğuruyor.
Native modüllere ön-yüz örneği olarak, sleep
metodumuzu kullanan basit bir html sayfası yapalım.
Burada dikkat edilecek husus, modülleri sayfaya eklerken script
tag‘inin module
tipinde tanımlanması.
HTML dosyasını tarayıcıda doğrudan açmaya çalışırsanız, CORS’tan ötürü JS modüllerinin yüklenmediğini göreceksiniz.
Bu engeli aşmak için bir http sunucusu ayağa kaldırmanız gerekiyor, mesela python -m http.server
gibi.
require
ve module.exports
ile kullanılıyor.console.log(module);
komutunun çıktısını inceleyin ve üzerine düşünün :smile: