Como Usar Monorepo com TypeScript: Turborepo, Paths e Shared Packages em Produção Já leu

O que é Monorepo e por que usar Turborepo? Um monorepo é um repositório único que contém múltiplos projetos ou pacotes independentes. Diferente da abordagem tradicional de manter cada projeto em um repositório separado, o monorepo centraliza o código, facilita o compartilhamento de dependências e simplifica o versionamento. Turborepo é um orquestrador de build moderno que otimiza a execução de tarefas em monorepos através de cache inteligente e execução paralela eficiente. A vantagem principal do Turborepo é a performance. Ele detecta quais pacotes foram alterados e executa apenas as tarefas necessárias, evitando rebuilds desnecessários. Isso reduz significativamente o tempo de desenvolvimento em projetos grandes. Além disso, funciona perfeitamente com TypeScript e ferramentas como npm, yarn e pnpm, oferecendo uma experiência fluida e sem fricção. Estruturando um Monorepo com Turborepo e TypeScript Setup Inicial Para começar, crie uma estrutura básica de monorepo com Turborepo: Crie o arquivo na raiz para configurar as tarefas: O símbolo indica dependência de outros pacotes —

O que é Monorepo e por que usar Turborepo?

Um monorepo é um repositório único que contém múltiplos projetos ou pacotes independentes. Diferente da abordagem tradicional de manter cada projeto em um repositório separado, o monorepo centraliza o código, facilita o compartilhamento de dependências e simplifica o versionamento. Turborepo é um orquestrador de build moderno que otimiza a execução de tarefas em monorepos através de cache inteligente e execução paralela eficiente.

A vantagem principal do Turborepo é a performance. Ele detecta quais pacotes foram alterados e executa apenas as tarefas necessárias, evitando rebuilds desnecessários. Isso reduz significativamente o tempo de desenvolvimento em projetos grandes. Além disso, funciona perfeitamente com TypeScript e ferramentas como npm, yarn e pnpm, oferecendo uma experiência fluida e sem fricção.

Estruturando um Monorepo com Turborepo e TypeScript

Setup Inicial

Para começar, crie uma estrutura básica de monorepo com Turborepo:

mkdir meu-monorepo && cd meu-monorepo
npm init -y
npm install -D turbo typescript

Crie o arquivo turbo.json na raiz para configurar as tarefas:

{
  "$schema": "https://turborepo.com/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["^build"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

O símbolo ^ indica dependência de outros pacotes — ou seja, antes de fazer build do pacote A, execute build de suas dependências.

Estrutura de Diretórios

Organize seus pacotes em um diretório comum:

meu-monorepo/
├── packages/
│   ├── core/
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── ui/
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── web/
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
├── turbo.json
├── tsconfig.json
├── package.json
└── pnpm-workspace.yaml

Para usar pnpm (recomendado para monorepos), crie pnpm-workspace.yaml:

packages:
  - 'packages/*'

Path Aliases: Simplificando Importações

Path aliases permitem importar módulos usando caminhos amigáveis em vez de caminhos relativos bagunçados. Configure-os no tsconfig.json raiz:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@core/*": ["packages/core/src/*"],
      "@ui/*": ["packages/ui/src/*"],
      "@shared/*": ["packages/shared/src/*"]
    }
  }
}

Agora, em qualquer pacote, você pode fazer:

// Ao invés de: import { Button } from '../../../ui/src/Button'
import { Button } from '@ui/components';
import { formatDate } from '@shared/utils';

Para que funcione em runtime no Node.js, instale e configure tsconfig-paths:

npm install tsconfig-paths

No seu arquivo de entrada (exemplo packages/web/src/index.ts):

import 'tsconfig-paths/register';
import { Button } from '@ui/components';

console.log('App iniciado', Button);

Criando e Reutilizando Shared Packages

Exemplo Prático: Um Pacote Compartilhado

Crie um pacote shared para funcionalidades comuns:

mkdir packages/shared
cd packages/shared
npm init -y

packages/shared/package.json:

{
  "name": "@monorepo/shared",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": "./dist/index.js",
    "./utils": "./dist/utils.js",
    "./types": "./dist/types.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch"
  }
}

packages/shared/src/types.ts:

export interface User {
  id: string;
  name: string;
  email: string;
}

export interface ApiResponse<T> {
  success: boolean;
  data: T;
  error?: string;
}

packages/shared/src/utils.ts:

import { User } from './types';

export const isValidEmail = (email: string): boolean => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};

export const formatUserName = (user: User): string => {
  return user.name.toUpperCase();
};

packages/shared/src/index.ts:

export * from './types';
export * from './utils';

Consumindo o Shared Package

Em outro pacote (exemplo web), adicione @monorepo/shared como dependência no packages/web/package.json:

{
  "name": "web",
  "dependencies": {
    "@monorepo/shared": "workspace:*"
  }
}

A flag workspace:* indica ao pnpm que use a versão local.

Agora use em packages/web/src/app.ts:

import { User, ApiResponse, isValidEmail, formatUserName } from '@monorepo/shared';

const user: User = {
  id: '1',
  name: 'João Silva',
  email: 'joao@example.com'
};

if (isValidEmail(user.email)) {
  console.log('Usuário válido:', formatUserName(user));
}

Executando Tarefas no Monorepo

Com Turborepo configurado, execute:

# Build de todos os pacotes respeitando dependências
turbo run build

# Executar testes apenas em pacotes alterados
turbo run test --filter=[HEAD]

# Watch em desenvolvimento
turbo run dev

# Ver grafo de dependências
turbo run build --graph

Para filtrar pacotes específicos:

turbo run build --filter @monorepo/shared
turbo run test --filter web

Conclusão

Dominar monorepos com Turborepo, paths aliases e shared packages transforma sua produtividade em projetos escaláveis. Os três pilares aprendidos aqui são: (1) Turborepo otimiza builds através de cache e execução paralela inteligente; (2) path aliases eliminam importações relativas confusas, melhorando legibilidade; (3) shared packages centralizam lógica comum, reduzindo duplicação e mantendo consistência entre projetos.

Comece pequeno com dois ou três pacotes, configure o turbo.json corretamente e expanda conforme necessário. A maioria dos problemas em monorepos vem de má configuração inicial — invista tempo nisso.

Referências


Artigos relacionados