Proje Yapılandırma
Neden Yapılandırma Önemli?
Bir ev inşa ederken önce temel atılır, sonra duvarlar çıkar, sonra çatı kapatılır. Temeli sağlam olmayan ev, güzel boyansa bile güvenilmez. TypeScript projelerinde tsconfig.json o temeldir. Yanlış yapılandırılmış bir proje, derlense bile hataları gizler, performans sorunları yaratır ve ekip içinde tutarsızlıklara yol açar.
Bu derste tsconfig.json'un her köşesini keşfedeceğiz — strict mode'un neden "açık" olması gerektiğini, path alias'ların nasıl büyük projelerde okunurluğu artırdığını, .d.ts dosyalarının kütüphane dünyasındaki rolünü ve monorepo yapılarında TypeScript'i nasıl yönetebileceğini öğreneceğiz.
tsconfig.json Detaylı İnceleme
tsconfig.json, TypeScript derleyicisine (tsc) projenin kurallarını söyler. Hangi dosyalar derlenmeli? Hangi JavaScript sürümü hedeflenmeli? Tip kontrolü ne kadar katı olmalı? Hepsinin cevabı burada.
Temel Yapı
{
"compilerOptions": {
// Derleme hedefi ve modül sistemi
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// Çıktı ayarları
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true,
// Tip kontrolü
"strict": true,
"noUncheckedIndexedAccess": true,
// Modül interop
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
// İleri ayarlar
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}Bu çok fazla seçenek gibi görünüyor, ama her birinin bir nedeni var. Gruplara ayırarak inceleyelim.
target ve lib
target, TypeScript'in hangi ECMAScript sürümüne derleneceğini belirler. lib ise hangi yerleşik API'lerin tip tanımlarının dahil edileceğini seçer:
{
"compilerOptions": {
// Modern tarayıcılar hedefleniyorsa
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// Node.js backend ise (DOM gerekmez)
// "target": "ES2022",
// "lib": ["ES2022"],
// Eski tarayıcı desteği gerekiyorsa
// "target": "ES5",
// "lib": ["ES5", "DOM", "ScriptHost"],
}
}| target | Özellikler | Kullanım |
|---|---|---|
| ES5 | var, function | IE11 desteği |
| ES2015 | let/const, class, arrow, Promise | Eski Android/iOS |
| ES2020 | BigInt, optional chaining, nullish coalescing | Modern tarayıcılar |
| ES2022 | top-level await, class fields, at() | Güncel projeler |
| ESNext | En son özellikler | Bundler kullanıyorsan |
💡 İpucu: Bundler (Vite, esbuild) kullanıyorsan
target: "ESNext"güvenle kullanabilirsin — dönüştürmeyi bundler yapar. Doğrudantscile derliyorsan hedef ortamına uygun seç.
module ve moduleResolution
Modül sistemi ve modül çözümleme stratejisi — bu ikisi birlikte çalışır:
{
"compilerOptions": {
// Modern projeler için önerilen
"module": "ESNext",
"moduleResolution": "bundler",
// Node.js (ESM) için
// "module": "NodeNext",
// "moduleResolution": "NodeNext",
// Node.js (CommonJS) için
// "module": "CommonJS",
// "moduleResolution": "node",
}
}moduleResolution: "bundler" TypeScript 5.0+ ile geldi ve şunu söylüyor: "Ben bir bundler (Vite, webpack) kullanıyorum, modül çözümleme kurallarını ona göre gevşet." Bu sayede import'larda uzantı yazmak zorunda kalmazsın.
Strict Mode: Neden Her Zaman Açık Olmalı
"strict": true tek bir bayrakla birçok katı kontrolü açar. Bunu emniyet kemeri olarak düşün — rahatsız edebilir ama kaza anında hayat kurtarır.
strict Neyi Açar?
{
"compilerOptions": {
"strict": true
// Aşağıdakilerin HEPSİNİ açar:
// "noImplicitAny": true,
// "strictNullChecks": true,
// "strictFunctionTypes": true,
// "strictBindCallApply": true,
// "strictPropertyInitialization": true,
// "noImplicitThis": true,
// "useUnknownInCatchVariables": true,
// "alwaysStrict": true
}
}Her birinin ne yaptığını ve neden önemli olduğunu görelim:
noImplicitAny
// ❌ strict: false — parametre tipi yok, any kabul edilir
function greet(name) { // name: any (gizli tehlike!)
console.log(name.toFixed()); // Runtime'da patlayabilir
}
// ✅ strict: true — tip belirtmek ZORUNLU
function greet(name: string) {
console.log(name.toUpperCase()); // Güvenli ✅
}strictNullChecks
// ❌ strict: false — null/undefined tehlikesi gizlenir
function getUser(id: number): User {
return database.find(u => u.id === id); // undefined dönebilir ama tip User diyor!
}
const user = getUser(999);
console.log(user.name); // 💥 Runtime: Cannot read property 'name' of undefined
// ✅ strict: true — null/undefined olasılığı görünür
function getUser(id: number): User | undefined {
return database.find(u => u.id === id);
}
const user = getUser(999);
// console.log(user.name); // ❌ Derleme hatası: user undefined olabilir
console.log(user?.name ?? "Bilinmiyor"); // ✅ Güvenli erişimstrictPropertyInitialization
// ❌ strict: false — property başlatılmayabilir
class UserService {
db: Database; // Başlatılmamış ama hata yok — runtime'da patlayacak
getUsers() {
return this.db.query("SELECT * FROM users"); // 💥
}
}
// ✅ strict: true — ya başlat ya da assertion kullan
class UserService {
db: Database;
constructor(db: Database) {
this.db = db; // ✅ Constructor'da başlatıldı
}
// Alternatif: kesinlikle sonra atanacağını biliyorsan
// db!: Database; // definite assignment assertion (dikkatli kullan!)
}Ekstra Strict Bayraklar (strict'e dahil değil ama önerilen)
{
"compilerOptions": {
"strict": true,
// Ekstra güvenlik katmanları
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true
}
}// noUncheckedIndexedAccess — dizi/nesne erişiminde undefined olasılığı
const names: string[] = ["Ali", "Veli"];
const first = names[0]; // string | undefined (string değil!)
// Bu güvenlik katmanı array out-of-bounds hatalarını önler
// noImplicitOverride — override edilen metotlarda explicit işaret
class Animal {
move() { console.log("hareket"); }
}
class Dog extends Animal {
override move() { console.log("koşuyor"); } // ✅ override keyword zorunlu
}⚠️ Dikkat: Mevcut bir projeye
strict: trueeklemek yüzlerce hata üretebilir. Yeni projede her zaman strict başla. Mevcut projede kademeli geçiş yap: öncestrictNullChecks, sonranoImplicitAny, sonra diğerleri.
Path Aliases: Temiz Import Yolları
Büyük projelerde dosyalar derinleşir. Relative import'lar çirkinleşir:
// ❌ Göreceli yol cehennemi
import { UserService } from "../../../services/user/UserService";
import { Database } from "../../../../core/database/Database";
import { Logger } from "../../../utils/Logger";
// Dosyayı taşıdığında tüm import'lar bozulur 😱Path alias'lar bu sorunu çözer:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@services/*": ["src/services/*"],
"@models/*": ["src/models/*"],
"@utils/*": ["src/utils/*"],
"@config": ["src/config/index.ts"]
}
}
}// ✅ Temiz, okunabilir import'lar
import { UserService } from "@services/user/UserService";
import { Database } from "@/core/database/Database";
import { Logger } from "@utils/Logger";
import config from "@config";
// Dosyayı taşısan bile import yolları DEĞİŞMEZBundler/Runtime ile Entegrasyon
Path alias'lar sadece TypeScript'e yol gösterir — runtime'da veya bundler'da da tanımlanmalı:
Vite:
// vite.config.ts
import { defineConfig } from "vite";
import path from "path";
export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@services": path.resolve(__dirname, "./src/services"),
"@utils": path.resolve(__dirname, "./src/utils"),
},
},
});Node.js (tsconfig-paths):
npm install -D tsconfig-paths// package.json
{
"scripts": {
"dev": "ts-node -r tsconfig-paths/register src/index.ts"
}
}Jest:
// jest.config.js
module.exports = {
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
"^@services/(.*)$": "<rootDir>/src/services/$1",
},
};💡 İpucu: Path alias'ları tek bir yerde tanımlayıp diğer araçlara otomatik aktarmak için
tsconfig.json'u tek kaynak (source of truth) olarak kullan. Vite içinvite-tsconfig-pathseklentisi bunu otomatik yapar.
Declaration Files (.d.ts)
.d.ts dosyaları TypeScript'in tip tanım dosyaları — JavaScript kütüphanelerine tip bilgisi ekler. Bir kütüphanenin "kullanım kılavuzu" gibi düşün: kodun kendisini görmezsin, ama ne yapabileceğini bilirsin.
Neden .d.ts?
JavaScript dünyasında milyonlarca npm paketi var. Çoğu TypeScript'te yazılmamış. .d.ts dosyaları bu paketlere tip elbisesi giydirir:
// lodash.d.ts — lodash kütüphanesinin tip tanımı (basitleştirilmiş)
declare module "lodash" {
export function chunk<T>(array: T[], size: number): T[][];
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): T;
export function get(object: any, path: string, defaultValue?: any): any;
// ... yüzlerce fonksiyon daha
}DefinitelyTyped (@types)
Topluluk tarafından yönetilen, binlerce kütüphanenin tip tanımlarını içeren dev bir repo:
# JavaScript kütüphanesi için tip tanımı yükle
npm install -D @types/lodash
npm install -D @types/express
npm install -D @types/node
npm install -D @types/react
# Not: TypeScript'te yazılmış kütüphaneler (axios, zod, prisma...)
# kendi .d.ts dosyalarını içerir — @types gerekmezKendi .d.ts Dosyanı Yazma
Bazen kendi tip tanımlarına ihtiyacın olur — özellikle JavaScript modüllerini, global değişkenleri veya ortam değişkenlerini tanımlamak için:
// src/types/env.d.ts — Ortam değişkenlerini tanımla
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
DATABASE_URL: string;
API_KEY: string;
PORT?: string;
}
}
// Artık process.env otomatik tamamlama ve tip kontrolü sağlar
const dbUrl = process.env.DATABASE_URL; // string ✅
const env = process.env.NODE_ENV; // "development" | "production" | "test" ✅// src/types/assets.d.ts — Dosya import'ları için tip tanımı
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.png" {
const content: string;
export default content;
}
declare module "*.css" {
const classes: Record<string, string>;
export default classes;
}
// Artık dosya import'ları tip güvenli
import logo from "./logo.svg"; // string ✅
import styles from "./App.module.css"; // Record<string, string> ✅// src/types/global.d.ts — Global değişken tanımı
declare global {
interface Window {
__APP_VERSION__: string;
analytics: {
track(event: string, data?: Record<string, any>): void;
};
}
// Global fonksiyon (nadiren kullanılır ama bazen gerekir)
function __DEV__(): boolean;
}
export {}; // Bu dosyayı modül yap (global augmentation için gerekli)declaration ve declarationMap
Kendi kütüphaneni yayınlarken .d.ts otomatik üretimi:
{
"compilerOptions": {
"declaration": true, // .d.ts dosyaları üret
"declarationMap": true, // .d.ts → .ts kaynak eşleştirmesi
"declarationDir": "./types", // .d.ts çıktı klasörü
"emitDeclarationOnly": true // Sadece .d.ts üret (JS üretme)
}
}// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
// Otomatik üretilen: types/utils/math.d.ts
// export declare function add(a: number, b: number): number;
// export declare function multiply(a: number, b: number): number;Monorepo TypeScript Yapılandırması
Büyük projelerde birden fazla paket tek bir repo'da yaşar — buna monorepo denir. Her paketin kendi tsconfig.json'u olur, ama ortak ayarlar base config'den miras alınır.
Monorepo Klasör Yapısı
my-monorepo/
├── tsconfig.base.json ← Ortak ayarlar
├── packages/
│ ├── shared/ ← Paylaşılan tipler ve utils
│ │ ├── src/
│ │ ├── tsconfig.json ← extends base
│ │ └── package.json
│ ├── api/ ← Backend
│ │ ├── src/
│ │ ├── tsconfig.json ← extends base
│ │ └── package.json
│ └── web/ ← Frontend
│ ├── src/
│ ├── tsconfig.json ← extends base
│ └── package.json
└── package.jsonBase Config
// tsconfig.base.json — tüm paketlerin ortak ayarları
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true,
"incremental": true
}
}Paket Config'leri
// packages/shared/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}// packages/api/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"references": [
{ "path": "../shared" }
]
}// packages/web/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"references": [
{ "path": "../shared" }
]
}Project References ve composite
"references" ve "composite" birlikte çalışır:
composite: true→ Paket bağımsız olarak derlenebilirreferences→ Paketler arası bağımlılık bildirimitsc --build→ Bağımlılık sırasına göre derleme
# Tüm projeyi bağımlılık sırasına göre derle
tsc --build packages/web/tsconfig.json
# Derleme sırası: shared → api → web
# Sadece değişen paketler yeniden derlenir (incremental)💡 İpucu: Monorepo araçları (Turborepo, Nx, pnpm workspaces) ile birlikte
project referenceskullanmak derleme süresini dramatik olarak kısaltır. 100+ dosyalık projelerde fark belirgindir.
pnpm/npm Workspaces ile Birlikte
// package.json (kök)
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*"
]
}// packages/api/package.json
{
"name": "@myapp/api",
"dependencies": {
"@myapp/shared": "workspace:*"
}
}// packages/api/src/index.ts — shared paketinden import
import { User, validateEmail } from "@myapp/shared";
const user: User = {
name: "Ali",
email: validateEmail("ali@test.com"),
};Yaygın Hatalar
1. include/exclude Unutmak
// ❌ include belirtilmemiş — tüm .ts dosyaları derlenir (node_modules dahil!)
{
"compilerOptions": { "strict": true }
}
// ✅ Kapsamı belirle
{
"compilerOptions": { "strict": true },
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}2. Path Alias'ı Runtime'da Çözmemek
// tsconfig.json'da paths tanımlı ama runtime'da çalışmıyor
// ❌ Error: Cannot find module '@utils/Logger'
// Çözüm: Runtime/bundler'da da alias tanımla
// Vite → vite.config.ts resolve.alias
// Jest → moduleNameMapper
// Node → tsconfig-paths3. skipLibCheck Kullanmamak
// ❌ node_modules içindeki .d.ts hatalarından dolayı derleme başarısız
{
"compilerOptions": {
"skipLibCheck": false // Tüm .d.ts dosyalarını kontrol eder
}
}
// ✅ Kendi kodunu kontrol et, kütüphane tiplerini atla
{
"compilerOptions": {
"skipLibCheck": true // Derleme hızını da artırır
}
}4. isolatedModules Yanlış Anlamak
{
"compilerOptions": {
"isolatedModules": true // Bundler (esbuild, swc) uyumluluğu
}
}// ❌ isolatedModules ile sorun yaratan pattern'lar
// Re-export type (sadece tip, değer yok)
export { User } from "./types"; // Hata! Bundler bunu silebilir
export type { User } from "./types"; // ✅ Doğru
// const enum (runtime'da yok)
const enum Direction { Up, Down } // Hata!
enum Direction { Up, Down } // ✅ Normal enum kullanGerçek Dünya: Tam Proje Yapılandırması
Modern bir full-stack TypeScript projesi için önerilen yapılandırma:
// tsconfig.json — production-ready
{
"compilerOptions": {
// Hedef
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// Çıktı
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true,
// Strict — tamamı açık
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
// Modül interop
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true,
// Path aliases
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
// React (frontend ise)
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}Özet
tsconfig.json, TypeScript projesinin temel yapılandırma dosyasıdır — target, module, strict ayarları burada belirlenir.
strict: true her zaman açık olmalı —
noImplicitAny,strictNullChecksgibi kritik kontrolleri kapsar. Yeni projede açık başla, mevcut projede kademeli geçiş yap.Path aliases (
@/,@services/) import yollarını temizler, dosya taşımada kırılmayı önler. Runtime/bundler'da da tanımlanmalı..d.ts dosyaları JavaScript kütüphanelerine tip tanımı ekler.
@types/*paketleri, ortam değişkenleri, global tipler için kullanılır.Monorepo'larda
extendsile ortak ayarlar,referencesile paketler arası bağımlılık,compositeile artımlı derleme yapılır.Her ayarın bir nedeni var — körü körüne kopyalama yerine ne yaptığını bilerek yapılandır.
AI Asistan
Sorularını yanıtlamaya hazır