Preparação e Diagnóstico do Projeto
Antes de iniciar qualquer migração, você precisa entender o estado atual do seu projeto. A primeira etapa é criar um inventário: quantos arquivos JavaScript existem, quais dependências estão instaladas e qual é a complexidade do código. Execute npm list para visualizar as dependências e identifique se alguma já possui tipos TypeScript nativos ou DefinitelyTyped.
A configuração inicial requer a instalação do TypeScript e ferramentas complementares. Execute npm install --save-dev typescript ts-node e inicialize um arquivo tsconfig.json com npx tsc --init. Comece com uma configuração conservadora: defina "strict": false temporariamente e aumente gradualmente a rigidez das verificações. Isso reduz erros imediatos e permite uma transição suave.
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Estratégia de Migração Incremental
A melhor abordagem é migrar arquivos gradualmente, começando pelos menos dependentes (utilitários, helpers) e evoluindo para módulos centrais. Renomeie apenas um arquivo por vez de .js para .ts, corrija os erros de tipo encontrados e teste a funcionalidade. Isso minimiza riscos e facilita a identificação de problemas.
Configure seu bundler (Webpack, Vite ou similar) para aceitar ambos os tipos de arquivo durante a transição. No webpack.config.js, garanta que TypeScript seja processado corretamente:
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
}
};
Exemplo prático: migre um módulo de utilitários primeiro. Considere este arquivo JavaScript:
// utils.js (antes)
export function formatDate(date) {
return new Intl.DateTimeFormat('pt-BR').format(date);
}
export function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
Convertendo para TypeScript:
// utils.ts (depois)
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('pt-BR').format(date);
}
interface Item {
price: number;
[key: string]: any;
}
export function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
Tipagem de Módulos e Dependências
Um desafio comum é lidar com dependências que não possuem tipos nativos. Para pacotes populares, procure no DefinitelyTyped (@types/nome-do-pacote). Instale npm install --save-dev @types/express @types/node conforme necessário.
Para dependências sem tipos, crie um arquivo de declaração. Na raiz, crie uma pasta types/ com arquivos .d.ts:
// types/meu-pacote.d.ts
declare module 'meu-pacote-sem-tipos' {
export function minhaFuncao(param: string): void;
export interface MinhaInterface {
propriedade: string;
}
}
Um exemplo prático com Express (aplicação real):
// src/server.ts
import express, { Request, Response } from 'express';
interface Usuario {
id: number;
nome: string;
email: string;
}
const app = express();
app.use(express.json());
const usuarios: Usuario[] = [];
app.post('/usuarios', (req: Request, res: Response): void => {
const novoUsuario: Usuario = {
id: usuarios.length + 1,
nome: req.body.nome,
email: req.body.email
};
usuarios.push(novoUsuario);
res.status(201).json(novoUsuario);
});
app.get('/usuarios/:id', (req: Request, res: Response): void => {
const usuario = usuarios.find(u => u.id === parseInt(req.params.id));
if (!usuario) {
res.status(404).json({ erro: 'Usuário não encontrado' });
return;
}
res.json(usuario);
});
app.listen(3000, () => console.log('Servidor rodando na porta 3000'));
Refinamento Progressivo de Tipos
Após migrar o essencial, aumente gradualmente o nível de rigor do TypeScript. Modifique o tsconfig.json incrementalmente:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true
}
}
Essa abordagem força a tipagem explícita de variáveis, retornos de funções e tratamento de null/undefined. Exemplo de refatoração:
// Antes (permissivo)
const processarDados = (dados) => {
return dados.map(item => item.valor * 2);
};
// Depois (rigoroso)
interface Dado {
valor: number;
}
const processarDados = (dados: Dado[]): number[] => {
return dados.map(item => item.valor * 2);
};
// Com tratamento de erros
const processarDadosSeguro = (dados: unknown[]): number[] => {
if (!Array.isArray(dados)) {
throw new Error('Dados deve ser um array');
}
return dados
.filter((item): item is Dado => typeof item === 'object' && item !== null && 'valor' in item)
.map(item => item.valor * 2);
};
Configure testes para garantir que a migração não quebrou funcionalidades. Use Jest com suporte TypeScript:
// __tests__/utils.test.ts
import { formatDate, calculateTotal } from '../src/utils';
describe('Utils', () => {
test('formatDate deve retornar string formatada', () => {
const data = new Date('2024-01-15');
expect(formatDate(data)).toMatch(/15/);
});
test('calculateTotal deve somar preços', () => {
const items = [{ price: 10 }, { price: 20 }];
expect(calculateTotal(items)).toBe(30);
});
});
Conclusão
Migrar para TypeScript é uma decisão estratégica que aumenta a confiabilidade do código. Primeiro aprendizado: comece devagar e de forma incremental — renomeie poucos arquivos por vez e mantenha ambos os tipos durante a transição. Segundo aprendizado: configure o tsconfig.json de forma progressiva, iniciando com strict: false e aumentando a rigidez conforme se adapta ao sistema de tipos. Terceiro aprendizado: invista em testes automatizados para validar que nada quebrou durante o processo. Com planejamento e disciplina, sua base de código se tornará mais segura e mantível.