Publicando Bibliotecas TypeScript: Types, Exports e Compatibilidade na Prática Já leu

Preparando seu Projeto TypeScript para Publicação Antes de publicar uma biblioteca TypeScript, você precisa entender que um projeto pronto para distribuição não é simplesmente um repositório com código TypeScript compilado. É necessário configurar corretamente o ambiente, definir metadados apropriados e garantir que os consumidores da sua biblioteca recebam tanto o código compilado quanto as informações de tipo (type definitions) sem ambiguidades. A primeira etapa é configurar seu de forma que a compilação gere não apenas JavaScript, mas também os arquivos (declaration files). Esses arquivos contêm as definições de tipo e são críticos para desenvolvedores que usam sua biblioteca em projetos TypeScript. Além disso, você precisa decidir qual será a estrutura de diretórios no seu pacote distribuído e como os consumidores importarão seu código. Configuração de TypeScript e Geração de Type Definitions Estruturando o tsconfig.json O arquivo é o coração da configuração de qualquer projeto TypeScript. Para uma biblioteca que será publicada, você deve focar em três aspectos: a geração automática

Preparando seu Projeto TypeScript para Publicação

Antes de publicar uma biblioteca TypeScript, você precisa entender que um projeto pronto para distribuição não é simplesmente um repositório com código TypeScript compilado. É necessário configurar corretamente o ambiente, definir metadados apropriados e garantir que os consumidores da sua biblioteca recebam tanto o código compilado quanto as informações de tipo (type definitions) sem ambiguidades.

A primeira etapa é configurar seu tsconfig.json de forma que a compilação gere não apenas JavaScript, mas também os arquivos .d.ts (declaration files). Esses arquivos contêm as definições de tipo e são críticos para desenvolvedores que usam sua biblioteca em projetos TypeScript. Além disso, você precisa decidir qual será a estrutura de diretórios no seu pacote distribuído e como os consumidores importarão seu código.

Configuração de TypeScript e Geração de Type Definitions

Estruturando o tsconfig.json

O arquivo tsconfig.json é o coração da configuração de qualquer projeto TypeScript. Para uma biblioteca que será publicada, você deve focar em três aspectos: a geração automática de declarações de tipo, a escolha do alvo de compilação (ECMAScript version) e a definição clara dos diretórios de entrada e saída.

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020"],
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

A opção declaration: true instrui o TypeScript a gerar um arquivo .d.ts para cada arquivo TypeScript compilado. declarationMap: true cria source maps para essas declarações, permitindo que desenvolvedores naveguem até o código-fonte original ao usar sua biblioteca em um IDE. O target: "ES2020" garante compatibilidade com navegadores modernos enquanto mantém características recentes de JavaScript. Note que module: "ESNext" permite que ferramentas de empacotamento como webpack e rollup façam tree-shaking, reduzindo o tamanho final do bundle dos consumidores.

Exemplo Prático de Type Definitions

Considere uma biblioteca simples de utilitários matemáticos. Seu arquivo fonte seria:

// src/math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

export interface Calculator {
  value: number;
  add(n: number): Calculator;
  multiply(n: number): Calculator;
}

export class ChainedCalculator implements Calculator {
  value: number = 0;

  constructor(initialValue: number = 0) {
    this.value = initialValue;
  }

  add(n: number): Calculator {
    this.value += n;
    return this;
  }

  multiply(n: number): Calculator {
    this.value *= n;
    return this;
  }
}

Após executar tsc, você terá em dist/math.d.ts:

export declare function add(a: number, b: number): number;
export declare function multiply(a: number, b: number): number;
export interface Calculator {
  value: number;
  add(n: number): Calculator;
  multiply(n: number): Calculator;
}
export declare class ChainedCalculator implements Calculator {
  value: number;
  constructor(initialValue?: number);
  add(n: number): Calculator;
  multiply(n: number): Calculator;
}

Essa declaração é automaticamente gerada e garante que consumidores da sua biblioteca terão acesso completo a informações de tipo, mesmo se estiverem usando JavaScript puro ou TypeScript.

Configuração do package.json e Estratégias de Export

Definindo Entry Points

O package.json é o documento que descreve sua biblioteca para o npm. Para uma biblioteca TypeScript publicada, você precisa indicar qual arquivo é o ponto de entrada principal, onde estão as type definitions e, se aplicável, oferecer suporte a diferentes módulos (CommonJS, ES Modules, etc.).

{
  "name": "@seu-nome/math-utils",
  "version": "1.0.0",
  "description": "Biblioteca de utilitários matemáticos com suporte total a TypeScript",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    },
    "./math": {
      "import": "./dist/math.mjs",
      "require": "./dist/math.js",
      "types": "./dist/math.d.ts"
    }
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "keywords": ["math", "typescript", "utils"],
  "author": "Seu Nome",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/seu-nome/math-utils"
  }
}

O campo exports é uma das inovações mais importantes na eclosfera do Node.js. Ele permite que você defina múltiplos pontos de entrada e especifique qual arquivo usar para diferentes contextos (importações ES modules vs CommonJS). Isso é crucial para compatibilidade máxima. O campo types aponta para onde encontrar as declarações de tipo do ponto de entrada principal.

Estrutura de Exportação Modular

Se sua biblioteca é grande, você pode oferecer importações granulares. Isso reduz o tamanho dos bundles dos consumidores. Suponha que sua estrutura de diretórios seja:

src/
├── index.ts
├── math.ts
├── string.ts
└── array.ts

Com o exports apropriado no package.json, consumidores podem fazer:

// Importação do ponto de entrada principal
import { add, multiply } from '@seu-nome/math-utils';

// Ou importações granulares
import { add } from '@seu-nome/math-utils/math';
import { capitalize } from '@seu-nome/math-utils/string';

Para isso funcionar, cada arquivo em src/ deve ter seu próprio ponto de entrada em dist/, e o package.json deve listar todos eles na seção exports. Seu script de build (usando tsc ou outra ferramenta) deve gerar tanto .js quanto .mjs para máxima compatibilidade.

Compatibilidade e Versionamento Semântico

Entendendo Mudanças Quebradoras vs Compatíveis

A compatibilidade de uma biblioteca TypeScript vai além de apenas manter a mesma interface de função. Inclui a compatibilidade estrutural de tipos, que é o sistema de tipos do TypeScript. Uma mudança é considerada "quebradora" (breaking change) se força consumidores a atualizar seu código para continuar usando sua biblioteca.

Exemplos de mudanças compatíveis:
- Adicionar um novo parâmetro opcional a uma função
- Adicionar uma propriedade opcional a uma interface
- Alargar o tipo de retorno (ex: numbernumber | string)
- Adicionar novos exports sem remover os antigos

Exemplos de mudanças quebradoras:
- Remover ou renomear uma função ou classe exportada
- Tornar um parâmetro obrigatório quando era opcional
- Estreitar o tipo de um parâmetro
- Remover uma propriedade de uma interface exportada

Versionamento Semântico na Prática

{
  "version": "2.3.1"
}

Essa string segue o padrão MAJOR.MINOR.PATCH. MAJOR é incrementado para mudanças quebradoras, MINOR para novas features compatíveis com versões anteriores, e PATCH para correções de bugs. Se sua versão é 2.3.1 e você introduz uma mudança quebradora, a próxima versão deve ser 3.0.0. Se apenas adiciona uma função nova, é 2.4.0. Se corrige um bug, é 2.3.2.

// Versão 1.0.0 - Sua biblioteca original
export function process(data: string): string {
  return data.toUpperCase();
}

// Versão 1.1.0 - Adiciona parâmetro opcional (compatível)
export function process(data: string, separator?: string): string {
  return data.toUpperCase() + (separator || '');
}

// Versão 2.0.0 - Remove a função antiga e cria uma nova (quebradora)
export function transform(data: string, options: { uppercase?: boolean } = {}): string {
  return options.uppercase !== false ? data.toUpperCase() : data;
}

Ao publicar no npm, você comunica essa história através das tags de versão no seu repositório. Consumidores que especificam "@seu-nome/math-utils": "^1.1.0" receberão atualizações até (mas não incluindo) versão 2.0.0 automaticamente.

Construindo e Testando sua Biblioteca para Publicação

Script de Build Profissional

Um script de build deve não apenas compilar TypeScript para JavaScript, mas também gerar as variantes necessárias (CommonJS e ES Modules) e preparar os arquivos de declaração. Uma abordagem moderna usa tsc com scripts auxiliares:

{
  "scripts": {
    "build": "npm run clean && npm run compile && npm run validate-types",
    "clean": "rm -rf dist",
    "compile": "tsc",
    "validate-types": "tsc --noEmit",
    "test": "jest",
    "prepublishOnly": "npm run test && npm run build",
    "lint": "eslint src --ext .ts"
  }
}

O script prepublishOnly é especial: ele é executado automaticamente pelo npm antes de publicar seu pacote, garantindo que você nunca publique uma versão sem testes passando e sem compilação bem-sucedida. O validate-types roda TypeScript sem emitir arquivos (apenas fazendo type-checking), o que é rápido e garante que não há erros de tipo.

Testando Compatibilidade com Consumidores

Antes de publicar, você deve testar como seus consumidores experimentarão a biblioteca. Uma prática excelente é testar tanto em projetos TypeScript quanto em projetos JavaScript:

// tests/integration.test.ts
import { add, multiply, ChainedCalculator } from '../src/math';

describe('Math Utils Integration', () => {
  test('add function works correctly', () => {
    expect(add(2, 3)).toBe(5);
  });

  test('ChainedCalculator allows method chaining', () => {
    const result = new ChainedCalculator(5)
      .add(3)
      .multiply(2)
      .value;

    expect(result).toBe(16);
  });

  test('types are correctly exposed', () => {
    const calc: ChainedCalculator = new ChainedCalculator();
    expect(calc).toBeDefined();
  });
});

Além dos testes unitários, você pode criar um projeto de teste separado que importa sua biblioteca localmente (usando npm link ou npm pack) e verifica se os tipos funcionam corretamente em um ambiente real:

// test-consumer/index.ts
import { add, ChainedCalculator } from 'math-utils';

// Isso deve compilar sem erros se os tipos estão corretos
const result: number = add(1, 2);

const calc = new ChainedCalculator();
// calc é do tipo Calculator aqui, TypeScript sabe disso
const chainedResult: number = calc.add(5).multiply(2).value;

Conclusão

Publicar uma biblioteca TypeScript profissional envolve três pilares principais. Primeiro, configurar corretamente a geração de type definitions através do tsconfig.json, garantindo que consumidores recebam informações de tipo precisas e completas — isso diferencia uma biblioteca TypeScript competente de uma genérica. Segundo, dominar o package.json e o campo exports, permitindo que diferentes tipos de consumidores (CommonJS, ES modules, TypeScript puro, JavaScript puro) usem sua biblioteca sem fricção. Terceiro, respeitar o versionamento semântico e testar compatibilidade, pois a reputação de sua biblioteca depende de confiabilidade — quebras inesperadas alienam usuários rapidamente.

Referências


Artigos relacionados