Introdução
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)