Takip et

TypeScript’te Modüller Nasıl Kullanılır: Kapsamlı Bir Rehber

TypeScript’te Modüller Nasıl Kullanılır: Kapsamlı Bir Rehber Giriş: Modüllerin Önemi ve Neden İhtiyaç Duyulduğu Modern yazılım gelişti

TypeScript’te Modüller Nasıl Kullanılır: Kapsamlı Bir Rehber

Giriş: Modüllerin Önemi ve Neden İhtiyaç Duyulduğu

Modern yazılım geliştirme, giderek artan karmaşıklıkta uygulamalar üretmeyi gerektiriyor. Bu karmaşıklığı yönetmek, kodun okunabilirliğini, bakımını ve yeniden kullanılabilirliğini sağlamak için belirli prensiplere ve araçlara ihtiyaç duyarız. İşte tam da bu noktada modüller devreye girer. TypeScript, JavaScript’in bir üst kümesi olarak, modül sistemlerini daha güçlü ve tip güvenli bir şekilde kullanmamızı sağlar.

Modüller, kodunuzu mantıksal olarak ayrılmış, bağımsız birimlere bölmenizi sağlayan mekanizmalardır. Her modül, kendi içinde belirli bir işlevselliği barındırır ve bu işlevselliği diğer modüllerle paylaşabilir veya diğer modüllerden alabilir. Bu yaklaşım, yazılım geliştirmenin temel taşlarından biri olan “böl ve yönet” prensibini destekler.

Peki, modüllere neden ihtiyaç duyarız?

* Küreselden Kaçınma (Avoiding Global Scope Pollution): Modüllerden önceki dönemlerde, JavaScript dosyaları genellikle küresel kapsamda değişkenler ve fonksiyonlar tanımlardı. Bu durum, farklı dosyalar veya kütüphaneler arasında isim çakışmalarına (name collision) yol açar ve uygulamanın beklenmedik şekillerde davranmasına neden olabilirdi. Modüller, her dosyanın kendi özel kapsamına sahip olmasını sağlayarak bu sorunu ortadan kaldırır. Bir modül içinde tanımlanan her şey, açıkça dışarı aktarılmadığı sürece o modüle özel kalır.
* Kodun Organizasyonu ve Yapılandırılması: Büyük projelerde, yüzlerce veya binlerce dosya olabilir. Modüller, bu dosyaları mantıksal gruplara ayırarak projenin yapısını düzenlemeye yardımcı olur. Örneğin, bir utils modülü yardımcı fonksiyonları, bir components modülü UI bileşenlerini içerebilir. Bu, geliştiricilerin belirli bir işlevselliği bulmasını ve anlamasını kolaylaştırır.
* Yeniden Kullanılabilirlik (Reusability): İyi tasarlanmış modüller, farklı projelerde veya projenin farklı bölümlerinde kolayca yeniden kullanılabilir. Bir modülü bir kez yazıp test ettikten sonra, onu defalarca içe aktarıp kullanabilirsiniz. Bu, geliştirme sürecini hızlandırır ve hataları azaltır.
* Bakım Kolaylığı: Modüler bir yapı, bir hatanın veya değişikliğin etkisini belirli bir modülle sınırlar. Bir modülde yapılan değişiklikler genellikle diğer modülleri doğrudan etkilemez, bu da bakım sürecini daha öngörülebilir ve daha az riskli hale getirir. Ayrıca, modüllerin bağımsız doğası, birim testlerinin yazılmasını ve uygulanmasını kolaylaştırır.
* Bağımlılık Yönetimi: Modüller, açıkça hangi bağımlılıklara sahip olduklarını belirtir. Bu, bir modülün çalışması için nelere ihtiyaç duyduğunu anlamayı kolaylaştırır ve gereksiz kodun yüklenmesini önler.

TypeScript, JavaScript’in modül sistemlerini tam olarak destekler ve hatta tip güvenliği ile bu sistemleri daha da geliştirir. Bu makalede, TypeScript’te modüllerin nasıl tanımlandığını, içe ve dışa aktarıldığını, farklı modül sistemlerinin ne anlama geldiğini ve tsconfig.json ayarlarıyla nasıl yapılandırıldığını detaylı bir şekilde inceleyeceğiz.

Modüllerin Temel Prensipleri: Import ve Export

TypeScript’te modül kullanmanın özü, export ve import anahtar kelimeleridir. Bu iki anahtar kelime, modüller arasında değerlerin, fonksiyonların, sınıfların veya arayüzlerin nasıl paylaşıldığını tanımlar.

export Anahtar Kelimesi: Ne İşe Yarar?

export anahtar kelimesi, bir modül içindeki bir öğeyi (değişken, fonksiyon, sınıf, tip, arayüz vb.) diğer modüller tarafından kullanılabilir hale getirir. İki ana export türü vardır: adlandırılmış (named) ve varsayılan (default).

Adlandırılmış Export (Named Exports)

Adlandırılmış exportlar, bir modülden birden fazla öğeyi dışarı aktarmak istediğinizde kullanılır. Her öğe, kendi orijinal adıyla dışarı aktarılır ve içe aktarılır.

// math.ts
export const PI = 3.14159;

export function sum(a: number, b: number): number {
    return a + b;
}

export class Calculator {
    add(x: number, y: number): number {
        return x + y;
    }
}

interface Point {
    x: number;
    y: number;
}
export type Coords = Point; // Type'ı dışa aktarma

// Dışa aktarma listesi ile de yapılabilir:
// const PI = 3.14159;
// function sum(a: number, b: number): number { return a + b; }
// export { PI, sum };
Varsayılan Export (Default Exports)

Bir modülün genellikle “ana” işlevini temsil eden tek bir öğeyi dışarı aktarmak istediğinizde varsayılan export kullanılır. Bir modül başına yalnızca bir varsayılan export olabilir. İçe aktarılırken, bu öğeye herhangi bir isim verebilirsiniz.

// greeter.ts
class Greeter {
    greet(name: string): string {
        return Hello, ${name}!;
    }
}

export default Greeter; // Sadece bir varsayılan export olabilir

// Veya bir fonksiyonu varsayılan olarak dışa aktarma:
// export default function sayHello(name: string): string {
//     return Hello, ${name}!;
// }

// Veya bir değeri:
// const myValue = 42;
// export default myValue;

import Anahtar Kelimesi: Nasıl Kullanılır?

import anahtar kelimesi, başka bir modül tarafından dışa aktarılmış öğeleri mevcut modülünüze dahil etmek için kullanılır.

Adlandırılmış Import (Named Imports)

Adlandırılmış exportları içe aktarırken, süslü parantezler {} kullanarak ve öğelerin orijinal adlarını belirterek içe aktarırsınız.

// app.ts
import { PI, sum, Calculator, Coords } from './math'; // './math' dosya yolunu belirtir

console.log(PI); // 3.14159
console.log(sum(5, 3)); // 8

const calc = new Calculator();
console.log(calc.add(10, 20)); // 30

const myCoords: Coords = { x: 10, y: 20 };
console.log(myCoords);

// İçe aktarılan öğelere farklı bir isim vermek isterseniz as anahtar kelimesini kullanabilirsiniz:
import { PI as MyPi, sum as addNumbers } from './math';
console.log(MyPi);
console.log(addNumbers(1, 2));
Varsayılan Import (Default Imports)

Varsayılan exportları içe aktarırken, süslü parantez kullanmadan, doğrudan içe aktarmak istediğiniz öğeye bir isim verirsiniz.

// app.ts
import MyGreeter from './greeter'; // 'Greeter' sınıfı 'MyGreeter' adıyla içe aktarıldı

const greeter = new MyGreeter();
console.log(greeter.greet('TypeScript User')); // Hello, TypeScript User!

Varsayılan ve adlandırılmış exportları aynı anda içe aktarabilirsiniz:

// mixed.ts
export const SOME_CONSTANT = 100;
export default class MyClass { / ... / }

// app.ts
import MyClass, { SOME_CONSTANT } from './mixed';
console.log(SOME_CONSTANT); // 100
const instance = new MyClass();
Tümünü Import Etme (Namespace Import)

Bir modülün tüm adlandırılmış exportlarını tek bir nesne altında içe aktarmak isterseniz, * as sözdizimini kullanabilirsiniz.

// app.ts
import * as MathUtils from './math';

console.log(MathUtils.PI);
console.log(MathUtils.sum(7, 8));
const calc = new MathUtils.Calculator();
console.log(calc.add(1, 2));
Yeniden Export Etme (Re-exporting)

Bazen bir modülün dışa aktardığı öğeleri başka bir modül aracılığıyla yeniden dışa aktarmak isteyebilirsiniz. Bu, API yüzeyini düzenlemek veya daha yüksek seviyeli bir modül oluşturmak için kullanışlıdır.

// components/button.ts
export class Button { / ... / }

// components/input.ts
export class Input { / ... / }

// components/index.ts (bir "barrel file" veya toplu export dosyası)
export * from './button'; // button.ts'deki tüm adlandırılmış exportları yeniden dışa aktar
export * from './input';  // input.ts'deki tüm adlandırılmış exportları yeniden dışa akt

// app.ts
import { Button, Input } from './components'; // Şimdi doğrudan 'components' modülünden alabiliriz

Varsayılan exportları yeniden dışa aktarmak için biraz farklı bir sözdizimi kullanılır:

// moduleA.ts
export default class A {}

// moduleB.ts
export { default as ClassA } from './moduleA'; // default'u ClassA olarak yeniden dışa aktar

// app.ts
import { ClassA } from './moduleB';
const a = new ClassA();
Yan Etkiler İçin Import Etme (Side-effect Imports)

Bazı modüllerin, sadece içe aktarıldıklarında çalışan global konfigürasyonlar veya diğer yan etkileri olabilir. Bu tür modülleri içe aktarmak için süslü parantez veya isim belirtmeden sadece import anahtar kelimesini kullanırsınız.

// polyfills.ts (örneğin, belirli tarayıcı API'larını ekler)
import 'some-polyfill-library';

// app.ts
import './polyfills'; // Sadece polyfills.ts dosyasının çalışmasını sağlar, herhangi bir değer içe aktarmaz.

Modül Sistemleri (Module Systems): Tarihçe ve TypeScript Desteği

JavaScript’in başlangıcından itibaren, modül sistemleri ekosistemde farklı şekillerde gelişti. TypeScript, bu farklı sistemleri destekleyerek geliştiricilere esneklik sunar. En yaygın modül sistemleri CommonJS ve ES Modülleri’dir.

CommonJS (CJS)

CommonJS, özellikle sunucu tarafı JavaScript (Node.js) için geliştirilmiş bir modül sistemidir. Senkron bir yükleme modeline sahiptir, yani bir modül içe aktarıldığında, o modülün kodu tamamen yüklenene ve çalıştırılana kadar programın yürütülmesi durur.

Node.js Ortamında Kullanımı

Node.js’te CommonJS, require() fonksiyonu ile modülleri içe aktarır ve module.exports veya exports nesnesi ile modülleri dışa aktarır.

// myModule.js (CommonJS)
const myVariable = 123;
function myFunction() { return 'Hello from CJS'; }

module.exports = {
    myVariable,
    myFunction
};

// app.js (CommonJS)
const { myVariable, myFunction } = require('./myModule');
console.log(myVariable); // 123
console.log(myFunction()); // Hello from CJS
TypeScript’te CommonJS Yapılandırması (tsconfig.json)

TypeScript, CommonJS sözdizimini kullanarak yazılmış kodları derleyebilir ve modern ES modül sözdizimini CommonJS’e dönüştürebilir. Bunu yapmak için tsconfig.json dosyasında module derleyici seçeneğini commonjs olarak ayarlamanız gerekir.

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs", // CommonJS modül sistemini kullan
    "target": "es2016",   // Hedef JavaScript sürümü
    "esModuleInterop": true, // ES Modülleri ile CommonJS arasındaki uyumluluğu artırır
    "strict": true,
    "outDir": "./dist"
  },
  "include": [
    "src//*"
  ]
}

esModuleInterop: true seçeneği önemlidir çünkü ES Modülleri’nin varsayılan importları ile CommonJS’in module.exports yapısı arasında uyumluluk sağlar. Bu olmadan, CommonJS modüllerini ES modülü sözdizimiyle içe aktarırken sorunlar yaşayabilirsiniz (örneğin, import * as React from 'react' yerine import React from 'react' kullanamama).

ES Modülleri (ESM / ECMAScript Modules)

ES Modülleri, ECMAScript 2015 (ES6) ile JavaScript’e resmi olarak eklenen standart modül sistemidir. Asenkron bir yükleme modeline sahiptir ve hem tarayıcılarda hem de Node.js gibi sunucu ortamlarında çalışacak şekilde tasarlanmıştır. import ve export anahtar kelimeleri bu sistemin bir parçasıdır.

Modern Web Geliştirmede Yeri

Modern web tarayıcıları, ES Modüllerini doğrudan destekler. Bir etiketine type="module" özniteliği ekleyerek tarayıcıda ES Modüllerini kullanabilirsiniz.


// app.js
import { sum } from './math.js'; // Tarayıcıda dosya uzantısı önemlidir
console.log(sum(1, 2));
Tarayıcı Desteği

Güncel tüm modern tarayıcılar ES Modüllerini desteklemektedir. Ancak, eski tarayıcılar için bir dönüştürme (transpilation) işlemi veya polyfill gerekebilir.

Node.js'te ESM Kullanımı

Node.js, v12 ve sonraki sürümlerden itibaren ES Modüllerini resmi olarak desteklemektedir. Node.js'te ESM kullanmanın iki ana yolu vardır:
1. .mjs uzantısı: Dosyalarınızı .mjs uzantısıyla kaydetmek, Node.js'e o dosyanın bir ES Modülü olduğunu bildirir.
2. package.json'da type: "module": Projenizin package.json dosyasına "type": "module" eklemek, .js uzantılı tüm dosyaların varsayılan olarak ES Modülü olarak ele alınmasını sağlar.

// package.json
{
  "name": "my-esm-project",
  "version": "1.0.0",
  "type": "module", // Bu, .js dosyalarının ESM olarak yorumlanmasını sağlar
  "main": "dist/index.js",
  "scripts": {
    "start": "node dist/index.js"
  }
}
TypeScript'te ES Modülleri Yapılandırması (tsconfig.json)

TypeScript kodunuzu ES Modüllerini kullanacak şekilde derlemek için module seçeneğini esnext, es2020, es2022, node16 veya nodenext gibi bir değere ayarlayabilirsiniz. esnext genellikle en güncel ES modül özelliklerini kullanır.

// tsconfig.json
{
  "compilerOptions": {
    "module": "esnext", // ES Modüllerini kullan (en güncel)
    "target": "es2020",  // Hedef JavaScript sürümü
    "strict": true,
    "outDir": "./dist",
    "moduleResolution": "node" // veya "nodenext"
  },
  "include": [
    "src//*"
  ]
}

AMD (Asynchronous Module Definition) - Kısa Bahis

AMD, tarayıcı ortamında asenkron modül yüklemesi için tasarlanmış bir modül sistemidir. require.js gibi kütüphanelerle popülerlik kazanmıştır. Günümüzde ES Modülleri'nin yaygınlaşmasıyla kullanımı azalmıştır. TypeScript, module: "amd" seçeneği ile AMD'yi destekler.

UMD (Universal Module Definition) - Kısa Bahis

UMD, CommonJS, AMD ve küresel değişkenler gibi farklı modül sistemlerinde çalışabilen, "evrensel" bir modül tanımlama şeklidir. Kütüphane yazarları tarafından, kütüphanelerinin mümkün olduğunca çok ortamda çalışmasını sağlamak için kullanılmıştır. TypeScript, module: "umd" seçeneği ile UMD'yi destekler.

Sistem Seçimi ve tsconfig.json'daki module Ayarı

Hangi modül sistemini seçeceğiniz projenizin hedefine ve ortamına bağlıdır:

* Node.js uygulamaları için: commonjs veya node16/nodenext (ESM kullanmak istiyorsanız).
* Tarayıcı uygulamaları için: esnext veya es2020 gibi modern bir ES modül değeri. Ardından bir paketleyici (webpack, Rollup, Vite) kullanarak tarayıcı uyumlu hale getirebilirsiniz.
* Kütüphane yazarları için: Genellikle esm (ES Modülleri) ve cjs (CommonJS) çıktıları birlikte sunulur, böylece kütüphane farklı ortamlarda kullanılabilir.

tsconfig.json'daki module ayarı, TypeScript'in import ve export ifadelerini hangi JavaScript modül çıktısına dönüştüreceğini belirler.

TypeScript'te Modül Çözümlemesi (Module Resolution): moduleResolution

Modül çözümlemesi, import ifadelerinde belirtilen modül yollarının gerçek dosya sistemindeki dosyalara nasıl dönüştürüldüğünü belirleyen süreçtir. TypeScript, bu süreç için farklı stratejiler sunar ve tsconfig.json'daki moduleResolution seçeneği ile bu stratejiyi yapılandırabilirsiniz.

Klasik (Classic) Çözümleme

Bu, TypeScript'in ilk modül çözümleme stratejisidir ve genellikle eski tarayıcı tabanlı projeler için kullanılır. Bağıl yolları ve baseUrl'ı temel alır. Bir import 'moduleA' ifadesi için, TypeScript önce moduleA.ts, moduleA.d.ts, moduleA.js gibi dosyaları mevcut dizinde arar, sonra üst dizinlere çıkarak bu aramayı tekrarlar. Bu modül çözünürlük stratejisi, node_modules dizinindeki paketleri doğrudan aramaz.

Node Çözümlemesi

Node.js'in modül çözümleme algoritmasını taklit eder. Bu, Node.js uygulamaları veya Node.js ortamında çalışacak tarayıcı uygulamaları için en yaygın ve önerilen stratejidir.
Bir import 'moduleA' ifadesi için:
1. Bağıl yol (relative path) ise: ./, ../ gibi yollarla belirtilmişse, doğrudan o yoldaki dosyayı arar.
2. Modül adı ise (Node.js paketleri için):
* node_modules dizininde moduleA adlı bir klasör arar.
* Bu klasörde package.json dosyasını arar ve main veya exports alanına bakar.
* Eğer package.json yoksa veya main belirtilmemişse, index.js, index.ts, index.d.ts gibi varsayılan dosyaları arar.
* Bulamazsa, üst dizinlere çıkarak node_modules içinde aramaya devam eder.

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "node", // Node.js modül çözümlemesini kullan
    "module": "commonjs",     // veya "esnext"
    "target": "es2016"
  }
}

Node16 / Nodenext Çözümlemesi

TypeScript 4.7 ile tanıtılan bu yeni stratejiler, Node.js'in ES Modülleri için uyguladığı daha katı çözümleme kurallarını yansıtır. Özellikle package.json'daki type alanı ("type": "module") ve dosya uzantıları (.mjs, .cjs) gibi faktörleri dikkate alır. Bu stratejiler, ESM'nin Node.js'teki kesin davranışını yansıtmak için tasarlanmıştır ve import ifadelerinde dosya uzantılarının belirtilmesini gerektirebilir.

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "nodenext", // Node.js'in ESM çözümlemesini kullan
    "module": "nodenext",          // module da "nodenext" olmalı
    "target": "es2022"
  }
}

baseUrl ve paths Kullanımı

Büyük projelerde, bağıl yollar (../../../components/button) karmaşık ve okunaksız hale gelebilir. baseUrl ve paths seçenekleri, mutlak import yollarını daha düzenli hale getirmek için kullanılır.

* baseUrl: Modül çözümlemesinin başlangıç noktası olarak kullanılacak temel dizini belirtir.

// tsconfig.json
    {
      "compilerOptions": {
        "baseUrl": "./src", // Tüm modül importları 'src' dizininden başlayacak
        "moduleResolution": "node"
      }
    }

Şimdi src/components/button.ts dosyasını import { Button } from 'components/button'; şeklinde içe aktarabilirsiniz.

* paths: Modül yolları için özel eşleşmeler tanımlamanıza olanak tanır. Bu, takma adlar (aliases) oluşturmak için harikadır.

// tsconfig.json
    {
      "compilerOptions": {
        "baseUrl": ".", // paths'i kullanmak için baseUrl gereklidir
        "paths": {
          "@components/": ["src/components/"],
          "@utils/": ["src/utils/"]
        },
        "moduleResolution": "node"
      }
    }

Şimdi src/components/button.ts dosyasını import { Button } from '@components/button'; şeklinde içe aktarabilirsiniz. Bu, kodun daha temiz ve taşınabilir olmasını sağlar.

Dosya Uzantıları ve Modül Çözümlemesi

ES Modülleri'nin Node.js'te ve tarayıcılarda kullanılmasıyla birlikte, import ifadelerinde dosya uzantılarının belirtilmesi (örneğin, import { foo } from './foo.js';) önem kazanmıştır. TypeScript, varsayılan olarak bu uzantıları otomatik olarak eklemez. moduleResolution: "nodenext" veya module: "nodenext" gibi ayarlar, bu davranışa daha yakından uymayı hedefler. Geliştiricilerin bu konuda dikkatli olması ve derleme çıktılarını kontrol etmesi önemlidir.

Modüllerle Çalışırken Gelişmiş Konular

TypeScript'te modüller sadece temel import/export yapılarıyla sınırlı değildir. Daha karmaşık senaryoları yönetmek için gelişmiş özellikler de mevcuttur.

Dinamik Importlar (import())

ES2020 ile birlikte gelen dinamik importlar, bir modülü koşullu olarak veya isteğe bağlı olarak yüklemenizi sağlar. Bu, statik import ifadelerinden farklı olarak bir Promise döndürür ve modülün içeriğine asenkron olarak erişmenizi sağlar.

// button-module.ts
export function createButton(text: string) {
    const btn = document.createElement('button');
    btn.textContent = text;
    return btn;
}

// app.ts
async function loadButtonModule() {
    if (document.getElementById('lazy-load-area')) {
        // Modülü sadece belirli bir koşul karşılandığında yükle
        const { createButton } = await import('./button-module');
        const button = createButton('Dinamik Buton');
        document.getElementById('lazy-load-area')?.appendChild(button);
    }
}

// Bir kullanıcı etkileşimi sonrası çağrılabilir
document.getElementById('load-btn')?.addEventListener('click', loadButtonModule);
Code Splitting

Dinamik importlar, modern web uygulamalarında "code splitting" (kod bölme) için temel bir araçtır. Bir uygulamanın başlangıç yükleme boyutunu azaltarak performansı artırır. Kullanıcı belirli bir rotaya gittiğinde veya belirli bir etkileşimde bulunduğunda ilgili kod parçacıklarını yükleyerek, sadece ihtiyaç duyulan kodun indirilmesini sağlar.

Koşullu Yükleme

Bir modülü belirli bir koşula (örneğin, kullanıcının rolü, tarayıcı özellikleri) bağlı olarak yüklemek istediğinizde dinamik importlar çok kullanışlıdır.

Modül Ortam Bildirimleri (Module Augmentation / Declaration Merging)

TypeScript, mevcut modüllere yeni özellikler eklemenize veya global kapsamda bildirimler yapmanıza olanak tanıyan bir dizi bildirim mekanizmasına sahiptir. Bu, özellikle üçüncü taraf kütüphaneler için tip tanımlamaları eklerken veya mevcut modüllerin tiplerini genişletirken kullanışlıdır.

Global Bildirimler

Eğer bir kütüphane global kapsamda değişkenler veya fonksiyonlar tanımlıyorsa (örneğin, $ veya _ gibi), bunları TypeScript'e tanıtmak için global bildirimler kullanabilirsiniz.

// global.d.ts
declare global {
    interface Window {
        myGlobalVariable: string;
    }
    function myGlobalFunction(arg: string): void;
}
// Artık 'window.myGlobalVariable' ve 'myGlobalFunction' tip güvenli bir şekilde kullanılabilir.
Modül Bildirimleri (declare module)

Mevcut bir modülün (kendi yazdığınız veya bir npm paketinden gelen) tip tanımlamalarını genişletmek istediğinizde declare module sözdizimini kullanırsınız. Bu, "declaration merging" (bildirim birleştirme) olarak da bilinir.

// types/my-library.d.ts
declare module 'my-library' {
    // Mevcut 'my-library' modülüne yeni bir arayüz ekle
    export interface LibraryOptions {
        customOption?: boolean;
    }

    // Mevcut bir sınıf veya fonksiyona yeni bir metot/özellik ekle
    interface MyClass {
        newMethod(): void;
    }
}

// app.ts
import { MyClass, LibraryOptions } from 'my-library';

const opts: LibraryOptions = { customOption: true }; // Artık bu tip mevcut
const instance = new MyClass();
instance.newMethod(); // Artık bu metot mevcut

Bu, özellikle bir kütüphanenin tip tanımlamaları eksik olduğunda veya kendi uygulamalarınız için özelleştirmeler yapmak istediğinizde çok güçlü bir özelliktir.

TypeScript Ad Alanları (Namespaces) vs. Modüller

TypeScript'in ilk zamanlarında, modüllerden önce "Internal Modules" olarak bilinen ad alanları (namespaces) yaygın olarak kullanılıyordu. Günümüzde ise ad alanlarının kullanımı genellikle daha azdır ve çoğu yeni projede ES Modülleri tercih edilir.

Ne Zaman Hangisini Kullanmalı?

* Modüller (ES Modülleri):
* Önerilen Yaklaşım: Modern JavaScript ve TypeScript geliştirmesi için standart ve tercih edilen yöntemdir.
* Dosya Bazlı Kapsam: Her dosya kendi başına bir modüldür ve kendi özel kapsamına sahiptir.
* Bağımlılık Yönetimi: import/export ifadeleriyle açık ve net bağımlılık grafiği oluşturur.
* Araç Desteği: Modern paketleyiciler (Webpack, Rollup, Vite) ve derleme araçları modüllerle mükemmel çalışır.
* Ölçeklenebilirlik: Büyük ve karmaşık uygulamalar için idealdir.

* Ad Alanları (Namespaces):
* Eski Yaklaşım: Genellikle daha eski veya çok küçük projelerde, tek bir global nesne altında kodu gruplamak için kullanılır.
* Global Kapsam: namespace içindeki öğeler export edilmediği sürece namespace'e özeldir, ancak namespace'in kendisi global kapsamda yer alır veya başka bir namespace içinde iç içe olabilir.
* Derleme Çıktısı: Genellikle tek bir JavaScript dosyasına derlenir ve global bir nesne oluşturur.
* Kullanım Senaryoları: Çok basit yardımcı kütüphaneler veya global kapsamda belirli bir isim altında gruplama yapmak istendiğinde kullanılabilir, ancak çoğu durumda modüller daha iyi bir seçenektir.

// namespaceExample.ts
namespace MyUtilities {
    export function add(a: number, b: number): number {
        return a + b;
    }
    export const PI = 3.14;
}

// app.ts (aynı dosyada veya global olarak erişilebilen başka bir dosyada)
console.log(MyUtilities.add(5, 10));
console.log(MyUtilities.PI);

Ad alanları, /// yönergeleri ile birlikte kullanılırdı, ancak bu da modüllerle büyük ölçüde yerini almıştır.

Geçmişten Günümüze Yaklaşım

TypeScript, başlangıçta kendi modül sistemini (Internal Modules, sonra Namespaces olarak yeniden adlandırıldı) sundu. Ancak JavaScript'in ES Modüllerini benimsemesiyle, TypeScript de bu standardı tam olarak desteklemeye ve öncelik vermeye başladı. Günümüzde, yeni TypeScript projelerinde ad alanları yerine ES Modüllerini kullanmak en iyi uygulamadır.

Modül Yapılandırması ve Derleme Süreci

tsconfig.json dosyası, TypeScript derleyicisinin nasıl davranacağını belirleyen ana yapılandırma dosyasıdır. Modüllerle ilgili birçok önemli ayar bu dosyada bulunur.

tsconfig.json'da module ve target İlişkisi

* target: Derlenen JavaScript kodunun hangi ECMAScript sürümünü hedefleyeceğini belirtir (örneğin, es5, es2015, esnext). Bu, dil özelliklerinin (async/await, arrow functions vb.) nasıl dönüştürüleceğini etkiler.
* module: import ve export ifadelerinin hangi JavaScript modül sistemine dönüştürüleceğini belirtir (örneğin, commonjs, esnext, node16).

Bu iki ayar birbiriyle ilişkilidir. Örneğin, target: "es5" ve module: "esnext" ayarlarsanız, TypeScript esnext modül sözdizimini es5 uyumlu CommonJS veya AMD gibi bir sisteme dönüştürür. target genellikle module'dan daha eski bir sürüme ayarlanabilir.

esModuleInterop ve allowSyntheticDefaultImports

* esModuleInterop: true olarak ayarlandığında, ES Modülleri ile CommonJS modülleri arasındaki uyumluluğu artırır. Bu, özellikle CommonJS modüllerini varsayılan import sözdizimiyle (import React from 'react') içe aktarırken önemlidir. Altında yatan mekanizma, CommonJS modüllerinin default export'unu simüle etmektir. Bu ayar genellikle true olarak önerilir.
* allowSyntheticDefaultImports: esModuleInterop false olduğunda ve bir CommonJS modülünü varsayılan import ile içe aktarmak istediğinizde kullanılır. Sadece tip kontrolü için sentetik bir varsayılan import'a izin verir, ancak çalışma zamanında bir sorun yaşanabilir. esModuleInterop genellikle allowSyntheticDefaultImports'u içerir.

resolveJsonModule

true olarak ayarlandığında, .json uzantılı dosyaları modül olarak içe aktarmanıza izin verir. TypeScript, bu JSON dosyasını tip güvenli bir şekilde any tipinde bir nesne olarak ele alır.

// data.json
{
  "name": "TypeScript Module",
  "version": "1.0.0"
}

// app.ts
import * as data from './data.json';
console.log(data.name); // TypeScript Module

isolatedModules

true olarak ayarlandığında, her dosyanın bir modül olarak ele alınmasını ve bağımsız olarak derlenebilmesini sağlar. Bu, Babel gibi dönüştürücülerle birlikte TypeScript kullanırken veya Webpack gibi araçlarla transpileOnly modunu kullanırken önemlidir. Bu ayar, TypeScript'in tek dosyalı derleme modunda çalışmasına izin verir ve bazı tip kontrol güvenliklerini devre dışı bırakabilir (örneğin, const enum veya export = kullanılamaz).

typeRoots ve types

* typeRoots: TypeScript'in global tip tanımlama dosyalarını (örneğin, @types paketleri) arayacağı dizinleri belirtir. Varsayılan olarak ./node_modules/@types'tır.
* types: Sadece belirli global tip tanımlama paketlerini dahil etmek istediğinizde kullanılır. Belirtilenler dışındaki tüm @types paketleri göz ardı edilir.

Bu ayarlar, projenizin tip güvenliğini ve modül çözünürlüğünü doğru bir şekilde yönetmek için kritik öneme sahiptir.

Pratik Uygulama ve En İyi Yöntemler

Modülleri etkin bir şekilde kullanmak için bazı en iyi yöntemleri ve pratik ipuçlarını takip etmek önemlidir.

Her Dosya Bir Modül Prensibi

Modern TypeScript geliştirmede, her .ts veya .tsx dosyasını ayrı bir modül olarak düşünmek en iyi yaklaşımdır. Bu, kodu daha düzenli hale getirir ve küresel kapsam kirliliğini tamamen önler. Açıkça export veya import ifadeleri içermeyen dosyalar bile kendi özel kapsamına sahip bir modül olarak kabul edilir.

İyi Adlandırılmış Exportlar

Dışa aktardığınız öğelere anlamlı ve açıklayıcı adlar verin. Bu, modülünüzü içe aktaran diğer geliştiricilerin neyi içe aktardıklarını kolayca anlamalarını sağlar.

// Kötü
export const f = (a, b) => a + b;

// İyi
export const addNumbers = (a: number, b: number): number => a + b;

Tek Varsayılan Export Kuralı (İsteğe Bağlı)

Bazı geliştirme ekipleri, bir modülün yalnızca tek bir varsayılan export'a sahip olmasını tercih eder. Bu, modülün "ana" işlevini netleştirir ve içe aktarma sözdizimini basitleştirir.

// component.ts
interface Props { / ... / }
export default function MyComponent(props: Props) { / ... / }

// app.ts
import MyComponent from './component';

Ancak, adlandırılmış exportlar da birçok senaryoda oldukça kullanışlıdır, özellikle bir modülden birden fazla ilgili işlevselliği dışa aktarırken. Karar, projenin ve ekibin tercihine bağlıdır.

Dairesel Bağımlılıklar (Circular Dependencies) ve Kaçınma Yolları

Dairesel bağımlılık, Modül A'nın Modül B'ye, Modül B'nin de Modül A'ya bağımlı olması durumudur. Bu durum, uygulamanın beklenmedik şekillerde davranmasına, çalışma zamanı hatalarına veya performansı düşürmesine neden olabilir.

// moduleA.ts
import { bFunction } from './moduleB'; // A -> B
export function aFunction() {
    console.log('aFunction called');
    bFunction();
}

// moduleB.ts
import { aFunction } from './moduleA'; // B -> A (Dairesel bağımlılık!)
export function bFunction() {
    console.log('bFunction called');
    // aFunction(); // Bu satır Stack Overflow'a neden olabilir!
}

Kaçınma Yolları:
1. Yeniden Yapılandırma: Kodu yeniden düzenleyerek bağımlılıkları tek yönlü hale getirin. Ortak işlevselliği üçüncü bir modüle taşıyın.
2. Gecikmeli Yükleme: Gerekirse, dinamik importlar (import()) kullanarak bağımlılıkları çalışma zamanında yükleyin.
3. Arayüzleri Ayırma: Arayüzleri ve somut uygulamaları farklı modüllere ayırın.

Test Edilebilirlik ve Modüller

İyi modüler tasarım, kodun test edilebilirliğini artırır. Her modül bağımsız bir birim olarak test edilebilir. Bağımlılıkları kolayca mock'layabilir veya stub'layabilirsiniz. Bu, birim testlerinin yazılmasını ve sürdürülmesini kolaylaştırır.

Sonuç: Modern TypeScript Geliştirmede Modüllerin Vazgeçilmezliği

TypeScript'te modüller, modern ve ölçeklenebilir uygulamalar geliştirmenin temel taşıdır. Kodun organizasyonunu, okunabilirliğini ve bakımını iyileştirerek geliştirme sürecini daha verimli hale getirirler. import ve export anahtar kelimeleriyle başlayan bu yolculuk, CommonJS ve ES Modülleri gibi farklı modül sistemlerini anlamayı, tsconfig.json dosyasındaki module, moduleResolution, esModuleInterop gibi kritik ayarları yapılandırmayı ve dinamik importlar veya modül bildirimleri gibi gelişmiş özellikleri kullanmayı içerir.

Doğru modül stratejisini seçmek, tsconfig.json ayarlarını doğru bir şekilde yapılandırmak ve en iyi uygulamaları takip etmek, TypeScript projelerinizin sağlam, sürdürülebilir ve performanslı olmasını sağlayacaktır. Modüller, sadece kodunuzu parçalara ayırmaktan öte, daha iyi bir mimari, daha kolay işbirliği ve daha hızlı geliştirme döngüleri sunar. Dolayısıyla, TypeScript ile çalışan her geliştiricinin modül kavramlarını derinlemesine anlaması ve ustaca kullanması modern yazılım geliştirmenin vazgeçilmez bir parçasıdır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık