12k
All articles

Publicando uma biblioteca JavaScript com Rslib

Publique bibliotecas JavaScript com Rslib: configure builds ESM e CJS, gere declarações TypeScript, inclua estilos de componentes e detecte erros do pacote no CI.

OpenReplay Team
OpenReplay Team
Publicando uma biblioteca JavaScript com Rslib

O Rslib é uma ferramenta de build de bibliotecas baseada no Rsbuild e no Rspack. A partir de uma única configuração, ele gera saídas ESM e CommonJS, declarações TypeScript, componentes compilados pelo framework e assets processados. O foco são pacotes publicados no npm, não aplicações que você faz deploy.

Essa distinção é importante. Um bundle de aplicação só precisa rodar em uma aba do navegador. Uma biblioteca é importada pelos bundlers, versões do Node.js e configurações de TypeScript de outras pessoas, e quebra de formas que seu próprio app de teste nunca revela: um require() que falha, tipos que são resolvidos como any, um componente publicado sem seus estilos. Este guia leva um pacote do primeiro build até uma verificação pré-publicação no CI usando o Rslib 1.0, lançado em 3 de setembro de 2026.

Principais pontos

  • O Rslib 1.0 tem uma configuração e uma API JavaScript estáveis que seguem o Versionamento Semântico, e exige Node.js 20.19+ ou 22.12+.
  • A documentação do Rslib recomenda ESM puro para pacotes novos e um build duplo ESM/CJS apenas quando algum consumidor específico não consegue carregar ESM.
  • No Node.js ^20.19.0 ou >=22.12.0, código CommonJS pode fazer require() de um pacote ESM puro, desde que nenhum módulo carregado, incluindo o entry, use await de nível superior.
  • O modo bundle (bundle: true) é adequado para SDKs, CLIs e utilitários Node.js. O modo bundleless (bundle: false) preserva a estrutura de arquivos do código-fonte e é adequado para bibliotecas de componentes.
  • rsbuild-plugin-publint e rsbuild-plugin-arethetypeswrong podem rodar dentro do rslib build apenas no CI, fazendo com que erros de empacotamento quebrem o build antes do npm publish.

Por que publicar uma biblioteca é mais difícil do que construir uma aplicação?

Publicar uma biblioteca é mais difícil do que construir uma aplicação porque um pacote publicável precisa acertar quatro coisas ao mesmo tempo. Ele precisa de uma saída ESM, e possivelmente CJS, que carregue em todos os runtimes que você suporta. Precisa de arquivos de declaração que sejam resolvidos com a configuração de moduleResolution de cada consumidor. Os componentes precisam ser publicados com seu CSS. E o campo exports do package.json precisa apontar cada condição para um arquivo que realmente existe.

Normalmente, cada uma dessas tarefas fica a cargo de uma ferramenta diferente: um bundler para o JavaScript, o tsc para as declarações, uma etapa de PostCSS para os estilos e um mapa exports escrito à mão para amarrar tudo. Sempre que uma dessas ferramentas muda um caminho de saída, o pacote quebra.

O que é o Rslib?

O Rslib é uma ferramenta de build de bibliotecas que cobre essas quatro tarefas em um único build. Um array lib define cada formato de saída. A opção dts gera as declarações e, no modo bundleless, também corrige aliases de caminho e extensões de import dentro delas para que correspondam aos arquivos JavaScript gerados. O tratamento de CSS depende do modo de build. O Publint detecta erros no exports. O Rslib não escreve o campo exports por você; ele verifica o que você escreveu.

Um build mínimo com Rslib, de ponta a ponta

A forma mais rápida de começar é gerar um projeto com o scaffold ou executar um único comando da CLI. Para um pacote já existente, adicione um arquivo de configuração com uma entrada lib por formato. De acordo com o quick start, o Rslib v1 exige Node.js 20.19+ ou 22.12+.

npm create rslib@latest
# or, in an existing package
npm add @rslib/core -D

Um build de formato único não precisa de arquivo de configuração:

npx rslib build --entry src/index.ts --format esm --dts

--entry define o entry do código-fonte, --format esm escolhe o formato de módulo e --dts gera as declarações. Para saída dupla, crie o rslib.config.ts:

// rslib.config.ts
import { defineConfig } from '@rslib/core';

export default defineConfig({
  lib: [
    { format: 'esm', dts: true },
    { format: 'cjs', dts: true },
  ],
  syntax: 'es2023',
});

Tudo o que é definido no nível superior se aplica a todas as saídas. O Rslib mescla essas opções com cada item de lib, então cada entrada só lista o que é diferente. O Rslib 1.0 também permite omitir lib completamente quando você precisa apenas de uma saída ESM padrão. Adicione um script de build:

{ "scripts": { "build": "rslib build" } }

Após o build, o diretório de saída padrão é dist. Ele contém um build ESM, um build CJS e os arquivos de declaração. As extensões dos arquivos seguem lib.autoExtension. Com "type": "module", a saída ESM recebe .js e a saída CJS recebe .cjs. Sem ele, o CJS recebe .js e o ESM recebe .mjs. Por padrão, os dois formatos podem gerar declarações com o mesmo nome .d.ts. As opções de dts controlam as extensões de declaração por formato. No seu mapa exports, aponte a condição import para o entry ESM e a condição require para o entry CJS, e associe a cada uma as declarações correspondentes. Copie os caminhos reais do seu próprio dist em vez de adivinhá-los.

Nas saídas ESM e CJS, o Rslib não inclui no bundle, por padrão, dependencies, optionalDependencies nem peerDependencies, então uma peer dependency como o React permanece externa. Já as devDependencies são incluídas no bundle.

Publicar apenas ESM ou ESM/CJS duplo?

O guia de formatos de saída do Rslib recomenda publicar pacotes novos como ESM puro e adicionar CJS apenas quando algum consumidor claramente precisar. A saída dupla custa mais do que uma segunda etapa de build. São dois conjuntos de entradas exports para manter corretos e o dobro de configurações para testar. Um consumidor que carrega os dois formatos também acaba com duas instâncias do seu pacote, o que pode quebrar estado compartilhado e verificações de identidade no estilo instanceof.

Muitos consumidores CommonJS já nem precisam mais de um build CJS. No Node.js ^20.19.0 ou >=22.12.0, eles podem carregar um pacote ESM puro diretamente. Duas condições precisam ser atendidas: seu exports deve permitir que uma chamada require() alcance o entry ESM, e nenhum módulo no grafo carregado pode usar await de nível superior:

const utils = require('my-esm-only-package');

Publique saída dupla apenas quando parte dos seus usuários CommonJS estiver em uma versão do Node, ferramenta ou runtime que não consegue carregar ESM via require(), ou quando algum usuário pedir um arquivo CJS separado. Os exemplos de pacotes do Node.js cobrem o lado do package.json em ambas as escolhas.

Verificações antes de publicar

Dois plugins do Rsbuild detectam bugs de empacotamento em um build do Rslib antes que o npm detecte. O rsbuild-plugin-publint procura erros comuns no seu package.json, na estrutura de arquivos e na configuração do exports. O rsbuild-plugin-arethetypeswrong testa se seus tipos continuam carregando quando o consumidor usa uma configuração de resolução de módulos diferente. O anúncio do Rslib 1.0 mostra os dois rodando apenas quando a variável CI está definida:

// rslib.config.ts
import { defineConfig } from '@rslib/core';
import { pluginAreTheTypesWrong } from 'rsbuild-plugin-arethetypeswrong';
import { pluginPublint } from 'rsbuild-plugin-publint';

export default defineConfig({
  lib: [
    { format: 'esm', dts: true },
    { format: 'cjs', dts: true },
  ],
  plugins: [
    pluginPublint({ enable: Boolean(process.env.CI) }),
    pluginAreTheTypesWrong({ enable: Boolean(process.env.CI) }),
  ],
});

Instale os dois como dependências de desenvolvimento. Os builds locais continuam rápidos, e a etapa rslib build que você já executa no CI passa a funcionar como barreira antes da publicação.

A geração de declarações também fica mais rápida em projetos grandes. Com o TypeScript 7 ou superior, o Rslib usa o tsgo automaticamente e continua fazendo a checagem de tipos. A opção experimental dts.isolated é ainda mais rápida, mas pula a checagem de tipos, então execute uma etapa separada de type-check junto com ela. No teste da própria equipe do Rslib no repositório do Rsbuild, a geração de declarações levou 9,7 s com TypeScript 6, 4,1 s com TypeScript 7 e 2,3 s com declarações isoladas. Os ganhos no seu projeto podem variar.

Bibliotecas de componentes e o modo bundleless

Use o modo bundleless para bibliotecas de componentes e o modo bundle para SDKs e CLIs. Com bundle: false, o Rslib transforma cada arquivo-fonte em um arquivo de saída próprio, mantendo a mesma estrutura de pastas do código-fonte. Isso deixa o tree shaking e o carregamento sob demanda a cargo do bundler do consumidor. Os compiladores de React, Vue, Svelte e Solid são integrados por meio de plugins do Rsbuild, e o create-rslib inclui um template para cada framework.

// rslib.config.ts
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rslib/core';

export default defineConfig({
  bundle: false,
  output: { target: 'web' },
  plugins: [pluginReact()],
});

Os componentes importam estilos da forma habitual:

import './button.css';
import styles from './button.module.css';

CSS Modules e PostCSS funcionam sem configuração adicional, e a forma como o Rslib gera os estilos muda conforme o modo de build. Sass, Less, Stylus e Tailwind CSS são adicionados por meio de plugins do Rsbuild. Bibliotecas Vue, Svelte e Solid usam a mesma estrutura de configuração com o respectivo plugin. No anúncio do Rslib 1.0, um benchmark com 10.000 componentes React mostra que a versão 1.0 reduz o tempo de build sem cache em cerca de 24,3% e o tempo de build com cache em cerca de 56,7% em comparação com a 0.7.0, com uma saída cerca de 32,2% menor antes do gzip e 4,1% menor depois.

Saída para Module Federation

O Rslib também pode gerar uma biblioteca como remote de Module Federation com format: 'mf', para que várias aplicações a carreguem em tempo de execução em vez de instalá-la pelo npm. Use essa opção quando o deploy independente de código compartilhado for mais importante do que releases versionadas de pacotes.

Rslib vs tsup vs modo biblioteca do Vite

Rslib, tsup e o modo biblioteca do Vite diferem mais em escopo do que em velocidade. O tsup é construído sobre o esbuild e foca em gerar bundles de TypeScript. O modo biblioteca do Vite pega uma ferramenta de build de aplicações e a direciona para um entry de biblioteca. O Rslib foi criado especificamente para bibliotecas, sobre o Rsbuild e o Rspack, e uma única configuração cobre formatos de saída, modos bundle e bundleless, declarações, compilação de frameworks, assets e verificações pré-publicação. Se sua aplicação já usa Rsbuild ou Rspack, a biblioteca pode compartilhar os mesmos plugins e configurações.

O artigo sobre o estado atual dos bundlers JavaScript mostra onde o Rspack se encaixa no ecossistema como um todo. Se você preferir montar seu próprio pipeline a partir de um bundler de uso geral e plugins, comece por este guia de introdução ao Rollup. Para migrações, o Rslib tem um guia de migração a partir do tsc, e o quick start traz o link para um guia equivalente para o tsup.

Conclusão

Uma biblioteca só está pronta para publicação quando seus formatos, declarações, estilos e mapa exports estão todos de acordo entre si, e o Rslib gera e verifica tudo isso em conjunto. Comece com ESM puro, adicione um entry CJS ao array lib apenas quando algum consumidor precisar e ative o publint e o arethetypeswrong no CI antes da sua próxima release.

Perguntas frequentes

Como incluir uma dependência no bundle gerado pelo Rslib em vez de deixá-la externa?

Defina output.autoExternal dentro da entrada lib e desative o campo de dependências que você quer incluir no bundle, por exemplo, dependencies definido como false. O Rslib v1 descontinua lib.autoExternal em favor do output.autoExternal do Rsbuild, mas a opção antiga ainda funciona. A externalização automática só se aplica ao modo bundle. No modo bundleless, as dependências nunca são incluídas no bundle. Para manter apenas pacotes específicos como externos, liste-os em output.externals.

O Rslib consegue agrupar todas as declarações TypeScript em um único arquivo?

Sim. Instale o @microsoft/api-extractor como dependência de desenvolvimento e defina dts.bundle como true na entrada lib. O API Extractor faz o bundle das declarações, e dts.bundle é false por padrão. As dependências diretas que não são externalizadas têm suas declarações incorporadas automaticamente. Definir dts.bundle.bundledPackages substitui essa lista padrão, o que é útil quando você precisa incluir dependências transitivas no bundle.

O Rslib consegue gerar um bundle UMD para uma CDN ou tag script?

Sim. Defina format como 'umd' em uma entrada lib e defina lib.umdName com o nome global que o bundle expõe, como MyLibrary. A externalização automática fica desativada por padrão na saída UMD, então as dependências são incluídas no bundle, a menos que você as liste em output.externals. Cada entrada ali mapeia um pacote para uma variável global do navegador, por exemplo, react para React. lib.autoExtension fica ativado por padrão no UMD.

Como gerar declarações .d.cts para o build CommonJS?

Ative dts.autoExtension em cada entrada lib. Ela segue as mesmas regras de lib.autoExtension: com type definido como module no package.json, as declarações ESM usam .d.ts e as declarações CJS usam .d.cts. A opção fica desativada por padrão, então as saídas ESM e CJS podem acabar com o mesmo nome de arquivo .d.ts. Aponte os tipos da condição require para o arquivo .d.cts.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.