Como usar o compilador incremental do TypeScript para builds mais rápidos
1. Fundamentos do Compilador Incremental do TypeScript
O compilador incremental do TypeScript é um mecanismo que reduz drasticamente o tempo de rebuild ao reutilizar resultados de compilações anteriores. Diferentemente do modo completo, que recompila todos os arquivos do projeto a cada execução do tsc, o modo incremental mantém um cache inteligente do grafo de dependências entre módulos.
Internamente, o TypeScript gera um arquivo especial chamado .tsbuildinfo. Esse arquivo armazena informações detalhadas sobre:
- O grafo completo de dependências entre arquivos
- Hashes dos conteúdos compilados
- Metadados de versões de cada módulo
- Estado das opções de compilação utilizadas
Quando você executa tsc novamente, o compilador compara os hashes atuais com os armazenados no .tsbuildinfo. Apenas arquivos modificados e seus dependentes diretos são recompilados, enquanto módulos inalterados são reaproveitados do cache.
// Exemplo de estrutura do .tsbuildinfo (simplificada)
{
"program": {
"fileInfos": {
"src/utils.ts": {
"version": "abc123",
"signature": "def456",
"affectsGlobalScope": false
},
"src/app.ts": {
"version": "ghi789",
"signature": "jkl012",
"imports": ["src/utils.ts"]
}
},
"options": {
"incremental": true,
"target": "ES2020",
"module": "commonjs"
}
}
}
Os benefícios são mais evidentes em projetos com centenas ou milhares de arquivos, onde rebuilds completos podem levar minutos. Com o modo incremental, o tempo cai para segundos na maioria dos cenários.
2. Ativação e Configuração Inicial
Para ativar o compilador incremental, adicione a opção no tsconfig.json:
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.cache/tsbuildinfo.json",
"outDir": "./dist",
"rootDir": "./src"
}
}
A opção "tsBuildInfoFile" permite definir o caminho e nome do arquivo de cache. Por padrão, o TypeScript cria o arquivo na pasta de saída (outDir) com o nome .tsbuildinfo. É recomendável armazená-lo em um diretório separado para facilitar a limpeza seletiva.
Para projetos maiores, combine com "composite": true e "declarationMap": true:
{
"compilerOptions": {
"incremental": true,
"composite": true,
"declarationMap": true,
"tsBuildInfoFile": "./.cache/tsbuildinfo.json"
}
}
A opção "composite" habilita referências entre projetos, enquanto "declarationMap" gera mapas de declaração que aceleram a navegação em editores.
3. Uso com Projetos Monorepo e Referências de Projeto
Em monorepos, o modo incremental brilha quando combinado com referências de projeto. Configure cada subprojeto com seu próprio tsconfig.json:
// packages/core/tsconfig.json
{
"compilerOptions": {
"incremental": true,
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../utils" }
]
}
// packages/utils/tsconfig.json
{
"compilerOptions": {
"incremental": true,
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
No tsconfig.json raiz, referencie todos os subprojetos:
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" }
]
}
Execute o build com tsc --build (ou tsc -b). Esse comando processa os projetos na ordem correta de dependências, respeitando o cache incremental de cada um. Para forçar rebuild completo, use tsc --build --force.
# Build incremental (apenas o necessário)
tsc --build
# Forçar rebuild completo
tsc --build --force
# Build seletivo de um projeto específico
tsc --build packages/core
4. Integração com Ferramentas de Build e Watch Mode
O modo watch (tsc --watch) já utiliza o compilador incremental por padrão. Cada salvamento de arquivo dispara uma recompilação apenas dos módulos afetados.
# Executar em modo watch com diagnóstico
tsc --watch --diagnostics
Para integração com bundlers como Webpack, use o ts-loader com transpileOnly: true combinado com fork-ts-checker-webpack-plugin:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
}
]
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin({
typescript: {
incremental: true,
tsBuildInfoFile: './.cache/tschecker.json'
}
})
]
};
Em task runners como Gulp, utilize gulp-typescript com incremental: true:
const gulp = require('gulp');
const ts = require('gulp-typescript');
gulp.task('build', function() {
return gulp.src('src/**/*.ts')
.pipe(ts({
incremental: true,
tsBuildInfoFile: './.cache/gulp-ts.json'
}))
.pipe(gulp.dest('dist'));
});
Para CI/CD, configure scripts que preservem o cache entre execuções:
# .github/workflows/build.yml
- name: Restore TS build cache
uses: actions/cache@v3
with:
path: |
.cache/
dist/
key: ${{ runner.os }}-ts-${{ hashFiles('**/tsconfig.json') }}
- name: Build
run: tsc --build
5. Limpeza e Gerenciamento do Cache Incremental
O cache incremental pode ser invalidado em situações como:
- Mudanças em tsconfig.json (especialmente opções que afetam a saída)
- Atualizações de dependências em node_modules
- Alterações manuais em arquivos compilados
Para limpeza seletiva, crie scripts personalizados:
// package.json
{
"scripts": {
"clean:cache": "rm -rf .cache/ dist/",
"clean:tsbuild": "find . -name '.tsbuildinfo' -delete",
"rebuild": "npm run clean:cache && tsc --build"
}
}
O comando tsc --build --clean remove todos os artefatos de build dos projetos referenciados:
# Remove dist e .tsbuildinfo de todos os subprojetos
tsc --build --clean
6. Diagnóstico e Monitoramento de Performance
Use --diagnostics e --extendedDiagnostics para monitorar o desempenho:
# Diagnóstico básico
tsc --diagnostics
# Diagnóstico estendido (mais detalhes)
tsc --extendedDiagnostics
Exemplo de saída de diagnóstico:
Files: 245
Lines: 45,678
Nodes: 123,456
Identifiers: 89,012
Symbols: 34,567
Types: 56,789
Instantiations: 12,345
Memory used: 456 MB
I/O read: 0.12s
I/O write: 0.08s
Parse time: 0.45s
Bind time: 0.23s
Check time: 1.67s
Emit time: 0.89s
Total time: 3.24s
Para comparar builds completos vs. incrementais:
# Build completo (primeira execução)
tsc --build --force
# Total time: 12.5s
# Build incremental (após modificar um arquivo)
tsc --build
# Total time: 1.8s (redução de ~85%)
Identifique gargalos observando:
- Check time elevado: módulos grandes ou dependências circulares
- Parse time alto: muitos arquivos importados desnecessariamente
- I/O read/write excessivo: cache mal configurado
7. Boas Práticas e Limitações Conhecidas
Boas práticas:
- Sempre versione o tsconfig.json, mas nunca o .tsbuildinfo (adicione ao .gitignore)
- Use "skipLibCheck": true para evitar verificação de tipos em declarações de bibliotecas
- Combine com "noEmit": true em projetos que só verificam tipos
- Para projetos com muitos arquivos dinâmicos, considere "isolatedModules": true
{
"compilerOptions": {
"incremental": true,
"skipLibCheck": true,
"isolatedModules": true,
"noEmit": true
}
}
Limitações:
- Mudanças no tsconfig.json invalidam completamente o cache
- O modo incremental não acelera a primeira compilação (sempre é completa)
- Projetos com muitos arquivos gerados dinamicamente podem ter ganhos reduzidos
- Dependências circulares podem forçar recompilações em cascata
Armadilhas comuns:
- Esquecer de limpar o cache após atualizar node_modules
- Alterar opções de target ou module sem limpar o cache
- Usar outDir diferente entre execuções
Referências
- Documentação Oficial do TypeScript: Compilação Incremental — Guia completo sobre a opção
incrementale configurações relacionadas no tsconfig.json - TypeScript Handbook: Project References — Tutorial oficial sobre referências de projeto e build incremental em monorepos
- Microsoft TypeScript Wiki: Performance — Dicas oficiais de performance, incluindo uso do compilador incremental e diagnóstico
- TypeScript Deep Dive: Incremental Build — Guia prático com exemplos de configuração e otimização de builds incrementais
- LogRocket: How to speed up TypeScript compilation — Artigo técnico com benchmarks e estratégias para acelerar compilações TypeScript
- Medium: TypeScript Incremental Compilation in Monorepos — Estudo de caso sobre implementação de builds incrementais em projetos monorepo reais