Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Package Exports no Node.js: exponha APIs públicas

Atualizado em: 10 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Package Exports no Node.js é o mecanismo usado para definir quais caminhos de um pacote podem ser importados por aplicações consumidoras. O campo exports do package.json funciona como uma fronteira pública: ele libera entradas suportadas e bloqueia o acesso acidental a arquivos internos.

Sem essa fronteira, consumidores podem depender de qualquer arquivo presente no pacote. Uma reorganização interna, mesmo sem alterar a API pretendida, passa a causar quebras. Com exports, o mantenedor controla a superfície pública, cria subpaths estáveis e prepara compatibilidade entre ESM e CommonJS.

Neste guia, você aprenderá entrada principal, subpath exports, padrões, encapsulamento, tipos TypeScript, compatibilidade, testes e migração segura.

Por que usar o campo exports?

Um pacote tradicional pode definir apenas main:

{
  "name": "minha-lib",
  "main": "./dist/index.js"
}

Isso informa a entrada principal, mas não impede imports internos:

const helper = require('minha-lib/dist/internal/helper.js');

Quando exports está presente, apenas os caminhos declarados ficam disponíveis.

Export principal

{
  "name": "minha-lib",
  "type": "module",
  "exports": "./dist/index.js"
}

O consumidor usa:

import { criarCliente } from 'minha-lib';

Formato com ponto

Quando haverá mais de uma entrada, use um objeto:

{
  "exports": {
    ".": "./dist/index.js",
    "./errors": "./dist/errors.js",
    "./testing": "./dist/testing.js"
  }
}

Agora estes imports são públicos:

import { criarCliente } from 'minha-lib';
import { AppError } from 'minha-lib/errors';
import { criarFake } from 'minha-lib/testing';

Encapsulamento de arquivos internos

Um import não declarado gera erro, mesmo que o arquivo exista fisicamente no pacote. Isso permite mover implementações internas sem transformar cada arquivo em compromisso público.

O encapsulamento não é uma barreira de segurança contra acesso ao disco. Ele é um contrato de resolução de módulos.

Subpaths pequenos e estáveis

Evite exportar centenas de arquivos individuais. Prefira entradas orientadas a capacidades:

{
  "exports": {
    ".": "./dist/index.js",
    "./http": "./dist/http/index.js",
    "./database": "./dist/database/index.js",
    "./errors": "./dist/errors/index.js"
  }
}

Cada subpath deve ter finalidade clara e documentação própria.

Exportando package.json

Alguns consumidores precisam ler versão ou metadados:

{
  "exports": {
    ".": "./dist/index.js",
    "./package.json": "./package.json"
  }
}

Exponha somente quando houver necessidade. Não transforme arquivos administrativos em API por conveniência.

Padrões de subpath

{
  "exports": {
    "./features/*.js": "./dist/features/*.js"
  }
}

Padrões reduzem repetição, mas ampliam a superfície pública. Para bibliotecas pequenas e médias, declarações explícitas são mais fáceis de versionar.

Bloqueando caminhos específicos

{
  "exports": {
    ".": "./dist/index.js",
    "./features/*.js": "./dist/features/*.js",
    "./features/private/*": null
  }
}

O valor null torna a intenção explícita, mas uma API pública menor continua sendo a melhor estratégia.

Exports e ESM

Um pacote ESM pode declarar:

{
  "type": "module",
  "exports": {
    ".": "./dist/index.js"
  }
}

Veja ESM no Node.js para entender extensões, import.meta.url e migração.

Exports condicionais

É possível entregar arquivos diferentes conforme o método de carregamento:

{
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

A ordem e a compatibilidade exigem cuidado. O artigo Conditional Exports no Node.js aprofunda essa configuração.

Tipos TypeScript

Os tipos precisam acompanhar cada entrada pública. Uma configuração simples:

{
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./errors": {
      "types": "./dist/errors.d.ts",
      "import": "./dist/errors.js"
    }
  }
}

Teste o pacote em um projeto consumidor real. Ferramentas e versões podem interpretar metadados de formas diferentes.

O problema do dual package hazard

Quando um pacote fornece ESM e CommonJS, a mesma aplicação pode carregar duas instâncias da biblioteca. Estado singleton, classes e símbolos podem deixar de ser compartilhados.

// duas árvores de carregamento podem manter caches diferentes
import lib from 'minha-lib';
const libCjs = require('minha-lib');

Evite estado global e teste a combinação de consumidores.

Compatibilidade com main

Alguns pacotes mantêm main para ferramentas antigas:

{
  "main": "./dist/index.cjs",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

Para runtimes que entendem exports, esse campo tem prioridade. Documente a versão mínima suportada.

Imports internos do próprio pacote

O campo imports cria aliases privados iniciados por #:

{
  "imports": {
    "#config": "./src/config.js",
    "#logger": "./src/logger.js"
  }
}
import { logger } from '#logger';

Esses aliases não são exportados aos consumidores. Eles ajudam a evitar caminhos relativos profundos sem publicar detalhes internos.

Estrutura recomendada

src/
  index.js
  errors.js
  http/
    index.js
  internal/
    normalize.js
dist/
  index.js
  errors.js
  http/
    index.js

Somente index, errors e http devem aparecer em exports. A pasta internal permanece privada.

Testando a API pública

Crie um teste que empacota e instala a biblioteca em diretório temporário:

npm pack
mkdir consumer-test
cd consumer-test
npm init -y
npm install ../minha-lib-1.0.0.tgz

Verifique imports permitidos e bloqueados, tipos, ESM, CommonJS e subpaths.

Teste de caminhos não autorizados

import assert from 'node:assert/strict';

await assert.rejects(
  import('minha-lib/dist/internal/helper.js')
);

Esse teste protege o encapsulamento durante mudanças no build.

Publicação e arquivos incluídos

O campo exports não adiciona arquivos ao pacote. Use files e confira o resultado de npm pack --dry-run:

{
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ]
}

Garanta que todos os alvos declarados realmente sejam publicados.

Migração para exports

  1. liste os imports usados por consumidores;
  2. identifique caminhos internos adotados sem intenção;
  3. crie entradas públicas equivalentes;
  4. adicione exports primeiro em versão de teste;
  5. execute testes de consumidores;
  6. publique mudança incompatível em versão major quando necessário;
  7. documente substituições para caminhos bloqueados;
  8. monitore issues após a publicação.

Semantic Versioning

Remover ou renomear um subpath público é breaking change. Adicionar novo subpath normalmente é compatível, desde que não altere resolução existente.

Não trate a estrutura interna do pacote como detalhe depois que ela foi usada publicamente sem exports. Faça uma migração explícita.

Erros comuns

  • declarar alvo que não entra no pacote;
  • esquecer extensões nos arquivos ESM;
  • exportar toda a pasta dist;
  • manter tipos apenas para a entrada principal;
  • criar duas instâncias com ESM e CommonJS;
  • alterar subpath sem versão major;
  • depender de ordem condicional sem testes;
  • confundir encapsulamento com segurança do sistema de arquivos.

Checklist para produção

  • defina uma API pública pequena;
  • declare ponto principal com .;
  • adicione somente subpaths necessários;
  • publique todos os arquivos-alvo;
  • inclua tipos;
  • teste ESM e CommonJS;
  • instale o tarball em projeto consumidor;
  • bloqueie imports internos;
  • versione alterações incompatíveis.

Conclusão

Package Exports no Node.js transforma a estrutura de um pacote em contrato explícito. O mantenedor deixa de expor acidentalmente toda a pasta publicada e passa a controlar entradas, subpaths, tipos e compatibilidade.

Comece com uma superfície pequena, teste o pacote empacotado e trate cada caminho exportado como API de longo prazo. Essa disciplina reduz quebras e permite reorganizar a implementação com confiança.

Consulte a documentação oficial de pontos de entrada de pacotes do Node.js e a documentação do package.json no npm.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita