Merhaba. TypeScript frontend için build time DI kütüphanesi prewire’ı geliştirdim.
Bu kütüphaneyi yapmama neden olan şey, yönettiğim frontend monorepo’nun yapısıydı.
Birden fazla iş kolunun frontend’i tek bir core/kit kodunu paylaşıyor ve mevcut uygulamalar Next.js kullanıyor. İleride bazı iş kollarında TanStack Router’ı denemek istiyordum; ancak bu yüzden paylaşılan core’un doğrudan next/navigation veya @tanstack/react-router’a bağımlı hale gelmesini istemedim.
Her iş kolunun farklı bir implementasyon seçebildiği, buna rağmen paylaşılan core’un değiştirilmediği bir yapıya ihtiyaç vardı.
prewire’da paylaşılan kod önce port ve token’ı bildirir.
export interface RouterPort {
href(to: string): string
push(to: string): void
}
export const ROUTER = new InjectionToken<RouterPort>('router')
Her uygulama bu port’u kullandığı framework ile implemente eder.
export const tanstackRouter = injectable(
{ logger: LOGGER },
({ logger }): RouterPort => ({
href: (to) => to,
push: (to) => {
logger.info(`navigate → ${to}`)
throw redirect({ to })
},
}),
{ provides: ROUTER },
)
Paylaşılan kod dahil tüketici tarafında, implementasyonun nereden geldiğini bilmeye gerek kalmadan aynı yolu import eder.
import { router } from '#prewire'
prewire codegen, uygulama ve paylaşılan paketlerdeki injectable() bildirimlerini statik olarak analiz eder ve bağımlılık sırasına göre düz bir TypeScript composition root üretir.
export const logger = consoleLoggerBinding.factory({})
export const router = tanstackRouterBinding.factory({ logger })
Runtime container, reflect-metadata veya decorator kullanılmaz. Eksik binding, döngüsel bağımlılık, yinelenen binding gibi sorunlar çalışma sırasında değil, kod üretimi aşamasında build hatası olarak ele alınır. Üretilen kod doğrudan okunabilir; gerekirse ayrılıp manuel composition root olarak da kullanılabilir.
Paylaşılan paketin implementasyonlarının uygulama tarafından keyfi biçimde override edilmemesi için override varsayılan olarak kapalıdır. Uygulama yalnızca paylaşılan kodun default: true olarak belirttiği binding’leri değiştirebilir. Niyet olarak Kotlin’deki open’a benzer.
Ayrıca ayrı bir environment ekseni de koydum. live/test, server/client veya iş uygulamasının adı gibi kullanıcının istediği ölçüte göre farklı root’lar oluşturulabilir. Örneğin sunucuya özel bir binding, istemci root’unda sadece çalıştırılmamakla kalmaz; üretilen kodda import’u bile yer almaz.
Mevcut repodaki örneklerde tek bir paylaşılan kit’in aşağıdaki üç uygulamaya farklı şekillerde bağlandığı bir yapı var.
- Next.js
- TanStack Start
- React Router
Build entegrasyonu için Vite·webpack·rspack’e yönelik unplugin ve Next.js için withPrewire() sağlanıyor.
Ancak henüz gerçek ürün koduna dahil etmiş değilim. Önce ayrı bir kütüphane ve örneklerle tasarımı doğrulayıp yayımladığım aşamadayım. Şu anda npm’de 0.1.1 olarak yayımlandı; erken 0.x sürümü olduğu için API değişebilir.
Ayrıca tek uygulama, tek environment ve az sayıda binding olan durumlarda prewire kullanmak için çok büyük bir neden yok. Böyle projelerde tek bir composition root dosyasını elle yazmak daha basit olur. Asıl hedef olarak, aynı paylaşılan kodun birden fazla uygulama·environment·test konfigürasyonunda farklı şekilde bağlanması gereken durumları düşünüyorum.
Ben ağırlıklı olarak backend geliştirdim; frontend monorepo yönetirken bu sorunu DI ve build time kod üretimiyle çözdüm. Bu yüzden çözümün kendisinin epey backend tarzı olduğunu da düşünüyorum.
Frontend’i profesyonel olarak ele alan kişilerin, birden fazla uygulamanın paylaşılan core kullandığı ve router gibi yalnızca framework’e özel implementasyonların farklılaştığı sorunları genelde nasıl çözdüğünü merak ediyorum. prewire gibi build time DI uygun görünüyor mu, yoksa daha basit ya da frontend ekosistemine daha aşina yaklaşımlar varsa görüşlerinizi duymak isterim.
GitHub: https://github.com/clroot/prewire
npm: https://www.npmjs.com/package/@prewire/core
MIT lisanslıdır. Erken aşamada olduğu için tasarım ve API’ye yönelik eleştirel geri bildirimlere de açığım.
Henüz yorum yok.