JavaScript Modül Sistemi (ES Modules) Nedir?
JavaScript’te modül kavramının ortaya çıkışı
JavaScript henüz ilk çıktığı yıllarda web sitelerinde oldukça az mitarda kullanılmaktaydı. Sunucudan render edilmiş halde gelen web sayfası üzerinde bir takım küçük işler yapmaya yardımcı oluyordu. Bu nedenle çok fazla kod içeren büyük JavaScript dosyalarına da gerek yoktu. O zamanlardan günümüze geldiğimizde ise artık neredeyse tamamen JavaScript ile çalışan web siteleri görmekteyiz. JavaScript artık sadece internet tarayıcılarında değil, aynı zamanda NodeJS ile birlikte sunucuda da yoğun olarak kullanılmaktadır.
Son zamanlarda JavaScript’in yoğun miktarda kullanımından dolayı, artık JS temelli büyük uygulamaların tekrar kullanılabilir bir hale getirilmesi için modüller halinde ayrılarak bir yapının sağlanmasına ihtiyaç oluştu. Bu nedenle NodeJS, neredeyse ilk versiyondan beri require()
metodu ile modüler yapıları desteklemekteydi. Web tarafında ise CommonJS ve AMD temelli RequireJS gibi yükleyiciler oluştu. Son zamanlarda ise Webpack ve Babel tarafında kullanım artış gösterdi.
2015 yılına kadar pek çok 3. parti modül yükleyicileri kullanılsa da, web tarafında JavaScript için standart bir modül sistemi yoktu. Ecmascript 2015 ile ES modülleri dile eklenerek resmî bir modül sisteminin kurulması sağlandı.
ES Modules nedir?
ES Modules (Ecmascript modülleri) JavaScript için bir import mekanizması sağlar. Bu mekanizma sayesinde farklı dosyalar içerisinde yer alan kodlar, diğer dosyalarda import ederek tekrar kullanılabilir hale gelmektedir.
ES modüllerinin ana hedefi tarayıcılar olsa da 2020’de NodeJS desteği ile sunucularda da kullanılabilir hale gelmiştir.
Örnek bir ES module
Bir ES modülü aşağıdaki gibi bir veya daha fazla export ifadesi içeren değişkenkler/fonksiyonlardan oluşur:
// utils.js
// Named export
export function helloNamed() {
return "Hello named export!";
}
// Değişkenler de named export olarak kullanılabilir
export const name = 'Zafer';
// Default export
export default function helloDefault() {
return "Hello default export!";
}
// Her fonksiyonda export ifadesi kullanmadan,
// tek seferde de export edilebilir
export { helloNamed, name, helloDefault };
Burada da görüleceği gibi export
ve export default
ifadeleri bulunmaktadır. Bu ifadelere sırasıyla named export ve default export da denmektedir. Named export sadece belirlenen isimle kullanılabilirken, default export’ta ilgili fonksiyona herhangi bir isim verilebilir. Şimdi kullanımları örneklendirelim:
Named export kullanımı
// consumer.js
// Named export import edilmesi
import { name, helloNamed } from "./util.js";
// Değişken kullanımı
const myVar = name;
// Fonksiyon kullanımı
helloNamed();
Default export kullanımı
// consumer.js
// Default export kullanımı
import helloDefault from "./util.js";
// Default export değişkenler özel olarak da isimlendirilebilir
import myFunction from "./util.js";
// İkisi de aynı çıktıyı verecektir
helloDefault();
myFunction();
Hem default hem de named export’un aynı satırda kullanımı
// consumer.js
import helloDefault, { helloNamed } from "./util.js";
helloDefault();
helloNamed();
Dosya içerisindeki tüm modüllerin * karakteri ile kullanımı
Tüm modüllerin tek nesne altında import edilmesi için “* as modulename” syntax’ı kullanılır. Bu sayede, ilgili modüldeki export edilmiş tüm fonksiyonlar alınarak, tek seferde istenilen modül adı ile kullanımı sağlanabilir.
import * as myModule from "./util.js";
myModule.helloNamed();
myModule.default();
Bu kullanımda, default export edilen fonksiyon kullanılacağı zaman .default()
ile çağrımı yapılmalıdır.
İsim çakışmalarının önlenmesi
Farklı dosyalarda aynı isimde bulunan fonksiyonların isimlerinin çakışmasını önlemek için de as keyword’ü kullanılarak import edilebilir. Örneğin aşağıdaki gibi kare, çember ve üçgen modülleri olsun ve bu modüllerin içinde her bir şekil için ilgili şeklin adını tutan name değişkeni ve alan hesaplama fonksiyonu bulunsun. Bu ifadeleri import ederek kullanmaya çalıştığınızda Uncaught SyntaxError: Identifier 'name' has already been declared
hatası verecektir:
import { name, calculateArea } from './modules/square.js';
import { name, calculateArea } from './modules/circle.js';
import { name, calculateArea } from './modules/triangle.js';
calculateArea();
Bu sorunun çözümü için import edilen ifadeler as keyword’ü ile yeniden isimlendirilerek kullanılmalıdır:
import { name as squareName,
calculateArea as calculateSquareArea } from './modules/square.js';
import { name as circleName,
calculateArea as calculateCircleArea } from './modules/circle.js';
import { name as triangleName,
calculateArea as calculateTriangleArea } from './modules/triangle.js';
Farklı bir sunucuda yer alan modülün import edilmesi
Örneğin reduxjs’in unpkg sitesinden alınarak import edilmesi aşağıdaki gibi gerçekleştirilebilir:
import { createStore } from "https://unpkg.com/[email protected]/es/redux.mjs";
const store = createStore()
İnternet tarayıcılarında kullanımı
İnternet tarayıcılarında kullanımı için <script type="module">
olacak şekilde bir syntax kullanılmaktadır:
<html lang="en">
<head>
</head>
<body>
<!-- ... -->
<script type="module">
import { helloNamed } from "./utils.js";
helloNamed();
</script>
<!-- Dosya adı da src özlliğinde verilebilir -->
<script type="module" src="main.js"></script>
</body>
</html>
ES modülleri direkt olarak tarayıcı içerisinde kullanılanilse de; eski tarayıcılara destek verme ve code-splitting gibi işler webpack yardımı ile otomatize hale getirilerek kullanılmaktadır.
Dinamik import kavramı
ES modülleri ilk zamanlarda statik olarak oluşturulabiliyordu ve çalışma anında değiştirilemiyordu. Ecmascript 2020’de gelen dynamic imports özelliği ile çalışma anında butona tıklanma gibi olaylarda dinamik olarak istenilen modülün getirilip import edilmesi sağlandı. (Bu arada dminamik import yapısının, webpack tarafından çok önceden beri desteklediğini de bahsetmeden geçmeyelim.)
Dinamik import’lar sayesinde code-splitting (kod parçalama) işlemleri yapılabilir. Böylece istenilen kod sadece gerektiği anda yüklenerek performansı uygulamalar yazılabilir. React ve Vue gibi kütüphaneler de dinamik import yaparak code-splitting işlemlerini gerçekleştirmektedir. Bu sayede route işlemlerinde veya herhangi bir kullanıcı etkileşiminde kodların ayrılması sağlanmaktadır.
Dinamik import örneği
Aşağıdaki gibi bir HTML sayfası olsun ve butona tıklandığında dinamik olarak import etme işlemini gerçekleştirsin:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dynamic imports</title>
</head>
<body>
<button id="btn">Load!</button>
</body>
<script src="loader.js"></script>
</html>
Loader.js içeriği aşağıdaki gibi tıklama anında utils.js
‘i import edecek şekilde oluşturulabilir:
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// Modülün asenkron olarak import edilmesi
import("./util.js").then((module) => {
module.helloNamed();
});
// Belirli bir fonksiyonun destructring yöntemi ile kullanımı için
import("./util.js").then(({ helloNamed }) => {
helloNamed();
});
});
Dosyanın başlangıcında dinamik import kullanımı
Dosya başında kullanılan dinamik import’lar bir Promise
döndürdükleri için then()
fonksiyonu ile geri dönüş değeri alınmalıdır:
const utilYukle = () => import("./util.js");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
utilYukle().then(module => {
module.helloNamed();
module.default();
});
});
JSON dosyasının dinamik olarak import edilmesi
Diyelim ki projenizde aşağıdaki gibi bir JSON dosyamız bulunsun:
{
"name": "Zafer",
"age": 30
}
Sayfada bir butona tıklandığında bu JSON dosyasının yükleneceğini varsayalım. Bunun için default export yöntemini aşağıdaki gibi kullanabiliriz:
const loadPerson = () => import("./person.json");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
loadPerson().then(module => {
const { name, age } = module.default;
console.log(name, age);
});
});
Burada da görüldüğü gibi name
ve age
özellikleri destructring yöntemi ile alınabilmektedir.
async/await ile dinamik import kullanımı
import()
fonksiyonu geriye bir Promise döndürdüğü için, dinamik import özelliğini async/await ifadeleriyle de kullanabiliriz:
const btn = document.getElementById("btn");
btn.addEventListener("click", async () => {
const utilsModule = await loadUtil();
utilsModule.helloNamed();
utilsModule.default();
});
Sonuç olarak
ES modülleri sayesinde hiçbir üçüncü parti araca gerek kalmadan direkt olarak JS uygulamanızı modüler hale getirebiliyorsunuz. Tabi bu durum sadece modern tarayıcılarda destek verildiği için, daha eski tarayıcılarda webpack gibi üçüncü parti bir araç kullanmak gerekebiliyor. Sonraki yazımda da webpack’i ele almayı düşünüyorum. Sonraki yazımda görüşmek üzere. Hoşça kalın…