Introdução
English version: React and typescript components lib, part 1: setup rollup and publish
Durante o ano passado criei artigos falando sobre alguns conteúdos referentes a React. A ideia agora é trazer a base da criação de uma lib React, dividida em diferentes partes, com a ideia de aplicar o que foi mostrado no ano passado e algumas partes novas: setup rollup e publicação, uso de styled-components, adição de typescript, testes unitários com Jest e testing-library, adição de eslint e prettier para padronização de código, documentação com storybook, pre-commit com Husky, autogeração de código com Hygen.
A medida que abordar temas que já escrevi artigos sobre, vou colocar como referência nos artigos referentes a lib, para os que uma nova versão trouxe maiores mudanças em relação ao que já foi escrito, vou produzir um artigo referente ao tema antes de seguir com a implementação na lib.
A parte 1 vai abordar como configurar a lib, criar os primeiros componentes de exemplo com typescript usando styled-components e publicar no npmjs para terem acesso a ela.
Rollup
É um empacotador de módulos para javascript que permite compilar pequenos pedaços de código em coisas maiores e mais complexas, como libs. Vai ser usado na criação da lib.
npmjs
Local onde vai ser publicada a lib e disponibilizada para outras apps usarem.
styled-components
Lib de estilização de componentes em aplicações React, que traz flexibilidade na definição da estilização a partir do recebimento de props. Criei um artigo ano passado explicando como funciona: Styled-components em React com Typescript
typescript
Adiciona typagem para app javascript, detectando erros em tempo de compilação.
Setup inicial
Criar uma pasta com o nome que vai ter a lib (aconselho verificar no npmjs se já tem uma lib com o mesmo nome, para evitar não conseguir adicionar lá), depois iniciar o projeto dentro dela:
yarn init
A princípio pode aceitar todos os default, depois será ajustado as informações do package.json gerado.
Adição do react e styled componentes como peer dependências, pois ao adicionarem a lib de componentes não será feito o bundle dessas libs (a app que vai usar a lib de componentes vai precisar ter elas adicionadas), e dev dependencies para uso durante o desenvolvimento da lib de componentes:
yarn add react react-dom styled-components --peer
yarn add react react-dom styled-components --dev
No momento desse artigo gerou as seguintes versões:
"react": "^19.0.0",
"react-dom": "^19.0.0",
"styled-components": "^6.1.14"
Adição typescript
Adição do typescript como dev dependencies:
yarn add typescript @types/react --dev
No momento desse artigo gerou as seguintes versões:
"@types/react": "^19.0.8",
"typescript": "^5.7.3"
Adição do arquivo de config do typescript, na raiz do projeto:
- tsconfig.json
{
"compilerOptions": {
"target": "ES2016",
"jsx": "react",
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"declarationDir": "types",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
- "target": "ES2016", especifica a versão ECMAScript alvo
- "jsx": "react", transforma código jsx em React
- "module": "ESNext", gera módulos js modernos para a lib
- "moduleResolution": "node", segue as regras do node.js para encontrar módulos
- "declaration": true, gera um arquivo .d.ts para os types da lib
- "emitDeclarationOnly": true, somente exporta as declarações de typos, sem gerar js pois o rollup realizará isso
- "outDir": "dist", diretório onde o projeto será gerado
- "declarationDir": "types", aonde será colocado o arquivo .d.ts
- "allowSyntheticDefaultImports": true, assume exports default se nenhum for criado manualmente
- "esModuleInterop": true, permite uma melhor compatibilidade entre CommonJS e ES modules
- "forceConsistentCasingInFileNames": true, verifica se o import de arquivos está consistente com o case-sensitive do nome do arquivo
- "strict": true, traz uma grande quantidade de checagem de types que garante uma ótima integridade do código
- "skipLibCheck": true, ignora a validação de types do arquivo d.ts
Criação componentes de exemplo
Serão criados dois componentes de exemplo, um simples para texto e outro para tag.
Primeiro será gerada na raiz do projeto uma pasta src, e dentro dela uma pasta components na qual serão colocados todos os componentes criados.
Começando pelo componente de texto, será criada a pasta Text dentro de components, com dois arquivos:
- Text.tsx
import React from "react";
import styled from "styled-components";
export interface TextProps {
children: React.ReactNode;
color?: string;
weight?: "normal" | "bold";
fontWeight?: number;
fontSize?: string;
fontFamily?: string;
}
export interface StyledTextProps {
$color?: string;
$weight?: "normal" | "bold";
$fontWeight?: number;
$fontSize?: string;
$fontFamily?: string;
}
export const StyledText = styled.span<StyledTextProps>`
color: ${(props) =>
props.$color
? props.$color
: "#000"};
font-size: ${(props) =>
props.$fontSize
? props.$fontSize
: "16px"};
font-weight: ${(props) =>
props.$fontWeight
? props.$fontWeight
: props.$weight
? props.$weight
: "normal"};
font-family: ${(props) =>
props.$fontFamily
? props.$fontFamily
: "Arial"};
`;
const Text = ({
children,
color,
weight,
fontWeight,
fontSize,
fontFamily,
}: TextProps) => (
<StyledText
$color={color}
$weight={weight}
$fontWeight={fontWeight}
$fontSize={fontSize}
$fontFamily={fontFamily}
>
{children}
</StyledText>
);
export default Text;
Que aceita 5 props: chidren que vai representar o texto em si, color que vai definir a cor do texto, fontWeight que vai definir a font-weight do texto, fontSize que vai definir a font-size do texto e por fim a fontFamily que vai definir a font-family do texto. Caso algum deles não sejam passados, com exceção do children, o StyledText já tem um valor default definido para cada propriedade.
- index.ts
export { default } from "./Text";
Vai disponibilizar o componente.
Agora vendo o componente de tag, será criada a pasta Tag dentro de components, com dois arquivos:
- Tag.tsx
import React from "react";
import styled from "styled-components";
import Text from "../Text/Text";
export interface TagProps {
type?: "default" | "success" | "alert" | "error";
text: string;
textColor?: string;
textWeight?: "normal" | "bold";
textFontWeight?: number;
textFontSize?: string;
textFontFamily?: string;
backgroundColor?: string;
format?: "default" | "semiRounded" | "rounded";
borderRadius?: string;
size?: "small" | "medium" | "large";
padding?: string;
}
export interface StyledTagProps {
$type?: "default" | "success" | "alert" | "error";
$textColor?: string;
$textWeight?: "normal" | "bold";
$textFontWeight?: number;
$textFontSize?: string;
$textFontFamily?: string;
$backgroundColor?: string;
$format?: "default" | "semiRounded" | "rounded";
$borderRadius?: string;
$size?: "small" | "medium" | "large";
$padding?: string;
}
export const StyledTag = styled.div<StyledTagProps>`
border: none;
padding: ${(props) =>
props.$padding
? props.$padding
: props.$size === "large"
? "15px 20px"
: props.$size === "medium"
? "10px 12px"
: "7px"};
background-color: ${(props) =>
props.$backgroundColor
? props.$backgroundColor
: props.$type === "error"
? "#e97451"
: props.$type === "alert"
? "#f8de7e"
: props.$type === "success"
? "#50c878"
: "#d3d3d3"};
pointer-events: none;
border-radius: ${(props) =>
props.$borderRadius
? props.$borderRadius
: props.$format === "rounded"
? "30px"
: props.$format === "semiRounded"
? "5px"
: "0"};
width: fit-content;
`;
const Tag = ({
text,
type,
textColor,
textWeight,
textFontWeight,
textFontSize,
textFontFamily,
backgroundColor,
format,
borderRadius,
size,
padding,
}: TagProps) => (
<StyledTag
$type={type}
$backgroundColor={backgroundColor}
$format={format}
$borderRadius={borderRadius}
$size={size}
$padding={padding}
>
<Text
color={textColor || "#fff"}
weight={textWeight}
fontWeight={textFontWeight}
fontSize={textFontSize}
fontFamily={textFontFamily}
>
{text}
</Text>
</StyledTag>
);
export default Tag;
Que aceita 11 props.
Sendo 3 pre-definidas: size que define o padding (se large define 15px 20px, se medium define 10px 12px), type que define a background-color (se error define #e97451, se alert define #f8de7e, se success define #50c878) e format que define o border-radius (se rounded define 30px, se semiRounded define 5px).
E sendo 8 customizáveis: text que define o texto da tag, textColor que define a cor do texto, textFontWeight que define a font-weight do texto, textFontSize que define a font-size do texto, textFontFamily que define a font-family do texto, backgroundColor que define a background-color da tag, borderRadius que define o border-radius da tag e por fim o padding que define o padding da tag.
Caso algum deles não sejam passados, com exceção do text, o StyledTag já tem um valor default definido para cada propriedade.
- index.ts
export { default } from "./Tag";
Vai disponibilizar o componente.
Após a definição dos dois componentes, vai ser centralizado o export deles dentro da pasta src/components:
- index.ts
export { default as Tag } from "./Tag";
export { default as Text } from "./Text";
Por fim vai ser centralizado o que a lib irá disponibilizar quando adicionarem ela, dentro da pasta src:
- index.ts
export * from "./components";
Dessa forma se tem dois componentes definidos e centralizado a disponibilização deles, podendo já prosseguir para configuração do rollup.
Setup Rollup
Para utilizar o Rollup, primeiro vai ser adicionadas as seguintes libs:
yarn add rollup rollup-plugin-dts rollup-plugin-peer-deps-external @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-typescript@11.1.6 @rollup/plugin-terser --dev
No momento desse artigo gerou as seguintes versões:
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "11.1.6",
"rollup": "^4.30.1",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-peer-deps-external": "^2.2.4"
Diferente das libs em geral que estão sendo adicionadas, foi definida uma versão específica para o @rollup/plugin-typescript, por ter uma issue que não permite utilizar ela em versões posteriores.
Depois vai ser necessário criar na raiz do projeto o arquivo de config do rollup, que vai usar as libs adicionadas:
- rollup.config.js
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import terser from "@rollup/plugin-terser";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
const packageJson = require("./package.json");
export default [
{
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
},
{
file: packageJson.module,
format: "esm",
},
],
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
terser(),
],
external: ["react", "react-dom", "styled-components"],
},
{
input: "src/index.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts.default()],
},
];
Nesse arquivo são usados as libs que foram adicionadas na app.
Em input: "src/index.ts" é definido o arquivo onde os componentes estão sendo exportados. Em output abaixo desse input, é definido a criação de dois arquivos de output, o primeiro com formato cjs e o segundo com formato esm, que vão ser especificados em package.json o caminho deles. Em peerDepsExternal() e em external: ["react", "react-dom", "styled-components"], são definidas as dependências externas que a lib possui, ou seja, que não vai acontecer o bundle delas na adição da lib. Em resolve() é usado o algoritmo do Node para encontrar módulos de terceiros em node_modules. Em commonjs() é convertido commonjs módulos em es módulos, permitindo o uso de ambos formatos. Em typescript({ tsconfig: "./tsconfig.json" }) é definida a integração entre o rollup e typescript, e é passado o local das configurações do typescript. Em terser() é reduzido o tamanho do bundle. No final a parte referente ao plugin dts.default() vai definir em output um arquivo com os types usados na lib.
package.json
Por agora o package.json deve estar parecido com o abaixo:
{
"name": "react-example-lib", // nome da pasta onde iniciou o projeto
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"@types/react": "^19.0.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rollup": "^4.30.1",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"styled-components": "^6.1.14",
"typescript": "^5.7.3"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"styled-components": "^6.1.14"
}
}
Vai ser preciso fazer algumas alterações e adições nele, ficando dessa forma:
{
"name": "react-example-lib",
"version": "0.1.0",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/griseduardo/react-example-lib.git"
},
"scripts": {
"build": "rollup -c --bundleConfigAsCjs"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"@types/react": "^19.0.8",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rollup": "^4.30.1",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"styled-components": "^6.1.14",
"typescript": "^5.7.3"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"styled-components": "^6.1.14"
}
}
- name: está definido o nome da lib
- version: a versão da lib, mudei de 1.0.0 para 0.1.0 por não estar pronta para utilização
- main: o caminho de saída para o formato cjs
- module: o caminho de saída para o formato esm
- types: o caminho para os types da lib
- files: a pasta onde vai estar contida toda a lib
- repository: onde está a app caso a coloque no github também
- scripts: definido o
build, que vai executar o rollup
Arquivo CHANGELOG
A medida que a lib vai sendo modificada, se torna interessante mostrar o que foi mudado a cada subida de versão para quem for utilizar ela. Para isso será criado um arquivo na raiz:
- CHANGELOG.md
## 0.1.0
_Jan. 29, 2025_
- initial config
Arquivo README
Outra parte importante é adicionar um README explicando do que se trata a app, como adicionar ela e como utilizar seus componentes. Será adicionado na raiz:
- README.md
Arquivo .gitignore
No caso de além de publicar a lib no npmjs, criar um repositório no github para ela, é interessante não subir algumas pastas para o github. Será adicionado na raiz do projeto:
- .gitignore
dist
node_modules
Estrutura de pastas
Por fim a estrutura inicial de pastas ficará da seguinte forma:
Publicação da lib
Primeiro passo a ser realizado é ver se a execução do rollup ocorre com sucesso. Para isso será executado o yarn build no terminal, que foi definido em package.json. Executando com sucesso, será criada automaticamente uma pasta dist na raiz do projeto.
Após isso será necessário criar uma conta em npmjs, local onde será publicado a lib.
Após a criação da conta no site, vai ser necessário fazer o login no terminal dentro do projeto:
npm login
Onde vai ser necessário colocar o username/email e senha (o mesmo referente ao que foi criado no site). Após o login é publicar a lib:
npm publish --access public
Executado com sucesso a publicação, no site do npmjs vai ser possível ver a lib publicada ali, ficando disponível para outras apps adicionarem e usarem os componentes que foram definidos dentro dela (Text e Tag). Como foi colocado em peer dependências o react, react-dom e styled-components, caso a app que adicionar a lib não tenha algum deles, vai ser informado que são dependências necessárias para execução da lib no momento da adição.
Conclusão
A ideia desse artigo foi trazer como criar e publicar uma lib de componentes em React com Typescript. Para isso, foi passado de forma geral como colocar o typescript e configurar o rollup, foram definidos dois componentes usando typescript com styled-components, foi apresentado alguns arquivos que acredito ser interessante para mostrar as alterações que forem sendo realizadas na lib e como usar ela, e por fim os passos para publicar no npmjs. A ideia agora é nos próximos artigos implementar coisas novas para ela, como testes, regras pre-commit, dentre outras que foram citadas na introdução. Estou colocando o repositório no github e publicando no npmjs para seguir o que estou escrevendo nos artigos.


Top comments (0)