TypeScript: Migração de um projeto JavaScript com…

Última atualização: 2026-08-26

A migração de um projeto JavaScript existente para o TypeScript é o cenário mais comum e prático — esta lição explica como realizar a migração de forma segura e gradual.

1. Visão geral das estratégias de migração

(1) Três estratégias de migração

Estratégia Velocidade Risco Cenários adequados
Migração completa única Rápida Alta Projetos pequenos (< 20 arquivos)
Migração incremental, arquivo por arquivo Média Baixa Projetos de médio a grande porte (recomendado)
Migração gradual do JSDoc Lenta Muito baixa Projetos grandes/críticos
📌 Recomendação: migração incremental, arquivo por arquivo. Primeiro, faça com que o compilador TS aceite os arquivos JS; depois, altere as extensões dos arquivos de .js para .ts, um por um, certificando-se de que a compilação seja bem-sucedida após a alteração de cada arquivo.

(2) Visão geral das etapas da migração

TEXT 📖 Somente leitura
1. Initialization tsconfig.json(Relaxed Mode)
2. Install @types packages
3. Open allowJs——TS and JS Coexistence
4. File by file .js → .ts(Starting with the leaf file)
5. Gradually Enable Strict Options
6. Finally Unlocked strict


2. Etapa 1: Inicializar a configuração

(1) Gerar o arquivo tsconfig.json

BASH
tsc --init

(2) Configuração inicial da chave — Modo flexível

JSON
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "allowJs": true,
    "checkJs": false,
    "noImplicitAny": false,
    "strict": false,
    "outDir": "./dist",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
💡 Dica importante: Não habilite strict ou noImplicitAny no início da migração — primeiro certifique-se de que o projeto seja compilado e, em seguida, torne as restrições mais rigorosas gradualmente.

(3) Declaração do tipo de instalação

BASH
# Install the type declarations required by the project
npm install @types/node @types/express @types/lodash --save-dev


3. Etapa 2: use allowJs para habilitar a coexistência de JS e TS

(1) Ativar allowJs

JSON
{
  "compilerOptions": {
    "allowJs": true
  }
}

O allowJs permite que o compilador do TypeScript aceite arquivos .js — os projetos podem conter arquivos JS e TS sem que um afete o outro.

(2) Compatibilidade de importação

TYPESCRIPT
// main.ts —— Can be imported .js Documents
import { helper } from "./utils";  // utils.js Just being there is enough

// utils.js —— JS Documents,Untyped Annotations
function helper(value) {
  return value.toString();
}

(3) checkJs — Verificação opcional de tipos em JavaScript

JSON
{
  "compilerOptions": {
    "checkJs": true
  }
}

Quando o checkJs estiver ativado, o TypeScript também verificará se há erros de tipo nos arquivos .js (com base nos comentários JSDoc e na inferência de tipos). Recomenda-se mantê-lo desativado durante a fase inicial de migração — ative-o somente depois que os arquivos JS tiverem sido gradualmente convertidos para TS.



4. Etapa 3: Migrar arquivo por arquivo

(1) Sequência de migração

Comece com o arquivo que tem o menor número de dependências — o arquivo “folha” (que contém funções utilitárias, constantes etc., sem importar arquivos de outros projetos):

TEXT 📖 Somente leitura
Recommended Migration Order:
1. Constants File(config.js → config.ts)
2. Utility Functions(utils.js → utils.ts)
3. Type Definitions(types.js → types.ts)
4. Data Model(models.js → models.ts)
5. Service Layer(services.js → services.ts)
6. Controller/Routing(controllers.js → controllers.ts)
7. Input File(index.js → index.ts)

(2) Etapas para a migração de um único arquivo

TYPESCRIPT
// ── Before the Migration:utils.js ──
function formatPrice(price, currency) {
  return currency + price.toFixed(2);
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}
TYPESCRIPT
// ── After the migration:utils.ts ──
function formatPrice(price: number, currency: string = "¥"): string {
  return currency + price.toFixed(2);
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

(3) Técnicas de migração — Comece com any e, em seguida, refine

TYPESCRIPT
// Step 1:Add a type annotation,Uncertain usage any
function process(data: any, options: any): any {
  return data.filter(item => item.active);
}

// Step 2:Gradual Replacement any For a specific type
interface DataItem {
  id: number;
  name: string;
  active: boolean;
}

interface Options {
  limit?: number;
  sort?: "asc" | "desc";
}

function process(data: DataItem[], options: Options = {}): DataItem[] {
  let result = data.filter(item => item.active);
  if (options.sort === "desc") result.reverse();
  if (options.limit) result = result.slice(0, options.limit);
  return result;
}

▶ Exemplo: Migração de um arquivo de rota do Express

TYPESCRIPT
// ── Before the Migration:users.js ──
const express = require("express");
const router = express.Router();

router.get("/", async (req, res) => {
  const users = await User.findAll();
  res.json(users);
});

router.post("/", async (req, res) => {
  const { name, email } = req.body;
  const user = await User.create({ name, email });
  res.status(201).json(user);
});

module.exports = router;
▶ Experimente

Saída:

TEXT 📖 Somente leitura
// API endpoint response (status 200)
// Returns JSON data
TYPESCRIPT
// ── After the migration:users.ts ──
import { Router, Request, Response } from "express";

const router = Router();

interface CreateUserBody {
  name: string;
  email: string;
}

router.get("/", async (_req: Request, res: Response) => {
  const users = await User.findAll();
  res.json(users);
});

router.post("/", async (req: Request<{}, {}, CreateUserBody>, res: Response) => {
  const { name, email } = req.body;
  const user = await User.create({ name, email });
  res.status(201).json(user);
});

export default router;


5. Anotações de tipo do JSDoc

Se você não quiser alterar a extensão do arquivo, pode usar o JSDoc para adicionar um tipo ao seu arquivo JS:

(1) Anotações de tipo básicas

JAVASCRIPT
// utils.js — Use JSDoc to add types
/**
 * Pricing Format
 * @param {number} price - Price
 * @param {string} [currency="¥"] - Currency Symbol
 * @returns {string} Formatted Price
 */
function formatPrice(price, currency = "¥") {
  return currency + price.toFixed(2);
}

/**
 * @typedef {Object} User
 * @property {number} id
 * @property {string} name
 * @property {string} email
 */

/**
 * Search for a User
 * @param {number} id
 * @returns {Promise<User>}
 */
async function findUser(id) {
  // ...
}

module.exports = { formatPrice, findUser };

(2) Referenciando tipos JSDoc em arquivos TS

TYPESCRIPT
// main.ts
import { findUser } from "./utils";  // utils.js has JSDoc types

let user = await findUser(1);  // ✅ Type inference is correct(From JSDoc)

(3) Tags de tipo comuns do JSDoc

Tag Finalidade Exemplo
@type Tipo de variável @type {string}
@param Tipo de parâmetro @param {number} x
@returns Tipo de retorno @returns {string}
@typedef Definir tipo @typedef {Object} User
@property Propriedades do objeto @property {string} name
@template Parâmetros genéricos @template T
@callback Tipo de retorno de chamada @callback Handler


6. Armadilhas comuns na migração

(1) Armadilha 1: Explosão implícita any

TYPESCRIPT
// A large number of implicit variables after migration any——Don't rush to start it noImplicitAny
function process(data) {  // data Implicit any
  return data.map(item => item.name);  // item Me too any
}

Solução: Primeiro, faça com que o projeto seja compilado com sucesso; depois, adicione as anotações de tipo uma a uma; e, por fim, desative noImplicitAny.

(2) Armadilha 2: Conflito entre module.exports e import

TYPESCRIPT
// JS For use with documents module.exports
module.exports = function() { /* ... */ };

// TS Import Requirements esModuleInterop
import fn from "./legacy";  // Required esModuleInterop: true

(3) Armadilha 3: Bibliotecas de terceiros sem tipagem

TYPESCRIPT
import untypedLib from "untyped-lib";  // ❌ Cannot find the declaration file

// Temporary solution——Create shim.d.ts
declare module "untyped-lib" {
  const lib: any;
  export default lib;
}

(4) Armadilha 4: Perda do tipo this

TYPESCRIPT
// JS in this Dynamic Binding
const obj = {
  name: "Charlie",
  greet() {
    console.log(this.name);  // ✅ JS in OK
  }
};

// TS in this Needs annotation
const obj2 = {
  name: "Charlie",
  greet(this: { name: string }) {
    console.log(this.name);  // ✅ TS Needed in this Parameters
  }
};


▶ Exemplo: Adicionando tipos a objetos JavaScript simples

TYPESCRIPT
// ── Before: shapes.js ──
const shapes = [
  { type: "circle", radius: 5 },
  { type: "rectangle", width: 10, height: 20 },
  { type: "circle", radius: 3 }
];

function totalArea(shapes) {
  return shapes.reduce((sum, s) => {
    if (s.type === "circle") return sum + Math.PI * s.radius * s.radius;
    return sum + s.width * s.height;
  }, 0);
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
// Executed successfully
TYPESCRIPT
// ── After: shapes.ts ──
interface Circle { type: "circle"; radius: number; }
interface Rectangle { type: "rectangle"; width: number; height: number; }
type Shape = Circle | Rectangle;

const shapes: Shape[] = [
  { type: "circle", radius: 5 },
  { type: "rectangle", width: 10, height: 20 },
  { type: "circle", radius: 3 }
];

function totalArea(shapes: Shape[]): number {
  return shapes.reduce((sum, s) => {
    if (s.type === "circle") return sum + Math.PI * s.radius * s.radius;
    return sum + s.width * s.height;
  }, 0);
}

▶ Exemplo: Migração de JSDoc para TypeScript

JAVASCRIPT
// ── Before: math.js (JSDoc-typed) ──
/**
 * @template T
 * @param {T[]} arr
 * @param {(item: T) => boolean} predicate
 * @returns {T[]}
 */
function filter(arr, predicate) {
  return arr.filter(predicate);
}

/**
 * @typedef {Object} Point
 * @property {number} x
 * @property {number} y
 */

/**
 * @param {Point} a
 * @param {Point} b
 * @returns {number}
 */
function distance(a, b) {
  return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
// Executed successfully
TYPESCRIPT
// ── After: math.ts (native TS types) ──
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
  return arr.filter(predicate);
}

interface Point {
  x: number;
  y: number;
}

function distance(a: Point, b: Point): number {
  return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}

❓ Perguntas Frequentes

P: Quanto tempo leva para migrar um projeto grande? R: Depende da quantidade de código e da equipe. Para projetos de pequeno a médio porte (10.000 linhas ou menos), leva cerca de 1 a 2 semanas. Projetos grandes (100.000 linhas ou mais) podem exigir de 1 a 3 meses de migração incremental. O segredo é não fazer todas as alterações de uma só vez — migre um arquivo por vez, garantindo que o código seja compilado com sucesso após cada etapa.

P: O que é melhor: as anotações de tipo do JSDoc ou as do TypeScript? R: As anotações de tipo do TypeScript são melhores — elas oferecem uma sintaxe mais concisa, são mais poderosas e contam com melhor suporte dos editores. O JSDoc é uma solução intermediária que não exige a alteração da extensão do arquivo, tornando-o adequado para situações em que não é possível modificar o nome do arquivo. O objetivo final continua sendo converter os arquivos JS para TypeScript.

P: E quanto à CI durante a migração? R: Adicione tsc --noEmit à CI para verificação de tipos (sem gerar arquivos). Nas fases iniciais da migração, --noImplicitAny false é permitido para garantir que a CI não falhe devido a problemas de tipos. À medida que o processo se torna gradualmente mais rigoroso, a verificação de tipos na CI se torna cada vez mais rigorosa.

P: Qual devo usar, ts-ignore ou ts-expect-error? R: Use @ts-expect-error primeiro — ele gerará um erro se não houver nenhum erro de tipo na linha seguinte (para evitar que você se esqueça de remover o comentário após a correção). O @ts-ignore ignora erros incondicionalmente, o que pode ocultar erros que já foram corrigidos. Ambos são soluções provisórias; no final das contas, você deve corrigir os problemas de tipo.

📖 Resumo

📝 Exercícios

  1. Exercício básico (Dificuldade ⭐): Migre um arquivo utilitário simples em JavaScript (3 a 5 funções) para TypeScript — adicione anotações de tipo para os parâmetros e valores de retorno de cada função e certifique-se de que ele seja compilado com sucesso.
  2. Exercício avançado (Dificuldade: ⭐⭐): Configure tsconfig.json para oferecer suporte a projetos mistos de JS/TS — habilite allowJs, use o JSDoc para adicionar tipos a um arquivo JS e, em seguida, importe-o e utilize-o em um arquivo TS.
  3. Desafio (Dificuldade: ⭐⭐⭐): Simule a migração de um projeto Express — configure tsconfig, adicione tipos às Request/Response do Express, lide com a segurança de tipos para req.body (defina a interface body) e lide com os tipos para req.params.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%