Migração de JavaScript para TypeScript em Projetos Existentes: Do Básico ao Avançado Já leu

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 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 e inicialize um arquivo com . Comece com uma configuração conservadora: defina temporariamente e aumente gradualmente a rigidez das verificações. Isso reduz erros imediatos e permite uma transição suave. 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 para , 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

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.

Referências


Artigos relacionados