Inicio de la biblioteca de componentes React y TypeScript


La mayor parte de mi trabajo, escribo backends, pero el otro día había una tarea para iniciar una biblioteca de componentes en React. Hace unos años, cuando la versión React era tan pequeña como mi experiencia en el desarrollo front-end, ya adopté un enfoque del proyectil y resultó torpe y torpemente. Teniendo en cuenta la madurez del ecosistema actual de React y mi creciente experiencia, esta vez me inspiré para hacer todo bien y convenientemente. Como resultado, tenía un espacio en blanco para la futura biblioteca, y para no olvidar nada y recopilar todo en un solo lugar, se escribió este artículo de hoja de trucos, que también debería ayudar a aquellos que no saben por dónde empezar. Veamos que hice.


TL / DR: el código para una biblioteca lista para comenzar se puede ver en github


El problema puede abordarse desde dos lados:


  1. Encuentre un kit de inicio listo para usar, placa de caldera o generador de cli
  2. Recoge todo tú mismo

El primer método es bueno para un inicio rápido, cuando absolutamente no desea ocuparse de configurar y conectar los paquetes necesarios. Además, esta opción es adecuada para principiantes que no saben por dónde empezar y cuál debería ser la diferencia entre la biblioteca y la aplicación habitual.


Al principio fui por el primer camino, pero luego decidí actualizar las dependencias y fijar un par de paquetes más, y luego llovieron todo tipo de errores e inconsistencias. Como resultado, se arremangó e hizo todo por sí mismo. Pero mencionaré el generador de la biblioteca.


Crear biblioteca de reacción


La mayoría de los desarrolladores que tratan con React han oído hablar de un práctico iniciador de aplicaciones React que le permite minimizar la configuración del proyecto y proporciona valores predeterminados razonables: Crear aplicación React (CRA). En principio, podría usarse para la biblioteca ( hay un artículo sobre el Habré ). Sin embargo, la estructura del proyecto y el enfoque para el desarrollo del kit de interfaz de usuario es ligeramente diferente del SPA habitual. Necesitamos un directorio separado con códigos fuente de componentes (src), un sandbox para su desarrollo y depuración (ejemplo), una herramienta de documentación y demostración ("showcase") y un directorio separado con archivos preparados para la exportación (dist). Además, los componentes de la biblioteca no se agregarán a la aplicación SPA, sino que se exportarán a través de un archivo de índice. Pensando en ello, fui a buscar y descubrí rápidamente un paquete CRA similar: Creat React Library (CRL).


CRL, como CRA, es una utilidad CLI fácil de usar. Utilizándolo, puedes generar un proyecto. Contendrá:


  • Rollup configurado para construir módulos cjs y es y un mapa fuente con soporte para módulos css
  • babel para transpilación en ES5
  • Broma para las pruebas
  • TypeScript (TS) como una opción que nos gustaría usar

Para generar el proyecto de la biblioteca, podemos hacerlo ( npx nos permite no instalar paquetes globalmente):


npx create-react-library 

Respondemos las preguntas propuestas.

Preguntas CLR


Y como resultado de la utilidad, obtenemos un proyecto generado y listo para trabajar de la biblioteca de componentes.


Con cierta estructura

Árbol clr


Y entonces algo salió mal ...

Las dependencias están un poco desactualizadas hoy, así que decidí actualizarlas a las últimas versiones usando npm-check :


 npx npm-check -u 

Otro hecho triste es que la aplicación de sandbox en el directorio de example se genera en js. Tendrá que reescribirlo manualmente en TypeScript, agregando tsconfig.json y algunas dependencias (por ejemplo, el typescript y los @types básicos).


Además, el paquete react-scripts-ts se declara deprecated y ya no es compatible. En su lugar, debe instalar react-scripts , porque desde hace algún tiempo CRA (cuyo paquete es react-scripts ) admite TypeScript desde el cuadro (usando Babel 7).


Como resultado, no dominé la react-scripts en mi idea de la biblioteca. Hasta donde recuerdo, el Jest de este paquete requería la opción del compilador isolatedModules , lo que iba en contra de mi deseo de generar y exportar d.ts desde la biblioteca (todo esto está relacionado de alguna manera con las limitaciones de Babel 7, que Jest usa y react-scripts para compilar TS ) Así que hice una eject de react-scripts de react-scripts , miré el resultado y rehice todo con mis manos, sobre lo que escribiré más adelante.


tsdx


Gracias al usuario StanEgo , quien habló sobre una excelente alternativa para Crear Biblioteca de React - tsdx . Esta utilidad cli también es similar a CRA y en un comando creará la base para su biblioteca con Rollup, TS, Prettier, husky, Jest y React configurados. Y React viene como una opción. Suficientemente simple para hacer:


 npx tsdx create mytslib 

Y como resultado, se instalarán las nuevas versiones necesarias de los paquetes y se realizarán todas las configuraciones. Obtenga un proyecto similar a CRL. La principal diferencia de CRL es la configuración cero. Es decir, la configuración de Rollup está oculta para nosotros en tsdx (al igual que CRA).


Después de haber revisado rápidamente la documentación, no encontré los métodos recomendados para una configuración más fina o algo así como expulsar como en CRA. Después de analizar el tema del proyecto, descubrí que hasta ahora no existe tal posibilidad . Para algunos proyectos, esto puede ser crítico, en cuyo caso tendrá que trabajar un poco con las manos. Si no lo necesita, tsdx es una excelente manera de comenzar rápidamente.


Tomar el control en nuestras propias manos


Pero, ¿qué pasa si vas por el segundo camino y recoges todo tú mismo? Entonces, comencemos desde el principio. Ejecute npm init y genere package.json para la biblioteca. Agregue información sobre nuestro paquete allí. Por ejemplo, escribimos las versiones mínimas para node y npm en el campo de engines . Los archivos recopilados y exportados se colocarán en el directorio dist . Indicamos esto en el campo de files . Estamos creando una biblioteca de componentes de reacción, por lo que esperamos que los usuarios tengan los paquetes necesarios: peerDependencies versiones mínimas requeridas de react y react-dom en el campo peerDependencies .


Ahora instale los paquetes react y react-dom y los tipos necesarios (ya que estaremos cortando componentes en TypeScript) como devDependencies (como todos los paquetes en este artículo):


 npm install --save-dev react react-dom @types/react @types/react-dom 

Instalar TypeScript:


 npm install --save-dev typescript 

tsconfig.json archivos de configuración para el código principal y las pruebas: tsconfig.json y tsconfig.test.json . Nuestro target estará en es5 , generaremos sourceMap , etc. Puede encontrar una lista completa de posibles opciones y sus significados en la documentación . No olvide include directorio fuente en el include , y agregue los node_modules y dist en el exclude . En package.json indicamos en el campo de typings dónde obtener los tipos para nuestra biblioteca: dist/index .


Cree el directorio src para los componentes de origen de la biblioteca. Agregue todo tipo de cosas pequeñas, como .gitignore , .editorconfig , un archivo con licencia y README.md .


Rollup


Utilizaremos Rollup para el ensamblaje, como lo sugiere CRL. Paquetes necesarios y configuración, también espiaba CRL. En general, escuché la opinión de que Rollup es bueno para las bibliotecas y Webpack para las aplicaciones. Sin embargo, no configuré Webpack (lo que CRA hace por mí), pero Rollup es realmente bueno, simple y hermoso.


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 

En package.json agregue los campos con la distribución de los paquetes de biblioteca recopilados, como nos recomienda el rollup : pkg.module :


  "main": "dist/index.js", "module": "dist/index.es.js", "jsnext:main": "dist/index.es.js" 

Cree el archivo de configuración 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() ] }; 

La configuración es un archivo js, ​​o más bien un objeto exportado. En el campo de input , especifique el archivo en el que se registran las exportaciones para nuestra biblioteca. output - describe nuestras expectativas sobre la salida - en qué módulo compilar en el formato y dónde colocarlo.


Luego viene el campo con una lista y configuración de complementos
  • rollup-plugin-peer-deps-external : le permite excluir peerDependencies del bundle para reducir su tamaño. Esto es razonable, porque se esperan peerDependencies del usuario de la biblioteca.
  • rollup-plugin-postcss : integra PostCss y Rollup. Aquí deshabilitamos los módulos css, incluimos css en el paquete de exportación de nuestra biblioteca, lo minimizamos y habilitamos la creación de sourceMap. Si no exporta ningún CSS que no sea el utilizado por los componentes de la biblioteca, entonces se puede evitar el extract : al final, se agregará el CSS necesario en los componentes a la etiqueta de encabezado de la página según sea necesario. Sin embargo, en mi caso, es necesario distribuir algunos css adicionales (cuadrícula, colores, etc.), y el cliente tendrá que conectar explícitamente la biblioteca css-bundle a sí mismo.
  • rollup-plugin-url : le permite exportar varios recursos, como imágenes
  • svgr : transforma svg en componentes React
  • rollup-plugin-node-resolve - define la ubicación de módulos de terceros en node_modules
  • rollup-plugin-typescript2 : conecta el compilador TypeScript y proporciona la capacidad de configurarlo
  • rollup-plugin-commonjs : convierte los módulos de dependencia de commonjs en módulos es para que puedan incluirse en el paquete

Agregue un comando en el campo scripts package.json para compilar ( "build": "rollup -c" ) e inicie el ensamblaje en modo de observación durante el desarrollo ( "start": "rollup -c -w && npm run prettier-watch" ) .


El primer componente y archivo de exportación


Ahora escribiremos el componente de reacción más simple para verificar cómo funciona nuestro ensamblaje. Cada componente de la biblioteca se colocará en un directorio separado en el directorio principal: src/components/ExampleComponent . Este directorio contendrá todos los archivos asociados con el componente: tsx , css , test.tsx , etc.
tsx archivo de estilos para el componente y el archivo tsx del componente mismo.


ExampleComponent.tsx
 /** * @class ExampleComponent */ import * as React from 'react'; import './ExampleComponent.css'; export interface Props { /** * Simple text prop **/ text: string; } /** My First component */ 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; 

También en src necesita crear un archivo con tipos que sean comunes a las bibliotecas, donde se declarará un tipo para css y svg (se espió en CRL).


typings.d.ts
 /** * Default CSS definition for typescript, * will be overridden with file-specific definitions by rollup */ 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 los componentes exportados y CSS deben especificarse en el archivo de exportación. Lo tenemos: src/index.tsx . Si algunos css no se utilizan en el proyecto y no figuran como parte de los importados en src/index.tsx , se eliminará del ensamblaje, lo cual está bien.


src / index.tsx
 import { ExampleComponent, Props } from './ExampleComponent'; import './export.css'; export { ExampleComponent, Props }; 

Ahora puede intentar compilar la biblioteca: npm run build . Como resultado, el rollup comienza y recopila nuestra biblioteca en paquetes, que encontraremos en el directorio dist .


A continuación, agregamos algunas herramientas para mejorar la calidad de nuestro proceso de desarrollo y su resultado.


Olvidar el formato de código con Prettier


Odio en una revisión de código señalar el formato que es descuidado o no estándar para un proyecto, y aún más discutir sobre ello. Tales fallas deberían, naturalmente, corregirse, pero los desarrolladores deberían centrarse en qué y cómo funciona el código, en lugar de cómo se ve. Estas soluciones son el primer candidato para la automatización. Hay un paquete maravilloso para esta tarea: más bonito . Instalarlo:


 npm install --save-dev prettier 

Agregue una configuración para un pequeño refinamiento de las reglas de formato.


.prettierrc.json
 { "tabWidth": 3, "singleQuote": true, "jsxBracketSameLine": true, "arrowParens": "always", "printWidth": 100, "semi": true, "bracketSpacing": true } 

Puede ver el significado de las reglas disponibles en la documentación . WebStrom después de crear el archivo de configuración en sí sugerirá usar prettier al comenzar a formatear a través del IDE. Para evitar que el formateo pierda tiempo, agregue el /node_modules y /dist a las excepciones utilizando el archivo .prettierignore (el formato es similar a .gitignore ). Ahora puede ejecutar prettier aplicando reglas de formato al código fuente:


 prettier --write "**/*" 

Para no ejecutar el comando explícitamente cada vez con sus manos y para asegurarse de que el código de otros desarrolladores del proyecto también tendrá un formato prettier , agregue la ejecución prettier en el precommit-hook de precommit-hook para los archivos marcados como por staged (a través de git add ). Para tal cosa, necesitamos dos herramientas. En primer lugar, es incómodo , responsable de ejecutar cualquier comando antes de cometer, empujar, etc. Y, en segundo lugar, está organizado en pelusas , que puede ejecutar diferentes linters en archivos en staged . Necesitamos ejecutar solo una línea para entregar estos paquetes y agregar comandos de lanzamiento a package.json :


 npx mrm lint-staged 

No podemos esperar a formatear antes de comprometernos, pero asegúrese de que prettier trabaje constantemente en archivos modificados en el proceso de nuestro trabajo. Sí, necesitamos otro paquete: onchange . Le permite monitorear los cambios en los archivos del proyecto e inmediatamente ejecutar el comando necesario para ellos. Instalar:


 npm install --save-dev --save-exact onchange 

Luego agregamos a los scripts campo de scripts en package.json :


 "prettier-watch": "onchange 'src/**/*' -- prettier --write {{changed}}" 

Sobre esto, todas las disputas sobre el formato en el proyecto pueden considerarse cerradas.


Evitar errores con ESLint


ESLint se ha convertido en un estándar durante mucho tiempo y se puede encontrar en casi todos los proyectos js y ts. Él nos ayudará también. En la configuración de ESLint, confío en CRA, así que solo tome los paquetes necesarios de CRA y conéctelos a nuestra biblioteca. Además, agregue configuraciones para TS y prettier (para evitar conflictos entre ESLint y 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 el archivo de configuración.


.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" } } 

Agregue el comando lint - eslint src/**/* --ext .ts,.tsx --fix al campo de scripts desde package.json . Ahora puede ejecutar eslint a través de npm run lint .


Prueba con Jest


Para escribir pruebas unitarias para componentes de la biblioteca, instale y configure Jest , una biblioteca de prueba de Facebook. Sin embargo, desde compilamos TS no a través de babel 7, sino a través de tsc, entonces también necesitamos instalar el paquete ts-jest :


 npm install --save-dev jest ts-jest @types/jest 

Para que Jest acepte correctamente las importaciones de CSS u otros archivos, debe reemplazarlos con mokami. Cree el directorio __mocks__ y cree dos archivos allí.
styleMock.ts :


 module.exports = {}; 

fileMock.ts :


 module.exports = 'test-file-stub'; 

Ahora crea la configuración jest.


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' } }; 

Escribiremos la prueba más simple para nuestro ExampleComponent en su directorio.


ExampleComponent.test.tsx
 import { ExampleComponent } from './ExampleComponent'; describe('ExampleComponent', () => { it('is truthy', () => { expect(ExampleComponent).toBeTruthy(); }); }); 

Agregue el npm run lint && jest test - npm run lint && jest al campo de scripts de package.json . Para mayor confiabilidad, también manejaremos el linter. Ahora puede ejecutar nuestras pruebas y asegurarse de que pasen - npm run test . Y para que las pruebas no caigan en dist durante el ensamblaje, agregue el campo de exclude en el complemento de configuración de Rollup al campo de exclude - ['src/**/*.test.(tsx|ts)'] . Especifique la ejecución de pruebas en el husky pre-commit hook antes de ejecutar lint-staged - "pre-commit": "npm run test && lint-staged" .


Diseñando, documentando y admirando componentes con un Storybook


Cada biblioteca necesita buena documentación para su uso exitoso y productivo. En cuanto a la biblioteca de componentes de interfaz, no solo quiero leer sobre ellos, sino también para ver cómo se ven, y es mejor tocarlos y cambiarlos. Para admitir dicha lista de deseos, hay varias soluciones. Solía ​​usar un Styleguidist . Este paquete le permite escribir documentación en formato de descuento, así como insertar ejemplos de los componentes React descritos en él. Además, se recopila la documentación y se obtiene un catálogo de escaparate del sitio, donde puede encontrar el componente, leer la documentación al respecto, conocer sus parámetros y también meter una varita mágica en él.


Sin embargo, esta vez decidí echar un vistazo más de cerca a su competidor: Storybook . Hoy parece más poderoso con su sistema de complementos. Además, está en constante evolución, tiene una gran comunidad y pronto también comenzará a generar sus páginas de documentación utilizando archivos de descuento. Otra ventaja del Storybook es que es un sandbox, un entorno para el desarrollo de componentes aislados. Esto significa que no necesitamos ninguna aplicación de muestra completa para el desarrollo de componentes (como sugiere CRL). En el libro de cuentos escribimos stories : archivos ts en los que transferimos nuestros componentes con algunos props entrada a funciones especiales (es mejor mirar el código para aclararlo). Como resultado, se ensambla una aplicación de escaparate a partir de estas stories .


Ejecute el script que inicializa el libro de cuentos:


 npx -p @storybook/cli sb init 

Ahora hazte amigo de TS. Para hacer esto, necesitamos algunos paquetes más, y al mismo tiempo pondremos un par de complementos útiles:


 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 

El script creó un directorio con la configuración del storybook : .storybook y un directorio de ejemplo que eliminamos sin piedad. Y en el directorio de configuración cambiamos los addons extensión y la config a ts . addons.ts complementos al archivo addons.ts :


 import '@storybook/addon-actions/register'; import '@storybook/addon-links/register'; import '@storybook/addon-knobs/register'; 

Ahora, debe ayudar al libro de cuentos usando la configuración del .storybook web en el directorio .storybook .


webpack.config.js
 module.exports = ({ config }) => { config.module.rules.push({ test: /\.(ts|tsx)$/, use: [ { loader: require.resolve('awesome-typescript-loader') }, // Optional { loader: require.resolve('react-docgen-typescript-loader') } ] }); config.resolve.extensions.push('.ts', '.tsx'); return config; }; 

config.ts un poco la configuración de config.ts , agregando decoradores para conectar complementos a todas nuestras historias.


config.ts
 import { configure } from '@storybook/react'; import { addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; import { withKnobs } from '@storybook/addon-knobs'; // automatically import all files ending in *.stories.tsx const req = require.context('../src', true, /\.stories\.tsx$/); function loadStories() { req.keys().forEach(req); } configure(loadStories, module); addDecorator(withInfo); addDecorator(withKnobs); 

Escribiremos nuestra primera story en el directorio 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" /> ~~~ ` }); 

Utilizamos complementos:


  • mandos : permite el cambio de accesorios en tiempo real en el componente que se muestra en Storybook. Para hacer esto, envuelva accesorios en funciones especiales en historias
  • info : le permite agregar documentación y descripción de accesorios a la página de la historia

Ahora tenga en cuenta que el script de inicialización del libro de cuentos agregó el comando del libro de cuentos a nuestro package.json . Úselo para ejecutar el npm run storybook . El Storybook se ensamblará y ejecutará en http://localhost:6006 . No olvide agregar a la excepción para el módulo de typescript en la configuración de Rollup - 'src/**/*.stories.tsx' .


Estamos desarrollando


Entonces, habiéndose rodeado de muchas herramientas convenientes y preparándolas para el trabajo, puede comenzar a desarrollar nuevos componentes. Cada componente se colocará en su directorio en src/components con el nombre del componente. Contendrá todos los archivos asociados con él: css, el componente en sí mismo en el archivo tsx, pruebas, historias. Comenzamos el libro de cuentos, creamos historias para el componente y allí escribimos documentación para ello. Creamos pruebas y pruebas. La importación-exportación del componente terminado se escribe en index.ts .


Además, puede iniciar sesión en npm y publicar su biblioteca como un nuevo paquete npm. Y puede conectarlo directamente desde el repositorio git tanto desde el maestro como desde otras ramas. Por ejemplo, para mi pieza de trabajo puede hacer:


 npm i -s git+https://github.com/jmorozov/react-library-example.git 

Para que en la aplicación de consumidor de la biblioteca en el directorio node_modules solo haya el contenido del directorio dist en el estado ensamblado, debe agregar el comando "prepare": "npm run build" al campo de scripts .


Además, gracias a TS, la finalización automática en el IDE funcionará.


Para resumir


A mediados de 2019, puede comenzar a desarrollar rápidamente su biblioteca de componentes en React y TypeScript, utilizando prácticas herramientas de desarrollo. Este resultado puede lograrse tanto con la ayuda de una utilidad automatizada como en modo manual. Se prefiere la segunda forma si necesita paquetes actuales y más control. Lo principal es saber dónde cavar, y con la ayuda de un ejemplo en este artículo, espero que esto se haya vuelto algo más fácil.


También puede tomar la pieza de trabajo resultante aquí .


Entre otras cosas, no pretendo ser la verdad última y, en general, estoy involucrado en el front-end en la medida en que. Puede elegir paquetes alternativos y opciones de configuración y también tener éxito en la creación de su biblioteca de componentes. Me alegraría si compartes tus recetas en los comentarios. ¡Feliz codificación!

Source: https://habr.com/ru/post/461439/


All Articles