Estratégias de fallback de tipos para bibliotecas sem @types

1. Introdução ao problema: bibliotecas JavaScript sem tipagem

O ecossistema npm é vasto e repleto de bibliotecas JavaScript que nunca receberam definições de tipo TypeScript. Embora o repositório DefinitelyTyped cubra milhares de pacotes com @types/*, muitas bibliotecas populares ou legadas permanecem sem suporte oficial. A ausência de tipos faz com que o TypeScript trate essas importações como any, resultando em perda de autocomplete, inferência de tipos e segurança em tempo de compilação. Antes de criar tipos do zero, avalie se vale a pena migrar para uma alternativa tipada ou se a biblioteca é essencial o suficiente para justificar o esforço de tipagem manual.

2. Criando declarações de módulo (*.d.ts) manuais

A estratégia mais direta é criar um arquivo de declaração de módulo. Crie um arquivo types.d.ts (ou shims.d.ts) na raiz do seu projeto e declare o módulo da biblioteca:

// types.d.ts
declare module 'biblioteca-sem-tipos' {
  export function funcaoPrincipal(param: string): number;
  export interface Config {
    timeout: number;
    retries?: number;
  }
  export function inicializar(config: Config): void;
}

Esse arquivo informa ao TypeScript que o módulo biblioteca-sem-tipos possui as funções e interfaces declaradas. A partir de agora, qualquer importação desse módulo terá tipagem correta. Você pode começar com apenas algumas exportações e expandir conforme necessário.

3. Usando declare module com wildcards e namespaces globais

Algumas bibliotecas expõem múltiplos submódulos ou poluem o escopo global. Para submódulos dinâmicos, use wildcards:

declare module 'biblioteca/*' {
  export function helper(id: string): unknown;
}

Para scripts que adicionam propriedades ao objeto window, utilize merging de interfaces:

interface Window {
  minhaBiblioteca: {
    versao: string;
    executar(): void;
  };
}

Isso permite acessar window.minhaBiblioteca com tipos completos, sem necessidade de casts manuais.

4. Estratégia de tipos parciais com Partial e unknown

Nem sempre é necessário tipar uma biblioteca inteira. Adote uma abordagem incremental: tipifique apenas as funções que você realmente utiliza. Para parâmetros ou retornos desconhecidos, use unknown e depois faça casts seguros com type guards:

declare module 'biblioteca-parcial' {
  export function apiCall(endpoint: string): unknown;
}

// No código de uso
import { apiCall } from 'biblioteca-parcial';

function isUserData(data: unknown): data is { id: number; nome: string } {
  return typeof data === 'object' && data !== null && 'id' in data && 'nome' in data;
}

const resultado = apiCall('/users/1');
if (isUserData(resultado)) {
  console.log(resultado.nome); // Tipo inferido corretamente
}

Essa técnica evita o uso excessivo de any e mantém a segurança gradualmente.

5. Aproveitando declare module com export = para CommonJS

Bibliotecas legadas que usam module.exports (CommonJS) exigem sintaxe especial. O TypeScript oferece export = para representar a atribuição direta ao exports:

declare module 'biblioteca-cjs' {
  const biblioteca: {
    saudacao(nome: string): string;
    versao: number;
  };
  export = biblioteca;
}

No código, a importação deve usar import biblioteca = require('biblioteca-cjs') ou import * as biblioteca from 'biblioteca-cjs'. Essa distinção é crucial para bibliotecas que não usam export default.

6. Ferramentas auxiliares: ts-expect-error e @ts-ignore

Em situações onde a tipagem é inviável ou temporária, use comentários de controle. Prefira @ts-expect-error a @ts-ignore, pois o primeiro alerta quando o erro subjacente é corrigido:

// @ts-expect-error - a biblioteca 'x' não tem tipos, mas funciona em runtime
import { recurso } from 'biblioteca-antiga';
recurso();

Use // @ts-nocheck apenas em arquivos inteiros que você não deseja tipar. Documente sempre o motivo do silenciamento para facilitar revisões futuras.

7. Gerando tipos automaticamente com dts-gen e typescript-json-schema

Ferramentas automatizadas podem gerar esboços de tipos a partir do comportamento runtime. O dts-gen analisa o objeto importado e produz declarações iniciais:

npm install -g dts-gen
dts-gen -m nome-da-biblioteca -o tipos-gerados.d.ts

Para bibliotecas que expõem APIs baseadas em JSON Schema, use typescript-json-schema:

npm install -g typescript-json-schema
typescript-json-schema "caminho/para/schema.json" "*" --out "tipos.d.ts"

Essas ferramentas geram tipos funcionais, mas exigem revisão manual, pois podem produzir any excessivo ou estruturas imprecisas.

8. Conclusão: criando um workflow sustentável de tipagem

Adotar estratégias de fallback de tipos não significa abandonar a segurança do TypeScript, mas sim adaptá-la à realidade do ecossistema. Siga este checklist ao encontrar uma biblioteca sem @types:

  1. Verifique se existe um pacote @types/* alternativo ou uma biblioteca similar tipada.
  2. Crie declarações manuais para as funções que você usa, começando com tipos parciais.
  3. Utilize unknown e type guards para evitar any em pontos críticos.
  4. Documente decisões de tipagem com comentários @ts-expect-error quando necessário.
  5. Considere ferramentas automáticas para esboços iniciais, mas revise o resultado.
  6. Integre a checagem de tipos no CI para garantir que declarações parciais não quebrem a build.

Com esse workflow, você mantém os benefícios do TypeScript mesmo em projetos com dependências JavaScript não tipadas.

Referências