1 Punkte von abcdkh1209 2 시간 전 | Noch keine Kommentare. | Auf WhatsApp teilen

Hallo, ich habe prewire entwickelt, eine Build-Time-DI-Bibliothek für TypeScript-Frontends.

Ausgangspunkt für diese Bibliothek war die Struktur des Frontend-Monorepos, das ich betreue.

Die Frontends mehrerer Geschäftsbereiche teilen sich einen gemeinsamen core/kit-Code, und die aktuellen Apps verwenden Next.js. Künftig wollte ich in einigen Bereichen gern TanStack Router ausprobieren, ohne dass der gemeinsame Core dadurch direkt von next/navigation oder @tanstack/react-router abhängt.

Ich brauchte also eine Struktur, in der jeder Geschäftsbereich eine andere Implementierung wählen kann, ohne den gemeinsamen Core zu verändern.

In prewire deklariert der Shared Code zuerst Port und Token.

export interface RouterPort {  
  href(to: string): string  
  push(to: string): void  
}  
  
export const ROUTER = new InjectionToken<RouterPort>('router')  

Jede App implementiert diesen Port mit dem jeweils verwendeten Framework.

export const tanstackRouter = injectable(  
  { logger: LOGGER },  
  ({ logger }): RouterPort => ({  
    href: (to) => to,  
    push: (to) => {  
      logger.info(`navigate → ${to}`)  
      throw redirect({ to })  
    },  
  }),  
  { provides: ROUTER },  
)  

Auf Verbraucherseite, auch im Shared Code, muss man nicht wissen, woher die Implementierung kommt, sondern importiert einfach immer denselben Pfad.

import { router } from '#prewire'  

prewire codegen analysiert die injectable()-Deklarationen der App und der Shared Packages statisch und erzeugt in Abhängigkeitsreihenfolge einen gewöhnlichen TypeScript-Composition-Root.

export const logger = consoleLoggerBinding.factory({})  
export const router = tanstackRouterBinding.factory({ logger })  

Es werden weder Runtime-Container noch reflect-metadata oder Decorators verwendet. Probleme wie fehlende Bindings, zirkuläre Abhängigkeiten oder doppelte Bindings werden nicht erst zur Laufzeit erkannt, sondern bereits in der Codegenerierungsphase als Build-Fehler behandelt. Der generierte Code ist direkt lesbar und kann bei Bedarf herausgelöst und als manueller Composition-Root verwendet werden.

Damit Apps Implementierungen aus Shared Packages nicht beliebig überschreiben können, sind Overrides standardmäßig geschlossen. Nur Bindings, die im Shared Code default: true angeben, lassen sich durch die App ersetzen. Die Absicht ist ähnlich wie bei open in Kotlin.

Zusätzlich gibt es eine separate environment-Achse. Damit lassen sich unterschiedliche Roots nach frei gewählten Kriterien erstellen, etwa live/test, server/client oder Namen einzelner Business-Apps. Ein nur für den Server bestimmtes Binding wird im Client-Root zum Beispiel nicht bloß nicht ausgeführt, sondern seine Imports erscheinen im generierten Code überhaupt nicht.

Die Beispiele im aktuellen Repository enthalten eine Konfiguration, in der ein gemeinsames Kit von den folgenden drei Apps jeweils unterschiedlich verdrahtet wird.

  • Next.js
  • TanStack Start
  • React Router

Für die Build-Integration gibt es ein unplugin für Vite, webpack und rspack sowie withPrewire() für Next.js.

Allerdings ist die Bibliothek bisher noch nicht in echtem Produktivcode eingeführt. Zuerst habe ich das Design mit einer separaten Bibliothek und Beispielen validiert und dann veröffentlicht. Aktuell ist sie auf npm als 0.1.1 verfügbar, und da es noch eine frühe 0.x-Version ist, kann sich die API ändern.

Bei einer einzelnen App in nur einer Umgebung mit wenigen Bindings gibt es außerdem kaum einen Grund, prewire zu verwenden. In solchen Projekten ist es einfacher, eine Composition-Root-Datei direkt selbst zu schreiben. Gedacht ist es vor allem für Fälle, in denen derselbe Shared Code über mehrere Apps, Umgebungen und Testkonfigurationen hinweg unterschiedlich verdrahtet werden muss.

Ich komme hauptsächlich aus der Backend-Entwicklung und habe dieses Problem bei der Verwaltung eines Frontend-Monorepos mit DI und Build-Time-Codegenerierung gelöst. Deshalb wirkt auch der Lösungsansatz selbst recht backend-lastig.

Mich würde interessieren, wie Leute mit starkem Frontend-Fokus dieses Problem normalerweise lösen, wenn mehrere Apps einen gemeinsamen Core verwenden, aber sich framework-spezifische Implementierungen wie beim Router unterscheiden. Haltet ihr Build-Time-DI wie prewire für einen sinnvollen Ansatz, oder gibt es einfachere oder im Frontend-Ökosystem vertrautere Wege? Ich freue mich über Meinungen.

GitHub: https://github.com/clroot/prewire
npm: https://www.npmjs.com/package/@prewire/core

Die Bibliothek steht unter der MIT-Lizenz. Da sie sich noch in einem frühen Stadium befindet, ist auch kritisches Feedback zu Design und API willkommen.

Noch keine Kommentare.

Noch keine Kommentare.