pnpm Catalogs: Monorepo'da Dependency Drift Çözümü
pnpm catalog'ları monorepo'larda dependency drift'i nasıl durduruyor: paylaşılan versiyonlar için tek dosya, legacy paketler için named catalog, CI'da enforcement.
JavaScript monorepo’larında dependency drift sessizce başlar. Bir service lodash’i ^4.17.20, diğeri ^4.17.21 olarak tanımlar; installer ikisini de kurar. Haftalar sonra ortak bir helper, hangi service’in import ettiğine göre farklı davranır ve hata beş makineden yalnızca birinde tekrarlanır.
pnpm catalog’ları bu açığı kaynağında kapatır. Paylaşılan her versiyon pnpm-workspace.yaml içinde bir kez tanımlanır, paketler ona catalog: ile referans verir ve bir paket versiyonu satır içinde yazdığında CI kontrolü build’i düşürür. Bedeli şu: tek bir dosya ortak bir darboğaza dönüşür. react için tam olarak istediğiniz şey budur; yalnızca tek bir service’in kullandığı bir parser için ise tam olarak istemediğiniz şeydir.
Dependency Drift Sorunu
Drift tek bir hatalı versiyon değildir. Versiyonun nerede kararlaştırıldığına dair bir yerin olmamasıdır. Her package.json kendi otoritesidir; bu yüzden bir authentication service, type’larını import ettiği payment service’ten farklı bir @types/node üzerinde kalabilir. Toolchain’de hiçbir şey itiraz etmez, ta ki bir build iki paketin de adını anmayan bir type hatasıyla düşene kadar.
Workspace araçları bu açığı kapatmaz. Local paketleri linklemek ve ortak olanları hoist etmek yalnızca tanımlı bir aralığın nasıl çözüldüğünü değiştirir. Her paket yine kendi versiyon aralığını tanımlar.
Pratikte Drift
İki service, iki package.json ve ortak tek bir kütüphane:
// packages/service-a/package.json
{
"dependencies": {
"lodash": "^4.17.21",
"axios": "^1.6.2"
}
}
// packages/service-b/package.json
{
"dependencies": {
"lodash": "^4.17.20", // Farklı versiyon!
"axios": "^1.6.5" // Farklı versiyon!
}
}
Burada geçersiz bir şey yok. İki aralık da çözülür, iki service de build alır ve store lodash’in iki kopyasını tutar. Bunu birkaç düzine pakete yayın; node_modules aynı kütüphaneyi aynı anda üç dört versiyonda taşır. Kimse yeni bir özellik eklemeden bundle’ın büyümesinin yolu budur.
Geleneksel Yaklaşımların Sınırları
npm workspaces, Yarn workspaces, Lerna ve Rush monorepo’yu yönetir. Hiçbiri versiyon kararının sahibi değildir:
| Yetenek | npm Workspaces | Yarn Workspaces | pnpm Catalogs |
|---|---|---|---|
| Paylaşılan versiyon için tek tanım yeri | overrides zorlar, tanımlamaz | resolutions zorlar, tanımlamaz | Var |
| Phantom dependency’leri engeller | Hayır | PnP ile | Var |
| Güncelleme tek dosyaya dokunur | Hayır | Hayır | Var |
| Paket grubuna göre ayrı versiyon | Hayır | Hayır | Named catalog |
Build log’unda hiç görünmeyen maliyet dikkattir. Her versiyon uyumsuzluğu bir kesintidir: biri iki lockfile diff’ini okur, bir kazanan seçer ve CI’ı yeniden çalıştırır. İş zor değildir; bu yüzden hiç önceliklenmez ve repo büyüdükçe geri gelmeye devam eder.
Merkezi Versiyon Yönetiminden Beklenenler
Bir governance katmanı ancak şu beşini birden karşılıyorsa yerini hak eder:
- Paylaşılan her versiyon için, her paketin okuyabildiği tek bir tanım
- Tanım eksik olduğunda hoist edilmiş bir kopyaya sessizce düşmemek
- Her
package.jsonyerine tek dosyaya dokunan bir güncelleme - Henüz güncel versiyona geçemeyen paketler için bir kaçış yolu
- İnsanların hatırlaması beklenen bir konvansiyon değil, CI’ı düşüren bir kontrol
pnpm Catalog’ların Uygulanması
Catalog’lar pnpm v9.5 ile geldi ve v10 hattı boyunca stabil. Konfigürasyon workspace dosyasında durur:
# pnpm-workspace.yaml
packages:
- 'apps/**'
- 'packages/**'
- 'services/**'
catalog:
# Core dependency'ler
typescript: ^5.9.2
lodash: ^4.17.21
axios: ^1.6.5
# React ekosistemi (stabil versiyonlar kullanarak)
react: ^18.3.1
react-dom: ^18.3.1
"@types/react": ^18.3.3
# Testing
vitest: ^1.2.0
"@testing-library/react": ^14.1.2
Artık individual paketleriniz sadece catalog’a referans veriyor:
{
"name": "@mycompany/user-service",
"dependencies": {
"lodash": "catalog:",
"axios": "catalog:",
"react": "catalog:"
}
}
Versiyon artık tek bir yerde durur ve onu uygulayan şey pnpm install’dır. "lodash": "^4.17.19" satırını elle yazan bir paket tartışmayı kazanamaz; 3. adımda eklenen kontrolde takılır.
Üç Adımda Migration
Aşağıdaki adımların her biri tek başına devreye alınabilir. Hiçbiri repo’yu dondurmayı gerektirmez.
Adım 1: Otomatik Catalog Oluşturma
Halihazırda tanımlı olandan başlayın. Bu script tüm monorepo’yu gezer ve catalog’un ilk taslağını üretir:
// scripts/migrate-to-catalogs.js
const fs = require('fs');
const yaml = require('js-yaml');
const glob = require('glob');
// Tüm unique dependency'leri topla
const dependencies = new Map();
glob.sync('**/package.json', {
ignore: ['**/node_modules/**', '**/dist/**']
}).forEach(file => {
const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
Object.entries(pkg.dependencies || {}).forEach(([name, version]) => {
if (!dependencies.has(name) || dependencies.get(name) < version) {
dependencies.set(name, version);
}
});
});
// Catalog konfigürasyonu oluştur
const catalog = Object.fromEntries(dependencies);
// pnpm-workspace.yaml'ı güncelle
const workspace = yaml.load(fs.readFileSync('pnpm-workspace.yaml', 'utf8'));
workspace.catalog = catalog;
fs.writeFileSync('pnpm-workspace.yaml', yaml.dump(workspace));
console.log(`${dependencies.size} dependency catalog'a migrate edildi`);
Yukarıdaki karşılaştırma versiyonları string olarak alfabetik sıralar; bu yüzden ^4.17.9, ^4.17.21’in önüne geçer. Çıktıyı taslak olarak görün ve commit etmeden önce üretilen dosyayı okuyun.
Adım 2: Legacy Service Yönetimi
Her şey hemen en son versiyonlara geçemez. Legacy service’ler React 17’de kalırken yenileri React 18 kullanabilir. Named catalog’lar bir çözüm sağlar:
catalogs:
# React 17'deki legacy service'ler
legacy:
react: ^17.0.2
react-dom: ^17.0.2
"@types/react": ^17.0.39
# React 18'deki modern service'ler
modern:
react: ^18.3.1
react-dom: ^18.3.1
"@types/react": ^18.3.3
Her paket ihtiyacı olan catalog’u seçer:
{
"name": "@mycompany/legacy-dashboard",
"dependencies": {
"react": "catalog:legacy",
"react-dom": "catalog:legacy"
}
}
Böylece güncelleme, repo geneline yayılan bir olay olmaktan çıkar; hazır olan paketteki tek satırlık bir değişikliğe dönüşür.
Adım 3: Enforcement ve Validation
Enforcement’ı olmayan bir catalog yalnızca bir isimlendirme konvansiyonudur. Bu script CI’da çalışır ve satır içi yazılmış her versiyonda build’i düşürür:
// scripts/validate-catalogs.js
const validateCatalogs = () => {
const violations = [];
glob.sync('**/package.json', {
ignore: ['**/node_modules/**']
}).forEach(file => {
const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
Object.entries(pkg.dependencies || {}).forEach(([name, version]) => {
// workspace protocol ve local paketleri atla
if (version.startsWith('workspace:') || version.startsWith('file:')) {
return;
}
// Catalog kullanmalı mı kontrol et
if (!version.startsWith('catalog:')) {
violations.push(`${file}: ${name}@${version} catalog kullanmalı`);
}
});
});
if (violations.length > 0) {
console.error('Catalog ihlalleri bulundu:', violations);
process.exit(1);
}
console.log('Tüm dependency'ler catalog protocol kullanıyor');
};
validateCatalogs();
Migration Sonrası Ne Değişir
Kazanım iki mekanizmadan gelir.
Birincisi pnpm’in content-addressable store’u. Bir paketin belirli bir versiyonu diske bir kez yazılır ve ihtiyacı olan her projeye hard link ile bağlanır. Catalog’lar bu store’un işini artırır: her workspace lodash’i aynı kayıt üzerinden çözdüğünde, neredeyse aynı üç kopya yerine linklenecek tek kopya kalır.
İkincisi diff’in biçimi. Bir dependency güncellemesi eskiden o paketi tanımlayan her package.json’a dokunuyordu. Artık pnpm-workspace.yaml’a ve lockfile’a dokunuyor. Aynı hafta farklı kütüphaneleri güncelleyen iki engineer aynı satırlarda çakışmayı bırakıyor.
Migration’dan önce kendi başlangıç ölçümünüzü alın: soğuk kurulum süresi, sıcak kurulum süresi, node_modules boyutu ve ayda yalnızca bir versiyon string’ini değiştirmek için açılan PR sayısı. Migration’ın sizin repo’nuzda işe yarayıp yaramadığını söyleyen bu dört sayıdır ve başlamadan önce toplamak ucuzdur.
Yaygın Hatalar ve Çözümler
Aşırı Merkezileştirme Tuzağı
Yaygın bir hata her dependency’yi catalog’a koymaktır. Tek bir service’in kullandığı özel bir parser veya build aracı oraya ait değildir; yalnızca ilgisiz takımların gözden geçirmek zorunda kaldığı bir dosya büyütür. Catalog’u birden fazla pakette görünen dependency’lere ayırın.
Phantom Dependency Sürprizi
Migration sırasında service’ler bazen hoist edilmiş bir dependency’ye dayandıkları için kırılır. Kod, node_modules içinde bulunan ama kendi package.json’ında hiç tanımlanmamış bir paketi import eder; daha sıkı yerleşim bunu görünür kılar.
Hata CI’da değil yerelde çıksın diye development’ta strict mode ile çalışın:
# .npmrc
strict-peer-dependencies=true
shamefully-hoist=false
Publishing Tuzağı
Bir paket publish edildiğinde catalog: protokolü çözülmüş versiyonla değiştirilir; çünkü workspace dışındaki tüketicilerin okuyabileceği bir catalog yoktur. Yani publish edilen manifest, diff’te kimsenin gözden geçirmediği bir versiyon taşıyabilir.
Release job’ı çalışmadan önce paketlenen manifest’i CI’da kontrol edin:
# .github/workflows/publish.yml
- name: Published paketi validate et
run: |
npm pack
tar -xzf *.tgz
cat package/package.json | jq '.dependencies'
Gelişmiş Uygulama Pattern’leri
Multi-Environment Catalog’lar
Named catalog’lar legacy ayrımının ötesinde ikinci bir sorunu daha çözer. Service’ler farklı Node runtime’larına deploy ediliyorsa, type paketleri repo’yu değil runtime’ı takip etmek zorundadır:
catalogs:
# Node 20 runtime'ında kalan service'ler
node20:
"@types/node": ^20.19.0
# Node 22'ye geçmiş service'ler
node22:
"@types/node": ^22.7.0
Bir service kendini "@types/node": "catalog:node20" ile sabitler; runtime güncellemesi de repo genelinde arama yapmak yerine o pakette tek kelimelik bir değişikliğe dönüşür.
Otomatik Dependency Güncellemeleri
Catalog kayıtları tek dosyada durduğu için Renovate bunları tek bir zamanlanmış PR’da gruplayabilir:
// renovate.json
{
"extends": ["config:recommended"],
"packageRules": [
{
"matchFileNames": ["pnpm-workspace.yaml"],
"groupName": "catalog dependency'leri",
"schedule": ["every weekend"]
}
]
}
Bir versiyon güncellemesi, o paketi tanımlayan her paket için ayrı PR yerine pnpm-workspace.yaml üzerinde tek bir PR’a dönüşür.
Baştan Verilecek Kararlar
Beş karar şimdi daha ucuz, sonra daha pahalı:
-
Catalog’lara repo büyümeden geçin. Retrofit maliyeti, halihazırda versiyon tanımlayan paket sayısıyla ölçeklenir.
-
Strict mode’u baştan açın. Esnek bir kurulum, phantom dependency’leri migration hepsini bir anda ortaya dökene kadar gizler.
-
Named catalog’ları ihtiyaç duymadan önce tasarlayın. Tek catalog’u sonradan bölmek, ikinci grupta olması gereken her pakete dokunmak demektir.
-
Validation script’ini ilk catalog’la birlikte yazın. Benimsemeden sonra eklenen enforcement, her zaman çoktan merge edilmiş ihlaller bulur.
-
İstisnaların gerekçesini dokümante edin.
catalog:protokolü kendini anlatır; bir paketin neden catalog dışında sabitlendiği ise anlatmaz.
Migration Checklist’i
Değerlendirme
- Tüm paketlerde tanımlı dependency’leri denetle
- Birden fazla versiyonda tanımlanmış her paketi listele
- Soğuk kurulum süresini, sıcak kurulum süresini ve
node_modulesboyutunu kaydet
Pilot
- pnpm v10 veya üstünü kur
- En çok paylaşılan iki üç dependency’yi
catalog:’a taşı - Başka bir şey migrate etmeden önce CI’a catalog validation ekle
- Taşınamayan paketleri ve nedenlerini yaz
Ölçekle
- Oluşturma script’ini kalan paketlerde çalıştır
- Runtime veya major versiyon farkı olan yerlerde named catalog’lara ayır
- Renovate’i
pnpm-workspace.yaml’a yönlendir
Sıkılaştır
-
shamefully-hoist=falseyap ve kırılanları düzelt - Yalnızca tek paketin kullandığı catalog kayıtlarını kaldır
- Başlangıç ölçümüyle yeniden karşılaştır
Sonuç
pnpm workspace’iniz dependency paylaşan birkaç paketten fazlasını barındırıyorsa, doğru varsayılan catalog’lardır. Kazanç, aynı framework’ün, aynı test runner’ının ve aynı type paketlerinin her package.json’da göründüğü yerde en yüksektir.
Atlamak için iki durum var. Tek paketli bir repo’nun paylaşılan versiyon dosyasından kazanacağı bir şey yoktur. Paketleri bağımsız olarak, bilinçli biçimde farklı destek pencerelerine sahip tüketicilere publish edilen bir workspace ise catalog’la sürekli çekişir; orada paket başına aralık ve güvenlik yamaları için hedefli bir override daha az sürtünme yaratır.
Migration’a başlarsanız tüm dependency listesiyle değil, her yerde görünen iki üç paketle başlayın. Validation script’i catalog’un kendisinden daha önemlidir: CI’da bir kontrol yoksa, satır içi versiyonlar birinin acelesi olduğu ilk anda geri gelir.
Kaynaklar
- pnpm Katalog Dokümantasyonu - pnpm-workspace.yaml dosyasında yeniden kullanılabilir bağımlılık versiyonu sabitleri tanımlamak için resmi pnpm katalog özelliği rehberi.
- pnpm Çalışma Alanları Dokümantasyonu - pnpm’in yerleşik monorepo desteğine yönelik resmi rehber; workspace protokolü ve paket bağlantısını kapsar.
- pnpm-workspace.yaml Referansı - Katalog ve workspace tanımları dahil pnpm-workspace.yaml yapılandırma dosyası için tam referans.
- pnpm Paket Yöneticisi - Disk kullanımını azaltan içerik adreslenebilir depo ve hard-link yaklaşımını kapsayan resmi pnpm dokümantasyonu.
- pnpm Paket Kaynakları - pnpm’nin paketleri nasıl çözdüğüne dair dokümantasyon; catalog: protokolünü ve bağımlılık çözümlemesini anlamak için yararlı.
- Renovate Yapılandırma Seçenekleri - Yukarıda catalog güncellemelerini tek PR’da gruplamak için kullanılan
packageRules,matchFileNamesve zamanlama referansı.
İlgili yazılar
Nub ve Vite+, 2026'nın oxc tabanlı iki Rust araç zinciri; rakip gibi görünüp öyle değiller. Hangi ikilinin hangi repoda yeri olduğuna dair net bir kural.
Sunucu tarafı micro-frontend kompozisyonu, sayfa parçalarının sahipliğini bağımsız ekiplere verir. Zor kısım sahiplik sınırı ve sürümlenmiş tutarlılık sözleşmesidir.
AWS Lambda'da Node.js'den Go'ya geçiş ne zaman kendini amorti eder, ne zaman etmez: karar çerçevesi, serverless Go pattern'ları ve maliyet matematiği.
Production zaman hatalarının kökleri, Moment.js'den Day.js ve date-fns'e geçiş ve her yerde UTC kullanıp dönüşümü görüntüleme sınırına bırakma.
Yönlendirme motoru, analytics toplama ve API Gateway yapılandırması: günlük milyonlarca yönlendirme için performans optimizasyonları ve debugging stratejileri.