TypeScript Compiler API: tsconfig Avançado e Project References
Entendendo o tsconfig.json Avançado
O arquivo tsconfig.json é muito mais que um simples arquivo de configuração. Ele controla como o compilador TypeScript processa seu código, otimiza builds e gerencia dependências entre projetos. Quando trabalhamos com aplicações grandes, dominar as opções avançadas é essencial para manter a performance e a qualidade do código.
As opções mais críticas para projetos profissionais incluem incremental, composite, declaration e moduleResolution. A opção incremental permite que o TypeScript armazene em cache informações de compilação anteriores, acelerando recompilações. Já composite prepara seu projeto para ser referenciado por outros, essencial quando usamos Project References.
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo",
"composite": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
Project References: Dividindo Projetos Grandes
Project References permitem que você organize código TypeScript em múltiplos projetos compilados independentemente. Isso é revolucionário para monorepos e aplicações complexas onde diferentes partes do código têm ciclos de compilação diferentes.
Quando você ativa composite: true e declaration: true em um tsconfig.json, esse projeto pode ser referenciado por outros. A chave está em usar references no arquivo raiz para informar ao compilador as dependências entre projetos.
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo"
},
"include": ["src/**/*"],
"references": [
{ "path": "../common" },
{ "path": "../api" }
]
}
A estrutura de um monorepo típico seria:
monorepo/
├── tsconfig.json (raiz)
├── common/
│ ├── tsconfig.json (composite: true)
│ └── src/
│ ├── types.ts
│ └── utils.ts
├── api/
│ ├── tsconfig.json (composite: true, references: [common])
│ └── src/
│ └── server.ts
└── web/
├── tsconfig.json (composite: true, references: [common, api])
└── src/
└── app.tsx
Compilação Incremental e Build Mode
A compilação incremental é um game-changer para projetos grandes. O TypeScript armazena informações do build anterior em tsBuildInfoFile, permitindo que recompilações processem apenas arquivos que mudaram. Combinado com Project References, você obtém builds extremamente rápidos.
// scripts/build.ts - usando a TypeScript Compiler API
import * as ts from 'typescript';
import * as path from 'path';
function buildProject(projectPath: string) {
const configPath = ts.findConfigFile(
projectPath,
ts.sys.fileExists,
'tsconfig.json'
);
if (!configPath) {
throw new Error(`tsconfig.json não encontrado em ${projectPath}`);
}
const config = ts.readConfigFile(configPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonConfigFileContent(
config.config,
ts.sys,
path.dirname(configPath)
);
// Usar o builder para compilação incremental
const host = ts.createIncrementalCompilerHost(parsedConfig.options);
const builder = ts.createIncrementalProgram({
rootNames: parsedConfig.fileNames,
options: parsedConfig.options,
host
});
const result = builder.emit();
const diagnostics = ts.getPreEmitDiagnostics(builder.getProgram());
diagnostics.forEach(diagnostic => {
if (diagnostic.file) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
diagnostic.start!
);
console.log(
`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`
);
}
});
return { exitCode: result.emitSkipped ? 1 : 0, builder };
}
buildProject('./packages/common');
Estratégias Avançadas: PathMapping e Resoluções Customizadas
Para projetos complexos, baseUrl e paths do tsconfig.json são essenciais. Eles permitem imports limpos sem caminhos relativos confusos. Combinados com Project References, criam uma estrutura de código profissional e escalável.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@common/*": ["packages/common/src/*"],
"@api/*": ["packages/api/src/*"],
"@types/*": ["packages/common/src/types/*"],
"@utils/*": ["packages/common/src/utils/*"]
},
"composite": true,
"declaration": true,
"incremental": true
},
"references": [
{ "path": "./packages/common" },
{ "path": "./packages/api" }
]
}
Com essa configuração, você importa assim:
// Em packages/api/src/server.ts
import { User } from '@types/models';
import { formatDate } from '@utils/date';
import { UserService } from '@api/services/user';
export class ApiServer {
private userService: UserService;
constructor() {
this.userService = new UserService();
}
async getUser(id: string): Promise<User> {
const user = await this.userService.findById(id);
return {
...user,
createdAt: formatDate(user.createdAt)
};
}
}
Conclusão
Dominar tsconfig.json avançado e Project References transforma sua capacidade de trabalhar com código TypeScript em larga escala. Os três pontos principais aprendidos são: (1) configurações como incremental, composite e declaration são fundamentais para builds rápidos e modulares; (2) Project References permitem que você organize código em múltiplos projetos compilados independentemente, acelerando o desenvolvimento; (3) baseUrl e paths, quando bem planejados, criam uma estrutura de imports intuitiva e escalável que melhora significativamente a manutenibilidade do projeto.
Aplique esses conceitos progressivamente: comece com configurações básicas, evoluia para Project References quando seu projeto crescer, e domine path mapping quando sua estrutura exigir múltiplos pacotes interdependentes.