नमस्ते। मैंने TypeScript frontend के लिए build time DI लाइब्रेरी prewire बनाई है.

इस लाइब्रेरी को बनाने की वजह उस frontend monorepo की संरचना थी जिसे मैं मैनेज करता हूँ.

कई बिज़नेस के frontend एक ही core/kit code को साझा करते हैं, और अभी ऐप्स Next.js का उपयोग कर रहे हैं। आगे कुछ बिज़नेस में TanStack Router आज़माना चाहता था, लेकिन इसका मतलब यह नहीं था कि shared core सीधे next/navigation या @tanstack/react-router पर निर्भर हो जाए.

हमें ऐसी संरचना चाहिए थी जिसमें हर बिज़नेस अलग implementation चुन सके, लेकिन shared core को बदले बिना.

prewire में shared code पहले port और token declare करता है.

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

फिर हर ऐप अपने उपयोग वाले framework से इस port को implement करता है.

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

shared code सहित consumer side पर implementation कहाँ से आया है, यह जाने बिना वही path import किया जाता है.

import { router } from '#prewire'  

prewire codegen ऐप और shared package की injectable() declarations का static analysis करके dependency order के अनुसार साधारण TypeScript composition root generate करता है.

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

यह runtime container, reflect-metadata या decorator का उपयोग नहीं करता. missing binding, circular dependency, duplicate binding जैसी समस्याएँ execution के दौरान नहीं, बल्कि code generation stage में build error के रूप में संभाली जाती हैं. generated code को सीधे पढ़ा जा सकता है, और ज़रूरत हो तो उसे अलग करके manual composition root की तरह भी इस्तेमाल किया जा सकता है.

shared package की implementation को ऐप मनमाने ढंग से override न कर सके, इसलिए override भी default रूप से बंद है. shared code में default: true सेट की गई binding ही ऐप में replace की जा सकती है. इसका इरादा Kotlin के open जैसा है.

environment axis भी अलग से रखा गया है. live/test, server/client, या बिज़नेस ऐप के नाम जैसे किसी भी मानदंड पर अलग root बनाए जा सकते हैं. उदाहरण के लिए, server-only binding client root में सिर्फ execute नहीं होती, बल्कि generated code में उसका import ही शामिल नहीं होता.

अभी repository के examples में एक shared kit को नीचे दिए गए तीन ऐप्स अलग-अलग तरीके से wire करते हैं.

  • Next.js
  • TanStack Start
  • React Router

build integration के लिए Vite·webpack·rspack हेतु unplugin और Next.js के लिए withPrewire() दिया गया है.

हालाँकि इसे अभी वास्तविक product code में लागू नहीं किया गया है. पहले इसे अलग लाइब्रेरी और examples के रूप में design verify करके public किया गया है. यह अभी npm पर 0.1.1 के रूप में publish है, और शुरुआती 0.x version होने के कारण API बदल सकती है.

इसके अलावा, अगर एक single app, single environment में bindings बहुत कम हैं, तो prewire इस्तेमाल करने का खास कारण नहीं है. ऐसे project में composition root की एक file सीधे लिखना ज़्यादा सरल है. इसका मुख्य लक्ष्य वे स्थितियाँ हैं जहाँ एक ही shared code को कई apps·environments·test configurations में अलग-अलग तरीके से wire करना हो.

मैं मुख्य रूप से backend development करता रहा हूँ, और frontend monorepo को मैनेज करते हुए मैंने इस समस्या को DI और build time code generation से हल किया. इसलिए मुझे लगता है कि यह समाधान काफ़ी backend-style है.

जो लोग frontend को पेशेवर रूप से संभालते हैं, उनसे जानना चाहता हूँ कि कई apps जब shared core का उपयोग करते हैं और router जैसी चीज़ों में सिर्फ framework-specific implementation अलग होती है, तो आम तौर पर इसे कैसे सुलझाते हैं. क्या prewire जैसा build time DI उचित लगता है, या इससे सरल या frontend ecosystem के अधिक परिचित approaches हैं? अगर हैं, तो आपकी राय सुनना चाहूँगा.

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

यह MIT license के तहत है. यह शुरुआती चरण में है, इसलिए design और API पर आलोचनात्मक feedback भी स्वागत योग्य है.

अभी कोई टिप्पणी नहीं है.

अभी कोई टिप्पणी नहीं है.