İçeriğe geç
academia.sh

Ders 22 / 23

Modül Çözümleme

Derleyicinin modül adını dosyaya çevirme kuralları, uzantı gerekliliği, çözümleme stratejileri, yol eşlemenin çıktı üzerindeki sınırı ve paket iç yollarıyla çözüm.

İçindekiler

Önceki dersteki içe aktarma satırı ./eski.js yazıyordu ve uzantı zorunluydu. Orada içe aktarılan dosya gerçekten bir JavaScript dosyasıydı; aynı yazım, kaynak bir TypeScript dosyası olduğunda da değişmez.

Bu, modül çözümlemenin (module resolution) kurallarından biridir: derleyicinin bir modül adını bir dosyaya çevirme süreci. Kurallar moduleResolution ayarına, dosyanın modül türüne ve paketin bildirimine bağlıdır. Bu ders süreci ve tuzaklarını ele alır.

Uzantı Gerekliliği

Bir proje düşünelim. package.json dosyası { "name": "olcum-modulleri", "type": "module", "version": "1.0.0" }, yapılandırma şu olsun:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src"]
}

src/veri/olcum.ts kursun modelini dışa aktarsın:

export interface Olcum {
  kimlik: string;
  deger: number;
}

export function ortalama(kayitlar: readonly Olcum[]): number {
  return kayitlar.length === 0
    ? 0
    : kayitlar.reduce((t, k) => t + k.deger, 0) / kayitlar.length;
}

src/ana.ts içinde üç farklı yazım denenebilir. Uzantısız:

import { ortalama } from "./veri/olcum";

console.log(ortalama([{ kimlik: "s-01", deger: 21.4 }]));
src/ana.ts(1,26): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './veri/olcum.js'?

Kaynak dosyanın gerçek adıyla, yani .ts uzantısıyla:

import { ortalama } from "./veri/olcum.ts";

console.log(ortalama([{ kimlik: "s-01", deger: 21.4 }]));
src/ana.ts(1,26): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

Ve derleyicinin önerdiği biçimde:

import { ortalama } from "./veri/olcum.js";

console.log(ortalama([{ kimlik: "s-01", deger: 21.4 }]));

Bu üçüncü biçim derlenir. Üretilen dist/ana.js:

import { ortalama } from "./veri/olcum.js";
console.log(ortalama([{ kimlik: "s-01", deger: 21.4 }]));

node dist/ana.js çıktısı 21.4tür.

Kural şaşırtıcıdır ve nedeni tip silme ilkesinde yatar: derleyici içe aktarma yollarını değiştirmez. Kaynakta ne yazıyorsa çıktıda o kalır. Çalışacak olan JavaScript dosyası olcum.js olduğuna göre, kaynakta da o yazılmalıdır. .ts uzantısı yazmak, çıktıda bulunmayan bir dosyaya başvurmak demektir.

Bu davranışı değiştiren seçenekler bulunur: .ts uzantısıyla yazmaya izin veren ve gerektiğinde çıktıda uzantıyı yeniden yazan bayraklar. Hangilerinin bulunduğu derleyici sürümüne bağlıdır; ilkeyi bilmek, hangi bayrağın ne yaptığını anlamayı sağlar.

Çözümleme Stratejileri

moduleResolution ayarı, adın hangi kurallarla aranacağını belirler. Üç sınıf vardır.

Çalışma zamanı kuralına uyan stratejiler (node16, nodenext), sunucu çalışma zamanının modül çözümleme algoritmasını taklit eder: uzantı zorunludur, paket bildirimi okunur, dosyanın modül mü betik mi olduğu package.json içindeki tür alanına göre belirlenir. Yukarıdaki tanılar bu sınıftan gelir.

Paketleyici kuralına uyan strateji (bundler), derleme adımında bir paketleyicinin bulunduğunu varsayar. Uzantı gerekmez, çünkü yolu çözecek olan derleyici değil paketleyicidir:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "preserve",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true
  },
  "include": ["src"]
}

Bu yapılandırmayla import { ortalama } from "./veri/olcum"; yazımı hatasız denetlenir.

Eski strateji (node10), paket bildirimindeki ayrıntıları okumayan ilk kuşak algoritmadır ve yeni projelerde seçilmez.

Seçim ölçütü şudur: kodu kim çalıştıracaksa onun kuralı seçilir. Çıktı doğrudan bir çalışma zamanına veriliyorsa çalışma zamanı stratejisi, bir paketleyiciden geçiyorsa paketleyici stratejisi. Yanlış seçimin belirtisi, tip denetiminin geçip çalıştırmanın başarısız olmasıdır.

Paket Adları ve Tip Bildirimlerinin Bulunması

Göreli olmayan bir ad — bir paket adı — farklı bir yolla çözülür: derleyici, dosya ağacında yukarı doğru ilerleyerek paket dizinlerini arar ve bulduğu paketin bildirimini okur.

Paket bildirimi, aynı ad için hem çalışma zamanı dosyasını hem tip dosyasını gösterebilir:

{
  "name": "olcum-surucusu",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/surucu.d.ts",
      "default": "./dist/surucu.js"
    }
  }
}

types koşulu derleyiciye, default koşulu çalışma zamanına yanıt verir. Paket dist/surucu.d.ts dosyasında export declare function oku(kanal: string): Promise<number>; bildirimini yayımlıyorsa, tüketici tarafında sözleşme uygulanır:

import { oku } from "olcum-surucusu";

export async function ilkOkuma(): Promise<number> {
  return oku("sicaklik.C");
}

export async function hataliOkuma(): Promise<number> {
  return oku(42);
}
src/ana.ts(8,14): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

Çözümlemenin hangi adımlardan geçtiği sorulabilir. --traceResolution seçeneği bunu yazar; aşağıdaki çıktıda proje kökünün mutlak yolu .../paket biçiminde kısaltılmıştır:

======== Resolving module 'olcum-surucusu' from '.../paket/src/ana.ts'. ========
Loading module 'olcum-surucusu' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.
Found 'package.json' at '.../paket/node_modules/olcum-surucusu/package.json'.
File '.../paket/node_modules/olcum-surucusu/dist/surucu.d.ts' exists - use it as a name resolution result.
======== Module name 'olcum-surucusu' was successfully resolved to '.../paket/node_modules/olcum-surucusu/dist/surucu.d.ts' with Package ID 'olcum-surucusu/dist/[email protected]'. ========

Çıktı, çözümlemenin nerede takıldığını bulmanın en doğrudan yoludur: “bu paketin tipleri neden görünmüyor” sorusu, hangi dosyanın arandığı ve bulunamadığı satırlarla yanıtlanır.

Paket kendi bildirimlerini yayımlamıyorsa iki seçenek kalır. Birincisi, topluluk tarafından ayrı bir pakette yayımlanan bildirimleri kurmaktır; bu paketler bir ad öneki altında toplanır ve derleyici onları kendiliğinden bulur. İkincisi, Bildirim Dosyaları dersindeki ortam bildirimini yazmaktır. İkisi de aynı borcu taşır: bildirim, paketin gerçek davranışıyla elle uyumlu tutulur.

Yol Eşleme ve Sınırı

Derin dizin yapılarında göreli yollar okunaksızlaşır. paths ayarı, ad ile dosya arasında bir eşleme kurar:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "rootDir": "src",
    "outDir": "dist",
    "paths": {
      "@veri/*": ["./src/veri/*"]
    }
  },
  "include": ["src"]
}
import { ortalama } from "@veri/olcum.js";

console.log(ortalama([{ kimlik: "s-01", deger: 21.4 }]));

Bu dosya tip denetiminden hatasız geçer. Üretilen dist/ana.js:

import { ortalama } from "@veri/olcum.js";
console.log(ortalama([{ kimlik: "s-01", deger: 21.4 }]));

node dist/ana.js çalıştırıldığında:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@veri/olcum.js'

Neden bir öncekiyle aynıdır: derleyici içe aktarma yollarını değiştirmez. paths ayarı yalnızca derleyicinin adı çözmesini sağlar; çalışma zamanının aynı eşlemeden haberi yoktur.

Bu, kursun ilk dersinde kurulan ayrımın son ve en pahalı örneğidir. Tip katmanı derleme anına aittir; çalışma zamanı davranışını değiştirmez. paths bir tip katmanı ayarıdır, bir modül sistemi ayarı değil.

Ayrıca paths ile birlikte sık kullanılan baseUrl ayarı bu derleyici sürümünde kullanımdan kaldırılmış sayılır ve yazıldığında TS5101 tanısı gelir. Yol desenleri artık doğrudan yapılandırma dosyasının bulunduğu dizine göre yazılır.

Doğru Çözüm: Paket İç Yolları

Aynı okunabilirlik, çalışma zamanının da bildiği bir yolla elde edilir. Paket bildirimine iç yol eşlemesi yazılır:

{
  "name": "olcum-modulleri",
  "type": "module",
  "version": "1.0.0",
  "imports": {
    "#veri/*": "./dist/veri/*"
  }
}

Yapılandırmadan paths kaldırılır ve içe aktarma şu biçimi alır:

import { ortalama } from "#veri/olcum.js";

console.log(ortalama([{ kimlik: "s-01", deger: 21.4 }]));

Derleme hatasız geçer, dist/ana.js çıktısı içe aktarmayı olduğu gibi korur ve node dist/ana.js çıktısı 21.4 olur.

Fark şudur: #veri/* deseni derleyicinin değil paket bildiriminin bir özelliğidir. nodenext stratejisi paket bildirimini okuduğu için derleyici de aynı eşlemeyi kullanır. Tek bir kaynak, iki tüketici.

Genel ilke: çözümleme kuralı, çalışma zamanının anlayacağı yerde tanımlanır. Yalnızca derleyicinin bildiği bir kural, çıktı ile kaynak arasında bir uçurum açar.

Paketleyici kullanan projelerde durum farklıdır: orada yolları çözen paketleyicidir ve paths ayarı, paketleyicinin kendi eşlemesiyle aynı tutulduğu sürece güvenlidir. İki yerde tutulan bir bilginin ayrışması ise bilinen bir bakım sorunudur.

Özet

  • Derleyici içe aktarma yollarını değiştirmez; kaynakta yazılan ad çıktıda aynen kalır.
  • Çalışma zamanı stratejilerinde göreli yollar uzantı ister ve uzantı, kaynağın değil çıktının adıdır; .ts yazımı TS5097, uzantısız yazım TS2835 tanısı verir.
  • Paketleyici stratejisinde uzantı gerekmez, çünkü yolu paketleyici çözer; strateji, kodu çalıştıracak olana göre seçilir.
  • paths ayarı yalnızca derleyicinin çözümlemesini etkiler; çalışma zamanı aynı eşlemeyi bilmediği için çıktı çalışmayabilir.
  • Paket bildirimindeki iç yol eşlemesi hem derleyici hem çalışma zamanı tarafından okunur ve tek kaynaklı bir çözüm verir.

Sonraki Adım

Kurs boyunca derleyici hem tip denetimi yaptı hem çıktı üretti. Bu iki işin ayrılması, büyük projelerde derleme süresini ve dağıtım düzenini belirleyen bir karardır. Son ders bu ayrımı, hata durumunda yayın davranışını ve kursun kurduğu tüm katmanları bir arada ele alacak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat