JavaScript Avançado

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

9 min de leitura

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

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 —

<h2>O que é Monorepo e por que usar Turborepo?</h2>

<p>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.</p>

<p>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.</p>

<h2>Estruturando um Monorepo com Turborepo e TypeScript</h2>

<h3>Setup Inicial</h3>

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

<pre><code class="language-bash">mkdir meu-monorepo &amp;&amp; cd meu-monorepo

npm init -y

npm install -D turbo typescript</code></pre>

<p>Crie o arquivo <code>turbo.json</code> na raiz para configurar as tarefas:</p>

<pre><code class="language-json">{

&quot;$schema&quot;: &quot;https://turborepo.com/schema.json&quot;,

&quot;pipeline&quot;: {

&quot;build&quot;: {

&quot;dependsOn&quot;: [&quot;^build&quot;],

&quot;outputs&quot;: [&quot;dist/**&quot;]

},

&quot;test&quot;: {

&quot;dependsOn&quot;: [&quot;^build&quot;]

},

&quot;dev&quot;: {

&quot;cache&quot;: false,

&quot;persistent&quot;: true

}

}

}</code></pre>

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

<h3>Estrutura de Diretórios</h3>

<p>Organize seus pacotes em um diretório comum:</p>

<pre><code>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</code></pre>

<p>Para usar <code>pnpm</code> (recomendado para monorepos), crie <code>pnpm-workspace.yaml</code>:</p>

<pre><code class="language-yaml">packages:

  • &#039;packages/*&#039;</code></pre>

<h2>Path Aliases: Simplificando Importações</h2>

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

<pre><code class="language-json">{

&quot;compilerOptions&quot;: {

&quot;baseUrl&quot;: &quot;.&quot;,

&quot;paths&quot;: {

&quot;@core/&quot;: [&quot;packages/core/src/&quot;],

&quot;@ui/&quot;: [&quot;packages/ui/src/&quot;],

&quot;@shared/&quot;: [&quot;packages/shared/src/&quot;]

}

}

}</code></pre>

<p>Agora, em qualquer pacote, você pode fazer:</p>

<pre><code class="language-typescript">// Ao invés de: import { Button } from &#039;../../../ui/src/Button&#039;

import { Button } from &#039;@ui/components&#039;;

import { formatDate } from &#039;@shared/utils&#039;;</code></pre>

<p>Para que funcione em runtime no Node.js, instale e configure <code>tsconfig-paths</code>:</p>

<pre><code class="language-bash">npm install tsconfig-paths</code></pre>

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

<pre><code class="language-typescript">import &#039;tsconfig-paths/register&#039;;

import { Button } from &#039;@ui/components&#039;;

console.log(&#039;App iniciado&#039;, Button);</code></pre>

<h2>Criando e Reutilizando Shared Packages</h2>

<h3>Exemplo Prático: Um Pacote Compartilhado</h3>

<p>Crie um pacote <code>shared</code> para funcionalidades comuns:</p>

<pre><code class="language-bash">mkdir packages/shared

cd packages/shared

npm init -y</code></pre>

<p><strong>packages/shared/package.json:</strong></p>

<pre><code class="language-json">{

&quot;name&quot;: &quot;@monorepo/shared&quot;,

&quot;version&quot;: &quot;1.0.0&quot;,

&quot;type&quot;: &quot;module&quot;,

&quot;main&quot;: &quot;dist/index.js&quot;,

&quot;types&quot;: &quot;dist/index.d.ts&quot;,

&quot;exports&quot;: {

&quot;.&quot;: &quot;./dist/index.js&quot;,

&quot;./utils&quot;: &quot;./dist/utils.js&quot;,

&quot;./types&quot;: &quot;./dist/types.js&quot;

},

&quot;scripts&quot;: {

&quot;build&quot;: &quot;tsc&quot;,

&quot;dev&quot;: &quot;tsc --watch&quot;

}

}</code></pre>

<p><strong>packages/shared/src/types.ts:</strong></p>

<pre><code class="language-typescript">export interface User {

id: string;

name: string;

email: string;

}

export interface ApiResponse&lt;T&gt; {

success: boolean;

data: T;

error?: string;

}</code></pre>

<p><strong>packages/shared/src/utils.ts:</strong></p>

<pre><code class="language-typescript">import { User } from &#039;./types&#039;;

export const isValidEmail = (email: string): boolean =&gt; {

return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

};

export const formatUserName = (user: User): string =&gt; {

return user.name.toUpperCase();

};</code></pre>

<p><strong>packages/shared/src/index.ts:</strong></p>

<pre><code class="language-typescript">export * from &#039;./types&#039;;

export * from &#039;./utils&#039;;</code></pre>

<h3>Consumindo o Shared Package</h3>

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

<pre><code class="language-json">{

&quot;name&quot;: &quot;web&quot;,

&quot;dependencies&quot;: {

&quot;@monorepo/shared&quot;: &quot;workspace:*&quot;

}

}</code></pre>

<p>A flag <code>workspace:*</code> indica ao <code>pnpm</code> que use a versão local.</p>

<p>Agora use em <code>packages/web/src/app.ts</code>:</p>

<pre><code class="language-typescript">import { User, ApiResponse, isValidEmail, formatUserName } from &#039;@monorepo/shared&#039;;

const user: User = {

id: &#039;1&#039;,

name: &#039;João Silva&#039;,

email: &#039;joao@example.com&#039;

};

if (isValidEmail(user.email)) {

console.log(&#039;Usuário válido:&#039;, formatUserName(user));

}</code></pre>

<h2>Executando Tarefas no Monorepo</h2>

<p>Com Turborepo configurado, execute:</p>

<pre><code class="language-bash"># 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</code></pre>

<p>Para filtrar pacotes específicos:</p>

<pre><code class="language-bash">turbo run build --filter @monorepo/shared

turbo run test --filter web</code></pre>

<h2>Conclusão</h2>

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

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

<h2>Referências</h2>

<ul>

<li><a href="https://turbo.build/repo/docs" target="_blank" rel="noopener noreferrer">Turborepo Official Documentation</a></li>

<li><a href="https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping" target="_blank" rel="noopener noreferrer">TypeScript Path Mapping Guide</a></li>

<li><a href="https://pnpm.io/workspaces" target="_blank" rel="noopener noreferrer">pnpm Workspaces</a></li>

<li><a href="https://nx.dev/concepts/monorepo-benefits" target="_blank" rel="noopener noreferrer">Monorepo Best Practices - Nx Documentation</a></li>

<li><a href="https://www.typescriptlang.org/docs/handbook/modules.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Modules</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Como Usar TypeScript Compiler API: tsconfig Avançado e Project References em Produção
Como Usar TypeScript Compiler API: tsconfig Avançado e Project References em Produção

TypeScript Compiler API: tsconfig Avançado e Project References Entendendo o...

Como Usar Arquitetura de Frontend: Flux, MVC, MVVM e Event-Driven Design em Produção
Como Usar Arquitetura de Frontend: Flux, MVC, MVVM e Event-Driven Design em Produção

MVC: O Fundamento Model-View-Controller é o padrão mais tradicional em arquit...

O que Todo Dev Deve Saber sobre Hooks Avançados em React: useReducer, useContext e useImperativeHandle
O que Todo Dev Deve Saber sobre Hooks Avançados em React: useReducer, useContext e useImperativeHandle

useReducer: Gerenciamento de Estado Complexo O é o hook ideal quando seu esta...