1 điểm bởi abcdkh1209 4 giờ trước | Chưa có bình luận nào. | Chia sẻ qua WhatsApp

Xin chào. Tôi đã tạo prewire, một thư viện DI ở thời điểm build dành cho frontend TypeScript.

Lý do tôi tạo thư viện này xuất phát từ cấu trúc monorepo frontend mà tôi đang quản lý.

Frontend của nhiều mảng kinh doanh cùng chia sẻ một mã core/kit, và các ứng dụng hiện tại đang dùng Next.js. Trong tương lai, một số mảng muốn thử dùng TanStack Router, nhưng tôi không muốn phần core dùng chung phải phụ thuộc trực tiếp vào next/navigation hay @tanstack/react-router.

Tôi cần một cấu trúc cho phép mỗi mảng chọn cách triển khai khác nhau, nhưng phần core dùng chung không phải sửa đổi.

Trong prewire, mã dùng chung trước hết khai báo port và token.

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

Mỗi ứng dụng triển khai port này bằng framework mà mình sử dụng.

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

Ở phía consumer, bao gồm cả mã dùng chung, chỉ cần import từ cùng một đường dẫn mà không cần biết implementation đến từ đâu.

import { router } from '#prewire'  

prewire codegen phân tích tĩnh các khai báo injectable() của ứng dụng và các package dùng chung, rồi tạo ra một composition root TypeScript thông thường theo đúng thứ tự phụ thuộc.

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

Không sử dụng runtime container, reflect-metadata hay decorator. Các vấn đề như thiếu binding, phụ thuộc vòng, binding trùng lặp được xử lý thành lỗi build ở bước sinh mã, chứ không phải trong lúc chạy. Mã được sinh ra có thể đọc trực tiếp, và nếu cần cũng có thể tách ra để dùng như một composition root thủ công.

Override cũng mặc định bị đóng để ứng dụng không thể tùy tiện ghi đè implementation của package dùng chung. Ứng dụng chỉ có thể thay thế các binding mà mã dùng chung chỉ định default: true. Ý định này tương tự open trong Kotlin.

Tôi cũng tách riêng trục environment. Có thể tạo các root khác nhau theo tiêu chí người dùng muốn, chẳng hạn live/test, server/client, hoặc tên ứng dụng của từng mảng kinh doanh. Ví dụ, một binding chỉ dành cho server không chỉ đơn giản là không được chạy trong client root, mà bản thân import của nó cũng không xuất hiện trong mã được sinh ra.

Ví dụ trong repository hiện tại có cấu hình trong đó một shared kit được kết nối theo các cách khác nhau bởi ba ứng dụng sau.

  • Next.js
  • TanStack Start
  • React Router

Về tích hợp build, prewire cung cấp unplugin cho Vite, webpack, rspack và withPrewire() cho Next.js.

Tuy nhiên, tôi vẫn chưa đưa nó vào mã sản phẩm thực tế. Đây là giai đoạn tôi kiểm chứng thiết kế bằng một thư viện và ví dụ riêng rồi công khai. Hiện nó đã được phát hành trên npm với phiên bản 0.1.1; vì còn là phiên bản 0.x ban đầu nên API có thể thay đổi.

Ngoài ra, nếu là ứng dụng đơn lẻ, một môi trường duy nhất và chỉ có vài binding, thì không có nhiều lý do để dùng prewire. Với những dự án như vậy, viết trực tiếp một file composition root sẽ đơn giản hơn. Đối tượng chính mà tôi nhắm tới là khi cùng một mã dùng chung cần được kết nối khác nhau trong nhiều ứng dụng, môi trường và cấu hình test.

Tôi chủ yếu phát triển backend, và khi quản lý monorepo frontend, tôi đã giải quyết vấn đề này bằng DI và sinh mã ở thời điểm build. Vì vậy tôi cũng nghĩ bản thân lời giải khá “mang phong cách backend”.

Tôi tò mò không biết những người chuyên làm frontend thường giải quyết thế nào khi nhiều ứng dụng dùng chung core nhưng chỉ khác phần implementation theo framework, như router. Tôi muốn nghe ý kiến xem DI ở thời điểm build như prewire có phù hợp không, hoặc liệu có cách tiếp cận nào đơn giản hơn hay quen thuộc hơn với hệ sinh thái frontend không.

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

Dự án dùng giấy phép MIT. Vì còn ở giai đoạn đầu, tôi cũng rất hoan nghênh các phản hồi mang tính phê bình về thiết kế và API.

Chưa có bình luận nào.

Chưa có bình luận nào.