Olá. Criei a prewire, uma biblioteca de DI em tempo de build para frontend em TypeScript.
A motivação para criar esta biblioteca veio da estrutura de um monorepo de frontend que eu mantenho.
Os frontends de vários negócios compartilham um único código core/kit, e os apps atuais usam Next.js. No futuro, queria experimentar TanStack Router em alguns negócios, mas não queria fazer o core compartilhado depender diretamente de next/navigation ou @tanstack/react-router.
Eu precisava de uma estrutura em que cada negócio pudesse escolher uma implementação diferente sem alterar o core compartilhado.
No prewire, o código compartilhado declara primeiro a port e o token.
export interface RouterPort {
href(to: string): string
push(to: string): void
}
export const ROUTER = new InjectionToken<RouterPort>('router')
Cada app implementa essa port com o framework que usa.
export const tanstackRouter = injectable(
{ logger: LOGGER },
({ logger }): RouterPort => ({
href: (to) => to,
push: (to) => {
logger.info(`navigate → ${to}`)
throw redirect({ to })
},
}),
{ provides: ROUTER },
)
Do lado consumidor, incluindo o código compartilhado, não é preciso saber de onde veio a implementação: basta importar pelo mesmo caminho.
import { router } from '#prewire'
O prewire codegen analisa estaticamente as declarações injectable() do app e dos pacotes compartilhados e gera uma composition root em TypeScript comum, na ordem das dependências.
export const logger = consoleLoggerBinding.factory({})
export const router = tanstackRouterBinding.factory({ logger })
Não usa contêiner em runtime, reflect-metadata nem decorators. Problemas como bindings ausentes, dependências circulares e bindings duplicados são tratados como erros de build na etapa de geração de código, não durante a execução. O código gerado pode ser lido diretamente e, se necessário, destacado para ser usado como uma composition root manual.
Overrides também são fechados por padrão para impedir que um app substitua arbitrariamente a implementação de um pacote compartilhado. Apenas bindings em que o código compartilhado especifica default: true podem ser substituídos pelo app. A intenção é parecida com o open do Kotlin.
Também separei um eixo de environment. É possível criar roots diferentes conforme o critério desejado, como live/test, server/client ou o nome do app de negócio. Por exemplo, um binding exclusivo do servidor não apenas deixa de ser executado na root do cliente: o próprio import nem é incluído no código gerado.
Os exemplos no repositório atual têm uma configuração em que um único kit compartilhado é conectado de formas diferentes pelos três apps a seguir.
- Next.js
- TanStack Start
- React Router
Para integração com o build, fornecemos um unplugin para Vite, webpack e rspack, além de withPrewire() para Next.js.
No entanto, ainda não adotei isso em código de produto real. Primeiro validei o design com uma biblioteca separada e exemplos, e publiquei nessa etapa. Atualmente está publicado no npm como 0.1.1; por ser uma versão inicial 0.x, a API pode mudar.
Além disso, se for um app único, em um único ambiente, com poucos bindings, não há muito motivo para usar prewire. Para esse tipo de projeto, escrever diretamente um único arquivo de composition root é mais simples. O principal caso de uso que tenho em mente é quando o mesmo código compartilhado precisa ser conectado de formas diferentes em vários apps, ambientes e configurações de teste.
Tenho trabalhado principalmente com backend e, ao gerenciar um monorepo de frontend, resolvi esse problema com DI e geração de código em tempo de build. Por isso também acho que a solução em si tem um jeito bem “backend”.
Tenho curiosidade de saber como pessoas especializadas em frontend normalmente resolvem o problema em que vários apps usam um core compartilhado, mas apenas implementações específicas por framework, como o roteador, variam. Gostaria de ouvir opiniões sobre se DI em tempo de build como o prewire parece adequado, ou se há abordagens mais simples ou mais familiares no ecossistema frontend.
GitHub: https://github.com/clroot/prewire
npm: https://www.npmjs.com/package/@prewire/core
A licença é MIT. Como está em estágio inicial, feedback crítico sobre o design e a API também é bem-vindo.
Ainda não há comentários.