Migrando um projeto JavaScript para TypeScript gradualmente
1. Por que migrar gradualmente? Estratégia e preparação
Migrar um projeto inteiro de JavaScript para TypeScript de uma só vez é uma tarefa assustadora e, na maioria dos casos, desnecessária. A abordagem incremental permite que sua equipe continue entregando valor enquanto adota os benefícios do TypeScript aos poucos. Os principais ganhos incluem: detecção precoce de erros, melhor documentação do código através de tipos, e um ambiente de desenvolvimento mais produtivo com autocompletar e refatorações seguras.
Antes de começar, avalie o tamanho do projeto, suas dependências e os riscos envolvidos. Projetos grandes com muitas dependências não tipadas exigirão mais trabalho de declaração de tipos. Comece configurando o ambiente com um tsconfig.json básico:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"allowJs": true,
"checkJs": false,
"outDir": "./dist",
"rootDir": "./src",
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
As opções allowJs e checkJs são fundamentais: a primeira permite que arquivos JavaScript coexistam com TypeScript, enquanto a segunda ativa a verificação de tipos nos arquivos JS.
2. Primeiros passos: integrando TypeScript ao build existente
Instale o TypeScript como dependência de desenvolvimento:
npm install --save-dev typescript
Se você usa webpack, adicione o ts-loader ou babel-loader com preset TypeScript. Para Vite, o suporte a TypeScript é nativo. Exemplo com webpack:
// webpack.config.js
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
};
Agora você pode começar a criar arquivos .ts que importam arquivos .js existentes. O compilador TypeScript entenderá ambos os formatos.
3. Ativando a verificação de tipos gradualmente
Uma estratégia eficaz é usar comentários de controle para ativar a verificação onde ela é mais útil. Adicione // @ts-check no topo de arquivos JS que você quer verificar, e // @ts-nocheck em arquivos que ainda não estão prontos:
// @ts-check
// src/utils/calculator.js
function add(a, b) {
return a + b; // TypeScript agora infere os tipos
}
Configure checkJs: true no tsconfig.json e, em vez de ativar strict imediatamente, comece com strict: false. Isso permite que você veja erros sem ser sobrecarregado por todos eles de uma vez.
4. Convertendo arquivos JavaScript para TypeScript
Comece renomeando arquivos .js para .ts. Você provavelmente encontrará erros imediatos. A abordagem pragmática é usar any temporariamente:
// src/services/userService.ts
function getUser(id: any): any {
// implementação existente
}
Para módulos sem declarações de tipo, use declare module:
// src/types/legacy-module.d.ts
declare module 'old-library' {
export function doSomething(param: any): any;
}
5. Tipando o coração do projeto: funções, objetos e APIs
Refatore gradualmente funções críticas adicionando tipos aos parâmetros e retornos:
// src/services/paymentService.ts
interface PaymentPayload {
amount: number;
currency: string;
customerId: string;
}
interface PaymentResult {
success: boolean;
transactionId?: string;
error?: string;
}
async function processPayment(payload: PaymentPayload): Promise<PaymentResult> {
const response = await fetch('/api/payments', {
method: 'POST',
body: JSON.stringify(payload),
});
return response.json();
}
Para callbacks e funções assíncronas, defina types específicos:
type Callback<T> = (error: Error | null, result?: T) => void;
function fetchData(url: string, callback: Callback<Data>) {
// implementação
}
6. Lidando com dependências externas e código legado
Instale pacotes de tipos para bibliotecas populares:
npm install --save-dev @types/react @types/lodash @types/express
Para bibliotecas sem tipos, crie declarações customizadas em *.d.ts:
// src/types/global.d.ts
declare module 'untyped-library' {
export function parse(input: string): Record<string, unknown>;
export const VERSION: string;
}
Use unknown em vez de any quando possível, e aplique type guards para verificar tipos em tempo de execução:
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj
);
}
7. Avançando para configurações mais rigorosas
Quando a base de código estiver mais estável, ative strict: true no tsconfig.json. Isso habilita várias verificações rigorosas:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
Corrija erros comuns como variáveis potencialmente nulas:
function getLength(str: string | null): number {
return str?.length ?? 0; // uso de optional chaining e nullish coalescing
}
8. Manutenção contínua e boas práticas
Configure um pipeline de CI que execute a verificação de tipos:
# .github/workflows/typecheck.yml
name: Type Check
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npx tsc --noEmit
Para times, estabeleça code reviews focados em tipos. Monitore métricas como cobertura de tipos (porcentagem de arquivos .ts) e redução do uso de any. Ferramentas como typescript-coverage-report podem ajudar:
npx typescript-coverage-report
Lembre-se: a migração gradual é uma maratona, não uma corrida de velocidade. Cada arquivo convertido e cada tipo adicionado são passos em direção a um código mais seguro e sustentável.
Referências
- Documentação oficial do TypeScript: Migração de JavaScript — Guia completo da equipe TypeScript sobre como migrar projetos JS para TS, incluindo configuração de tsconfig e estratégias de adoção gradual
- TypeScript: Configuração strict — Documentação detalhada sobre as opções strict do TypeScript e como habilitá-las progressivamente
- TypeScript Deep Dive: Migrando para TypeScript — Tutorial prático de migração gradual com exemplos reais e dicas para lidar com código legado
- Migrating a large codebase from JavaScript to TypeScript — Artigo da Twilio com estratégias testadas em projetos grandes, incluindo integração com CI/CD
- TypeScript Coverage Report — Ferramenta para medir a cobertura de tipos no projeto, útil para monitorar o progresso da migração
- Using TypeScript with Webpack — Guia oficial do webpack para configurar TypeScript no pipeline de build