<h2>Entendendo o tsconfig.json: Fundamentos Práticos</h2>
<p>O <code>tsconfig.json</code> é o arquivo de configuração central do TypeScript. Ele define como o compilador deve se comportar, quais arquivos processar, para qual versão JavaScript compilar e como lidar com módulos. Muitos desenvolvedores o deixam com configurações padrão, perdendo controle fino sobre seu projeto. Dominar suas opções significa ter precisão na compilação, melhor compatibilidade e menos bugs em tempo de execução.</p>
<p>Quando você executa <code>tsc</code> sem argumentos, o TypeScript procura por <code>tsconfig.json</code> no diretório atual ou em diretórios pai. Se encontrado, usa suas configurações. Se não encontrado, usa padrões muito permissivos que aceitam quase qualquer código. Um bom <code>tsconfig.json</code> é como um contrato bem definido entre você e o compilador: você especifica regras estritas, o compilador as aplica rigorosamente.</p>
<h2>Opções de Compilação Essenciais</h2>
<h3>Target e Lib: Definindo o Destino</h3>
<p>A opção <code>target</code> define para qual versão do ECMAScript o TypeScript deve compilar. Se seu projeto precisa rodar em navegadores antigos, use <code>ES5</code>. Se é um projeto moderno, <code>ES2020</code> ou superior faz sentido. A opção <code>lib</code> especifica quais bibliotecas de tipo estão disponíveis (DOM, ES6, etc.). Muitas vezes, você precisa ambas bem alinhadas.</p>
<pre><code class="language-json">{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext"
}
}</code></pre>
<p>No exemplo acima, estamos dizendo: compile para JavaScript moderno (ES2020), assume que teremos acesso às APIs do DOM e dos iteráveis, e use módulos ESNext (o compilador preservará a sintaxe <code>import/export</code> para um bundler lidar depois). Se seu projeto roda em Node.js puro, não inclua <code>DOM</code> na lib. Se precisa de funcionalidades específicas como <code>Promise</code> ou <code>Proxy</code>, garanta que estejam na lib.</p>
<h3>Module: Qual Sistema de Módulos Usar</h3>
<p>Existem vários sistemas de módulos: CommonJS (padrão Node.js antigo), ESNext (módulos modernos), UMD (compatível com múltiplos ambientes) e outros. A escolha depende do seu bundler e ambiente de execução.</p>
<pre><code class="language-json">{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020"
}
}</code></pre>
<p>Se usar Webpack, Vite ou Rollup, configure <code>module: "ESNext"</code> — esses bundlers entendem módulos ES6 nativamente e fazem otimizações melhores. Se seu código roda direto no Node.js sem bundler, use <code>commonjs</code>. Para bibliotecas NPM publicadas, muitas vezes você precisa gerar ambos: uma versão CommonJS (em <code>dist/cjs</code>) e uma ESNext (em <code>dist/esm</code>).</p>
<h3>Strict: Ativando Segurança Total</h3>
<p>A opção <code>strict</code> ativa um conjunto de verificações rigorosas simultaneamente. É como ligar todas as luzes de segurança de uma só vez.</p>
<pre><code class="language-json">{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}</code></pre>
<p>Ao ativar <code>strict: true</code>, o TypeScript força você a ser explícito sobre tipos. Variáveis não podem ser <code>any</code> implícito, valores não podem ser <code>null</code> ou <code>undefined</code> sem declaração, e métodos precisam ser tipados corretamente. Isso evita toneladas de bugs. Se estiver herdando um projeto legado sem tipos, comece com <code>strict: false</code> e migre gradualmente para <code>true</code>.</p>
<h3>Declaration e DeclarationMap: Gerando Tipos para Consumidores</h3>
<p>Se você está criando uma biblioteca NPM, precisa fornecer informações de tipo para consumidores. A opção <code>declaration</code> gera arquivos <code>.d.ts</code> (arquivos de declaração). A opção <code>declarationMap</code> cria source maps para esses arquivos, permitindo navegar até o código original em IDEs.</p>
<pre><code class="language-json">{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src"
}
}</code></pre>
<p>Quando você compila com essas opções, TypeScript gera um <code>.d.ts</code> para cada <code>.ts</code>. Uma ferramenta como <code>dts-bundle-generator</code> depois consolida tudo em um único arquivo de tipo. Consumidores da sua biblioteca obterão IntelliSense perfeito. Sem <code>declaration</code>, sua biblioteca é praticamente invisível para usuários TypeScript.</p>
<h2>Paths, Resolução de Módulos e BaseUrl</h2>
<h3>Configurando Aliases com Paths</h3>
<p>Em projetos grandes, caminhos relativos como <code>../../../../utils/helper</code> são ruins: difíceis de ler e quebram quando você move arquivos. A solução é usar aliases.</p>
<pre><code class="language-json">{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/": ["src/"],
"@components/": ["src/components/"],
"@utils/": ["src/utils/"],
"@types/": ["src/types/"]
}
}
}</code></pre>
<p>Agora, em qualquer arquivo, você pode fazer:</p>
<pre><code class="language-typescript">import { Button } from "@components/button";
import { formatDate } from "@utils/date";
import type { User } from "@types/user";</code></pre>
<p>Muito melhor. Seu bundler (Webpack, Vite, etc.) entende essas configurações automaticamente. Se estiver usando Jest para testes, configure <code>moduleNameMapper</code> no jest.config.js para que funcione lá também. TypeScript faz parte da história, mas não é toda.</p>
<h3>Module Resolution: Node vs Classic</h3>
<p>TypeScript suporta dois algoritmos de resolução de módulos. <code>node</code> segue a estratégia do Node.js (procura em <code>node_modules</code>, depois em diretórios pai). <code>classic</code> é o padrão antigo do TypeScript (quase obsoleto). Use sempre <code>node</code>.</p>
<pre><code class="language-json">{
"compilerOptions": {
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}</code></pre>
<p>A opção <code>resolveJsonModule</code> permite importar arquivos JSON diretamente (útil para configs). <code>esModuleInterop</code> corrige problemas de compatibilidade entre CommonJS e ESM. <code>allowSyntheticDefaultImports</code> permite sintaxe mais limpa ao importar CommonJS de código ESM.</p>
<h2>Controle de Compilação e Diretórios</h2>
<h3>Include, Exclude e Files</h3>
<p>TypeScript precisa saber quais arquivos compilar. Você controla isso com <code>include</code>, <code>exclude</code> e <code>files</code>. Use <code>include</code> para padrões (globs) e <code>exclude</code> para excluir caminhos específicos.</p>
<pre><code class="language-json">{
"include": ["src/*/"],
"exclude": ["node_modules", "dist", "*/.spec.ts", "*/.test.ts"],
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
}
}</code></pre>
<p>Este setup compila tudo em <code>src</code>, mas ignora testes (<code>.spec.ts</code> e <code>.test.ts</code>) e a pasta <code>node_modules</code>. Arquivos compilados vão para <code>dist</code>. Se usar <code>files</code> em vez de <code>include</code>, você lista arquivo por arquivo explicitamente — bom apenas para projetos muito pequenos.</p>
<h3>OutDir, RootDir e Preservando Estrutura</h3>
<p><code>rootDir</code> é o diretório raiz da sua fonte, <code>outDir</code> é onde TypeScript coloca os arquivos compilados. TypeScript preserva a estrutura de pastas, removendo <code>rootDir</code> do caminho.</p>
<pre><code class="language-json">{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"sourceMap": true,
"inlineSourceMap": false
}
}</code></pre>
<p>Se você tiver <code>src/components/Button.ts</code>, ele será compilado para <code>dist/components/Button.js</code>. A opção <code>sourceMap</code> gera arquivos <code>.map</code> que mapeiam o código compilado de volta ao TypeScript original — essencial para debugar em produção. <code>inlineSourceMap</code> embuça o source map no próprio JavaScript (útil para ambientes onde o arquivo <code>.map</code> não está disponível, mas aumenta o tamanho do arquivo).</p>
<h3>Skiplib e SkipDefaults</h3>
<p><code>skipLibCheck</code> faz TypeScript não verificar tipos de bibliotecas da <code>node_modules</code>. Economiza tempo de compilação massivamente (até 50% em alguns projetos). Se uma biblioteca tem tipos ruins, você não quer descobrir durante sua compilação.</p>
<pre><code class="language-json">{
"compilerOptions": {
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true
}
}</code></pre>
<p>A opção <code>forceConsistentCasingInFileNames</code> evita problemas em sistemas de arquivos case-sensitive (Linux/Mac) onde <code>Button.ts</code> e <code>button.ts</code> são arquivos diferentes — comum quando desenvolve em Windows e deploy em Linux.</p>
<h2>Segurança de Tipos Avançada</h2>
<h3>NoUnusedLocals e NoUnusedParameters</h3>
<p>Essas opções forçam você a remover código morto, mantendo o projeto limpo e focado.</p>
<pre><code class="language-typescript">function calculateTotal(items: Item[], tax: number, discount: number) {
// ❌ Com noUnusedParameters: true, discount causaria erro
return items.reduce((sum, item) => sum + item.price, 0) * (1 + tax);
}</code></pre>
<p>Com essas opções ativadas, qualquer variável ou parâmetro não usado gera um erro. Você é forçado a removê-lo ou usar. Parece chato, mas mantém seu código refatorado constantemente.</p>
<pre><code class="language-json">{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}</code></pre>
<p><code>noImplicitReturns</code> garante que todas as branches de uma função retornem algo. <code>noFallthroughCasesInSwitch</code> impede falls-through acidentais em switches sem <code>break</code>.</p>
<h3>Experimentais: useDefineForClassFields</h3>
<p>Quando seu código compila classes, TypeScript tem duas estratégias: uma segue o padrão JavaScript moderno (usando getters/setters definidos com <code>Object.defineProperty</code>), outra mais simples. A opção <code>useDefineForClassFields</code> usa a estratégia moderna.</p>
<pre><code class="language-typescript">class User {
name: string = "Unknown"; // Com useDefineForClassFields: true, comportamento diferente
constructor(name: string) {
this.name = name;
}
}</code></pre>
<p>Ative isso se seu <code>target</code> é <code>ES2022</code> ou superior. Evita problemas com herança e property descriptors.</p>
<pre><code class="language-json">{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true
}
}</code></pre>
<h2>Exemplo Prático Completo</h2>
<p>Aqui está um <code>tsconfig.json</code> realista e bem construído para um projeto full-stack moderno:</p>
<pre><code class="language-json">{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@/": ["src/"],
"@components/": ["src/components/"],
"@hooks/": ["src/hooks/"],
"@utils/": ["src/utils/"],
"@types/": ["src/types/"]
},
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"moduleResolution": "node",
"useDefineForClassFields": true,
"isolatedModules": true
},
"include": ["src/*/"],
"exclude": ["node_modules", "dist", "*/.spec.ts", "*/.test.ts"],
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
}
}</code></pre>
<p>Neste setup: você compila para ES2020 (moderno), usa paths para organizar imports, ativa verificações estritas completas, gera tipos para consumidores, ignora bibliotecas em type-checking rápido com <code>skipLibCheck</code>, e permite que <code>ts-node</code> rode testes em CommonJS (compatível com Jest). É profissional, robusto e escalável.</p>
<h2>Conclusão</h2>
<p>Três lições essenciais sobre <code>tsconfig.json</code>: <strong>Primeiro</strong>, opções como <code>strict</code>, <code>noUnusedLocals</code> e <code>noImplicitReturns</code> não são detalhes — elas definem a qualidade do seu código. Ativá-las cedo previne bugs massivos depois. <strong>Segundo</strong>, <code>baseUrl</code> e <code>paths</code> transformam a legibilidade de projetos grandes — investir tempo aqui vale a pena. <strong>Terceiro</strong>, entender <code>target</code>, <code>lib</code>, <code>module</code> e <code>moduleResolution</code> é crucial quando seu código precisa rodar em ambientes diferentes (navegador, Node.js, bundlers variados). Não deixe essas configurações ao acaso.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.typescriptlang.org/tsconfig" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Compiler Options</a></li>
<li><a href="https://basarat.gitbook.io/typescript/project/compilation-context" target="_blank" rel="noopener noreferrer">TypeScript Deep Dive - tsconfig.json</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/module-resolution.html" target="_blank" rel="noopener noreferrer">TypeScript Official Documentation - Module Resolution</a></li>
<li><a href="https://www.npmjs.com/package/typescript" target="_blank" rel="noopener noreferrer">Configuring TypeScript for Production</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/basic-types.html" target="_blank" rel="noopener noreferrer">Advanced TypeScript - Strict Mode Best Practices</a></li>
</ul>
<p><!-- FIM --></p>