Na maior parte do meu trabalho, escrevo back-ends, mas outro dia houve uma tarefa para iniciar uma biblioteca de componentes no React. Alguns anos atrás, quando a versão React era tão pequena quanto minha experiência no desenvolvimento de front-end, eu já havia adotado uma abordagem do projétil, que acabou desajeitada e desajeitadamente. Levando em conta a maturidade do atual ecossistema React e minha crescente experiência, desta vez fui inspirado a fazer tudo bem e convenientemente. Como resultado, eu tinha um espaço em branco para a futura biblioteca e, para não esquecer nada e coletar tudo em um só lugar, este artigo foi escrito, o que também deve ajudar aqueles que não sabem por onde começar. Vamos ver o que eu fiz.
TL / DR: O código para uma biblioteca pronta para iniciar pode ser visualizado no github
O problema pode ser abordado de dois lados:
- Encontre um kit de iniciação, clichê ou gerador de cli pronto para uso
- Colete tudo você mesmo
O primeiro método é bom para um início rápido, quando você absolutamente não deseja lidar com a configuração e a conexão dos pacotes necessários. Além disso, essa opção é adequada para iniciantes que não sabem por onde começar e qual deve ser a diferença entre a biblioteca e o aplicativo regular.
No começo, segui o primeiro caminho, mas depois decidi atualizar as dependências e fixar mais alguns pacotes, e então todos os tipos de erros e inconsistências caíram. Como resultado, ele arregaçou as mangas e fez tudo sozinho. Mas vou mencionar o gerador da biblioteca.
Criar biblioteca de reação
A maioria dos desenvolvedores que lidam com o React ouviram falar de um acionador de partida do aplicativo React conveniente, que permite minimizar a configuração do projeto e fornecer padrões razoáveis - Criar aplicativo de reação (CRA). Em princípio, poderia ser usado para a biblioteca ( há um artigo sobre o Habré ). No entanto, a estrutura do projeto e a abordagem para o desenvolvimento do ui-kit são ligeiramente diferentes do SPA usual. Precisamos de um diretório separado com fontes de componentes (src), uma sandbox para desenvolvimento e depuração (exemplo), uma ferramenta de documentação e demonstração ("showcase") e um diretório separado com arquivos preparados para exportação (dist). Além disso, os componentes da biblioteca não serão adicionados ao aplicativo SPA, mas serão exportados por meio de um arquivo de índice. Pensando nisso, procurei e descobri rapidamente um pacote semelhante de CRA - Creat React Library (CRL).
A CRL, como a CRA, é um utilitário CLI fácil de usar. Usando-o, você pode gerar um projeto. Ele conterá:
- Rollup configurado para criar módulos cjs e es e um mapa de origem com suporte para módulos css
- babel para transpilação no ES5
- Jest para testes
- TypeScript (TS) como uma opção que gostaríamos de usar
Para gerar o projeto da biblioteca, podemos fazê-lo (o npx nos permite não instalar pacotes globalmente):
npx create-react-library
Respondemos às perguntas propostas. E, como resultado do utilitário, obtemos um projeto gerado e pronto para o trabalho da biblioteca de componentes.
E então algo deu errado ...As dependências estão um pouco desatualizadas hoje, então decidi atualizá-las para as versões mais recentes usando o npm-check :
npx npm-check -u
Outro fato triste é que o aplicativo sandbox no diretório de example
é gerado em js. Você precisará reescrevê-lo manualmente no TypeScript, adicionando tsconfig.json
e algumas dependências (por exemplo, o próprio @types
typescript
e os @types
básicos).
Além disso, o pacote react-scripts-ts
é declarado deprecated
e não é mais suportado. Em vez disso, você deve instalar o react-scripts
, porque, já há algum tempo, o CRA (cujo pacote é o react react-scripts
) oferece suporte ao TypeScript da caixa (usando o Babel 7).
Como resultado, eu não dominei a puxar a react-scripts
do react-scripts
na minha idéia da biblioteca. Tanto quanto me lembro, o Jest deste pacote exigia a opção do compilador isolatedModules
, que contraria meu desejo de gerar e exportar d.ts
da biblioteca (tudo isso está de alguma forma relacionado às limitações do Babel 7, que é usado pelo Jest e react-scripts
para compilar o TS ) Então fiz uma eject
para react-scripts
, observei o resultado e refiz tudo com as mãos, sobre as quais vou escrever mais tarde.
tsdx
Obrigado ao usuário StanEgo , que falou sobre uma excelente alternativa ao Create React Library - tsdx . Este utilitário cli também é semelhante ao CRA e em um comando criará a base para sua biblioteca com Rollup, TS, Prettier, husky, Jest e React configurados. E o React vem como uma opção. Simples o suficiente para fazer:
npx tsdx create mytslib
E, como resultado, as novas versões necessárias dos pacotes serão instaladas e todas as configurações serão feitas. Obtenha um projeto semelhante à CRL. A principal diferença da CRL é a configuração zero. Ou seja, a configuração do Rollup está oculta para nós no tsdx (assim como o CRA).
Após analisar rapidamente a documentação, não encontrei os métodos recomendados para uma configuração mais refinada ou algo como ejetar como no CRA. Tendo analisado a questão do projeto, descobri que até agora não existe essa possibilidade . Para alguns projetos, isso pode ser crítico; nesse caso, você terá que trabalhar um pouco com as mãos. Se você não precisar, o tsdx é uma ótima maneira de começar rapidamente.
Assuma o controle em nossas próprias mãos
Mas e se você seguir o segundo caminho e coletar tudo sozinho? Então, vamos começar do começo. Execute o npm init
e gere package.json
para a biblioteca. Adicione algumas informações sobre o nosso pacote lá. Por exemplo, escrevemos as versões mínimas para nó e npm no campo de engines
. Os arquivos coletados e exportados serão colocados no diretório dist
. Nós indicamos isso no campo files
. Estamos criando uma biblioteca de componentes de reação, portanto esperamos que os usuários tenham os pacotes necessários - peerDependencies
versões mínimas necessárias de react
e react-dom
no campo peerDependencies
.
Agora instale os pacotes react e react react-dom
e os tipos necessários (já que veremos componentes no TypeScript) como devDependencies (como todos os pacotes deste artigo):
npm install --save-dev react react-dom @types/react @types/react-dom
Instale o TypeScript:
npm install --save-dev typescript
Vamos criar arquivos de configuração para o código principal e testes: tsconfig.json
e tsconfig.test.json
. Nossa target
será es5
, es5
, etc. Uma lista completa de opções possíveis e seus significados pode ser encontrada na documentação . Não esqueça de include
diretório de origem no include
e adicione os diretórios node_modules
e dist
na exclude
. No package.json
indicamos no campo typings
onde obter os tipos para nossa biblioteca - dist/index
.
Crie o diretório src
para os componentes de origem da biblioteca. Adicione todo tipo de pequenas coisas, como .gitignore
, .editorconfig
, um arquivo com uma licença e README.md
.
Rollup
Usaremos o Rollup para montagem, conforme sugerido pela CRL. Pacotes e configurações necessários, também espionei a CRL. Em geral, ouvi a opinião de que o Rollup é bom para bibliotecas e o Webpack para aplicativos. No entanto, não configurei o Webpack (o que o CRA faz por mim), mas o Rollup é realmente bom, simples e bonito.
Instalar:
npm install --save-dev rollup rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-postcss rollup-plugin-typescript2 rollup-plugin-url @svgr/rollup
No package.json
adicione os campos com a distribuição dos pacotes configuráveis da biblioteca coletada, como o rollup
nos recomenda - pkg.module :
"main": "dist/index.js", "module": "dist/index.es.js", "jsnext:main": "dist/index.es.js"
Crie o arquivo de configuração rollup.config.js import typescript from 'rollup-plugin-typescript2'; import commonjs from 'rollup-plugin-commonjs'; import external from 'rollup-plugin-peer-deps-external'; import postcss from 'rollup-plugin-postcss'; import resolve from 'rollup-plugin-node-resolve'; import url from 'rollup-plugin-url'; import svgr from '@svgr/rollup'; import pkg from './package.json'; export default { input: 'src/index.tsx', output: [ { file: pkg.main, format: 'cjs', exports: 'named', sourcemap: true }, { file: pkg.module, format: 'es', exports: 'named', sourcemap: true } ], plugins: [ external(), postcss({ modules: false, extract: true, minimize: true, sourceMap: true }), url(), svgr(), resolve(), typescript({ rollupCommonJSResolveHack: true, clean: true }), commonjs() ] };
A configuração é um arquivo js, ou melhor, um objeto exportado. No campo de input
, especifique o arquivo no qual as exportações para nossa biblioteca são registradas. output
- descreve nossas expectativas em relação à saída - em qual módulo compilar no formato e onde colocá-lo.
Em seguida, vem o campo com uma lista e configuração de plugins- rollup-plugin-peer-deps-external - permite excluir
peerDependencies
do bundle
peerDependencies
para reduzir seu tamanho. Isso é razoável, porque peerDependencies
se peerDependencies
do usuário da biblioteca. - rollup-plugin-postcss - integra PostCss e Rollup. Aqui, desabilitamos css-modules, incluímos css no pacote de exportação da nossa biblioteca, minimizamos e habilitamos a criação do sourceMap. Se você não exportar nenhum CSS diferente do usado pelos componentes da biblioteca, a
extract
poderá ser evitada - o CSS necessário nos componentes será adicionado à tag head na página conforme necessário no final. No entanto, no meu caso, é necessário distribuir alguns css adicionais (grade, cores, etc.), e o cliente precisará conectar explicitamente a biblioteca css-bundle a si mesma. - rollup-plugin-url - permite exportar vários recursos, como imagens
- svgr - transforma svg em componentes React
- rollup-plugin-node-resolve - define o local dos módulos de terceiros em node_modules
- rollup-plugin-typescript2 - conecta o compilador TypeScript e fornece a capacidade de configurá-lo
- rollup-plugin-commonjs - converte módulos de dependência commonjs em módulos es para que eles possam ser incluídos no pacote
Adicione um comando no campo scripts
package.json
para build ( "build": "rollup -c"
) e inicie o assembly no modo de observação durante o desenvolvimento ( "start": "rollup -c -w && npm run prettier-watch"
) .
O primeiro componente e arquivo de exportação
Agora, escreveremos o componente de reação mais simples para verificar como nossa montagem funciona. Cada componente da biblioteca será colocado em um diretório separado no diretório pai - src/components/ExampleComponent
. Este diretório conterá todos os arquivos associados ao componente - tsx
, css
, test.tsx
e assim por diante.
Vamos criar alguns arquivos de estilos para o componente e o arquivo tsx
do próprio componente.
ExampleComponent.tsx import * as React from 'react'; import './ExampleComponent.css'; export interface Props { text: string; } export class ExampleComponent extends React.Component<Props> { render() { const { text } = this.props; return ( <div className="test"> Example Component: {text} <p>Coool!</p> </div> ); } } export default ExampleComponent;
Também no src
você precisa criar um arquivo com tipos comuns às bibliotecas, onde um tipo será declarado para css e svg (visualizado na CRL).
typings.d.ts declare module '*.css' { const content: { [className: string]: string }; export default content; } interface SvgrComponent extends React.FunctionComponent<React.SVGAttributes<SVGElement>> {} declare module '*.svg' { const svgUrl: string; const svgComponent: SvgrComponent; export default svgUrl; export { svgComponent as ReactComponent }; }
Todos os componentes exportados e css devem ser especificados no arquivo de exportação. Nós temos - src/index.tsx
. Se algum css não for usado no projeto e não estiver listado como parte daqueles importados para src/index.tsx
, ele será descartado do assembly, o que é bom.
src / index.tsx import { ExampleComponent, Props } from './ExampleComponent'; import './export.css'; export { ExampleComponent, Props };
Agora você pode tentar construir a biblioteca - npm run build
. Como resultado, o rollup
inicia e coleta nossa biblioteca em pacotes, que encontraremos no diretório dist
.
Em seguida, adicionamos algumas ferramentas para melhorar a qualidade do nosso processo de desenvolvimento e seu resultado.
Eu odeio, em uma revisão de código, apontar a formatação descuidada ou não-padrão para um projeto, e ainda mais argumentar sobre isso. Tais falhas devem ser naturalmente corrigidas, mas os desenvolvedores devem se concentrar no que e como o código funciona, e não na aparência. Essas correções são o primeiro candidato à automação. Há um pacote maravilhoso para esta tarefa - mais bonito . Instale-o:
npm install --save-dev prettier
Adicione uma configuração para um pequeno refinamento das regras de formatação.
.prettierrc.json { "tabWidth": 3, "singleQuote": true, "jsxBracketSameLine": true, "arrowParens": "always", "printWidth": 100, "semi": true, "bracketSpacing": true }
Você pode ver o significado das regras disponíveis na documentação . O WebStrom depois de criar o próprio arquivo de configuração sugerirá o uso de prettier
ao iniciar a formatação através do IDE. Para evitar que a formatação perca tempo, adicione o /node_modules
e /dist
às exceções usando o arquivo .prettierignore
(o formato é semelhante ao .gitignore
). Agora você pode executar prettier
aplicando regras de formatação ao código-fonte:
prettier --write "**/*"
Para não executar o comando explicitamente todas as vezes com as mãos e para ter certeza de que o código dos outros desenvolvedores do projeto também será formatado prettier
, adicione a execução prettier
no precommit-hook
para os arquivos marcados como precommit-hook
(via git add
). Para tal, precisamos de duas ferramentas. Em primeiro lugar, é incómoda , responsável por executar qualquer comando antes de confirmar, pressionar, etc. Em segundo lugar, é encenado por fiapos , que pode executar diferentes linters em arquivos staged
. Precisamos executar apenas uma linha para entregar esses pacotes e adicionar comandos de lançamento ao package.json
:
npx mrm lint-staged
Não podemos esperar a formatação antes de confirmar, mas certifique-se de que a prettier
funcione constantemente em arquivos modificados no processo de nosso trabalho. Sim, precisamos de outro pacote - onchange . Ele permite monitorar alterações nos arquivos do projeto e executar imediatamente o comando necessário. Instalar:
npm install --save-dev --save-exact onchange
Em seguida, adicionamos aos comandos do campo scripts
no package.json
:
"prettier-watch": "onchange 'src/**/*' -- prettier --write {{changed}}"
Com isso, todas as disputas sobre formatação no projeto podem ser consideradas encerradas.
Evitando erros com o ESLint
O ESLint se tornou um padrão e pode ser encontrado em quase todos os projetos js e ts. Ele também nos ajudará. Na configuração do ESLint, confio no CRA; basta pegar os pacotes necessários do CRA e conectá-lo à nossa biblioteca. Além disso, adicione configurações para TS e prettier
(para evitar conflitos entre ESLint
e prettier
):
npm install --save-dev eslint eslint-config-react-app eslint-loader eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser babel-eslint eslint-config-prettier eslint-plugin-prettier
ESLint
usando o arquivo de configuração.
.eslintrc.json { "extends": [ "plugin:@typescript-eslint/recommended", "react-app", "prettier", "prettier/@typescript-eslint" ], "plugins": [ "@typescript-eslint", "react" ], "rules": { "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-member-accessibility": "off" } }
Inclua o comando lint
- eslint src/**/* --ext .ts,.tsx --fix
no campo de scripts
do package.json
. Agora você pode executar o eslint através do npm run lint
.
Testando com Jest
Para escrever testes de unidade para componentes da biblioteca, instale e configure o Jest , uma biblioteca de testes do facebook. No entanto, desde compilamos o TS não através do babel 7, mas através do tsc, então precisamos instalar o pacote ts-jest também:
npm install --save-dev jest ts-jest @types/jest
Para que o gracejo aceite corretamente as importações de css ou outros arquivos, você precisa substituí-los por mokami. Crie o diretório __mocks__
e crie dois arquivos lá.
styleMock.ts
:
module.exports = {};
fileMock.ts
:
module.exports = 'test-file-stub';
Agora crie a configuração de brincadeira.
jest.config.js module.exports = { preset: 'ts-jest', testEnvironment: 'node', moduleNameMapper: { '\\.(css|less|sass|scss)$': '<rootDir>/__mocks__/styleMock.ts', '\\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.ts' } };
Escreveremos o teste mais simples para nosso ExampleComponent
em seu diretório
ExampleComponent.test.tsx import { ExampleComponent } from './ExampleComponent'; describe('ExampleComponent', () => { it('is truthy', () => { expect(ExampleComponent).toBeTruthy(); }); });
Inclua o comando test
- npm run lint && jest
no campo de scripts
do package.json
. Para maior confiabilidade, também acionaremos o linter. Agora você pode executar nossos testes e garantir que eles passem - npm run test
. E para que os testes não caiam em dist
durante a montagem, adicione o campo de exclude
no plug-in de configuração de Rollup
ao campo de exclude
- ['src/**/*.test.(tsx|ts)']
. Especifique a execução de testes no husky pre-commit hook
antes de executar o lint-staged
- "pre-commit": "npm run test && lint-staged"
.
Projetando, documentando e admirando componentes com um Storybook
Cada biblioteca precisa de boa documentação para seu uso bem-sucedido e produtivo. Quanto à biblioteca de componentes de interface, não só quero ler sobre eles, mas também ver como eles se parecem, e é melhor tocá-los e alterá-los. Para suportar essa lista de desejos, existem várias soluções. Eu costumava usar um styleguidist . Este pacote permite que você escreva documentação no formato de descontos, além de inserir exemplos dos componentes descritos no React. Além disso, a documentação é coletada e, a partir dele, é obtido um site-showcase-catalog, onde você pode encontrar o componente, ler a documentação sobre ele, descobrir seus parâmetros e também colocar uma varinha nele.
No entanto, desta vez eu decidi dar uma olhada no seu concorrente - Storybook . Hoje parece mais poderoso com seu sistema de plugins. Além disso, está em constante evolução, possui uma grande comunidade e em breve também começará a gerar suas páginas de documentação usando arquivos de remarcação. Outra vantagem do Storybook é que é uma sandbox - um ambiente para o desenvolvimento de componentes isolados. Isso significa que não precisamos de aplicativos de amostra completos para o desenvolvimento de componentes (como sugere a CRL). No livro de histórias, escrevemos stories
- arquivos ts nos quais transferimos nossos componentes com alguns props
entrada para funções especiais (é melhor olhar o código para torná-lo mais claro). Como resultado, um aplicativo de apresentação é montado a partir dessas stories
.
Execute o script que inicializa o livro de histórias:
npx -p @storybook/cli sb init
Agora faça amizade com o TS. Para fazer isso, precisamos de mais alguns pacotes e, ao mesmo tempo, colocaremos alguns complementos úteis:
npm install --save-dev awesome-typescript-loader @types/storybook__react @storybook/addon-info react-docgen-typescript-loader @storybook/addon-actions @storybook/addon-knobs @types/storybook__addon-info @types/storybook__addon-knobs webpack-blocks
O script criou um diretório com a configuração do storybook
- .storybook
e um diretório de exemplo que excluímos implacavelmente. E no diretório de configuração, alteramos os addons
extensão e config
para ts
. Adicionamos addons ao arquivo addons.ts
:
import '@storybook/addon-actions/register'; import '@storybook/addon-links/register'; import '@storybook/addon-knobs/register';
Agora, você precisa ajudar o livro de histórias usando a configuração do webpack no diretório .storybook
.
webpack.config.js module.exports = ({ config }) => { config.module.rules.push({ test: /\.(ts|tsx)$/, use: [ { loader: require.resolve('awesome-typescript-loader') },
Vamos ajustar um pouco a configuração config.ts
, adicionando decoradores para conectar complementos a todas as nossas histórias.
config.ts import { configure } from '@storybook/react'; import { addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; import { withKnobs } from '@storybook/addon-knobs';
Escreveremos nossa primeira story
no diretório de componentes ExampleComponent
ExampleComponent.stories.tsx import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { ExampleComponent } from './ExampleComponent'; import { text } from '@storybook/addon-knobs/react'; const stories = storiesOf('ExampleComponent', module); stories.add('ExampleComponent', () => <ExampleComponent text={text('text', 'Some text')} />, { info: { inline: true }, text: ` ### Notes Simple example component ### Usage ~~~js <ExampleComponent text="Some text" /> ~~~ ` });
Usamos addons:
- knobs - permite alterar em tempo real os objetos no componente exibido no Storybook. Para fazer isso, envolva adereços em funções especiais nas histórias
- info - permite adicionar documentação e descrição de acessórios à página da história
Agora observe que o script de inicialização do storybook adicionou o comando storybook ao nosso package.json
. Use-o para executar o npm run storybook
. O Storybook será montado e executado em http://localhost:6006
. Não esqueça de adicionar à exceção do módulo de 'src/**/*.stories.tsx'
typescript
na configuração do Rollup
- 'src/**/*.stories.tsx'
.
Estamos desenvolvendo
Assim, tendo se cercado de muitas ferramentas convenientes e preparado-as para o trabalho, você pode começar a desenvolver novos componentes. Cada componente será colocado em seu diretório em src/components
com o nome do componente. Ele conterá todos os arquivos associados a ele - css, o próprio componente no arquivo tsx, testes, histórias. Iniciamos o livro de histórias, criamos histórias para o componente e lá escrevemos documentação para ele. Criamos testes e teste. A importação-exportação do componente final é gravada em index.ts
.
Além disso, você pode efetuar login no npm
e publicar sua biblioteca como um novo pacote npm. E você pode conectá-lo diretamente do repositório git a partir do master e de outros ramos. Por exemplo, para minha peça de trabalho, você pode fazer:
npm i -s git+https://github.com/jmorozov/react-library-example.git
Para que no aplicativo consumidor da biblioteca no diretório node_modules
exista apenas o conteúdo do diretório dist
no estado montado, é necessário adicionar o comando "prepare": "npm run build"
ao campo de scripts
.
Além disso, graças ao TS, o preenchimento automático no IDE funcionará.
Resumir
Em meados de 2019, você poderá rapidamente desenvolver sua biblioteca de componentes no React e TypeScript, usando ferramentas de desenvolvimento convenientes. Este resultado pode ser alcançado com a ajuda de um utilitário automatizado e no modo manual. A segunda maneira é preferida se você precisar de pacotes atuais e mais controle. O principal é saber onde cavar e, com a ajuda de um exemplo neste artigo, espero que isso se torne um pouco mais fácil.
Você também pode levar a peça resultante aqui .
Entre outras coisas, não pretendo ser a verdade última e, em geral, estou envolvida no front-end na medida em que. Você pode escolher pacotes alternativos e opções de configuração e também conseguir criar sua biblioteca de componentes. Eu ficaria feliz se você compartilhar suas receitas nos comentários. Feliz codificação!