La plupart de mon travail, j'écris des backends, mais l'autre jour, il y avait une tâche pour démarrer une bibliothèque de composants sur React. Il y a quelques années, alors que la version React était aussi petite que mon expérience en développement frontal, j'ai déjà adopté une approche du projectile et il s'est avéré maladroitement et maladroitement. Compte tenu de la maturité de l'écosystème React actuel et de mon expérience grandissante, j'ai été inspiré cette fois pour tout faire correctement et commodément. En conséquence, j'avais un blanc pour la future bibliothèque, et afin de ne rien oublier et de tout rassembler en un seul endroit, cet article de triche a été écrit, ce qui devrait également aider ceux qui ne savent pas par où commencer. Voyons voir ce que j'ai fait.
TL / DR: le code d'une bibliothèque prête à démarrer peut être consulté sur github
Le problème peut être abordé de deux côtés:
- Trouver un kit de démarrage, un passe-partout ou un générateur cli prêts à l'emploi
- Collectionnez tout vous-même
La première méthode est bonne pour un démarrage rapide, lorsque vous ne voulez absolument pas gérer la configuration et la connexion des packages nécessaires. En outre, cette option convient aux débutants qui ne savent pas par où commencer et quelle devrait être la différence entre la bibliothèque et l'application régulière.
Au début, je suis allé dans le premier sens, mais j'ai ensuite décidé de mettre à jour les dépendances et de fixer quelques paquets supplémentaires, puis toutes sortes d'erreurs et d'incohérences ont plu. En conséquence, il a retroussé ses manches et a tout fait lui-même. Mais je mentionnerai le générateur de bibliothèque.
Créer une bibliothèque React
La plupart des développeurs qui traitent avec React ont entendu parler d'un démarreur d'application React pratique qui vous permet de minimiser la configuration du projet et fournit des valeurs par défaut raisonnables - Create React App (CRA). En principe, il pourrait être utilisé pour la bibliothèque ( il y a un article sur le Habré ). Cependant, la structure du projet et l'approche du développement de l'ui-kit sont légèrement différentes de la SPA habituelle. Nous avons besoin d'un répertoire séparé avec les codes sources des composants (src), un bac à sable pour leur développement et débogage (exemple), un outil de documentation et de démonstration ("showcase") et un répertoire séparé avec des fichiers préparés pour l'exportation (dist). De plus, les composants de bibliothèque ne seront pas ajoutés à l'application SPA, mais seront exportés via un fichier d'index. En y réfléchissant, je suis allé chercher et j'ai rapidement découvert un package CRA similaire - Creat React Library (CRL).
CRL, comme CRA, est un utilitaire CLI facile à utiliser. En l'utilisant, vous pouvez générer un projet. Il contiendra:
- Rollup configuré pour créer des modules cjs et es et une carte source avec prise en charge des modules css
- babel pour la transpilation dans ES5
- Plaisanterie pour les tests
- TypeScript (TS) comme option que nous aimerions utiliser
Pour générer le projet de bibliothèque, nous pouvons le faire ( npx nous permet de ne pas installer les packages globalement):
npx create-react-library
Nous répondons aux questions proposées. Et grâce à l'utilitaire, nous obtenons un projet généré et prêt à travailler de la bibliothèque de composants.
Avec une certaine structure Et puis quelque chose s'est mal passé ...Les dépendances sont un peu dépassées aujourd'hui, j'ai donc décidé de toutes les mettre à jour vers les dernières versions en utilisant npm-check :
npx npm-check -u
Un autre fait triste est que l'application sandbox dans le répertoire d' example
est générée en js. Vous devrez le réécrire manuellement dans TypeScript, en ajoutant tsconfig.json
et certaines dépendances (par exemple, typescript
lui-même et @types
base).
Le package react-scripts-ts
est également déclaré deprecated
et n'est plus pris en charge. Au lieu de cela, vous devez installer react-scripts
, car depuis un certain temps maintenant CRA (dont le package est react-scripts
) prend en charge TypeScript à partir de la boîte (en utilisant Babel 7).
En conséquence, je n'ai pas réussi à tirer la react-scripts
sur mon idée de la bibliothèque. Pour autant que je m'en souvienne, le Jest de ce package nécessitait l'option de compilateur isolatedModules
, qui allait à l'encontre de mon désir de générer et d'exporter d.ts
partir de la bibliothèque (tout cela est en quelque sorte lié aux limitations de Babel 7, qui est utilisé par Jest et react-scripts
pour compiler TS ) J'ai donc fait une eject
pour les react-scripts
de react-scripts
, regardé le résultat et tout refait avec mes mains, dont je parlerai plus tard.
tsdx
Merci à l'utilisateur StanEgo , qui a parlé d'une excellente alternative à Create React Library - tsdx . Ce cli-utilitaire est également similaire à CRA et dans une commande créera la base de votre bibliothèque avec Rollup, TS, Prettier, husky, Jest et React configurés. Et React est disponible en option. Assez simple à faire:
npx tsdx create mytslib
Et par conséquent, les nouvelles versions nécessaires des packages seront installées et tous les paramètres définis. Obtenez un projet de type CRL. La principale différence avec CRL est Zero-config. Autrement dit, la configuration Rollup nous est cachée dans tsdx (tout comme CRA le fait).
Ayant rapidement parcouru la documentation, je n'ai pas trouvé les méthodes recommandées pour une configuration plus fine ou quelque chose comme éjecter comme dans CRA. Après avoir examiné la question du projet, j'ai constaté que jusqu'à présent, il n'y a pas une telle possibilité . Pour certains projets, cela peut être critique, auquel cas vous devrez travailler un peu avec vos mains. Si vous n'en avez pas besoin, alors tsdx est un excellent moyen de démarrer rapidement.
Prenez le contrôle entre nos mains
Mais que se passe-t-il si vous suivez la deuxième voie et que vous collectez tout vous-même? Commençons donc depuis le début. Exécutez npm init
et générez package.json
pour la bibliothèque. Ajoutez-y quelques informations sur notre forfait. Par exemple, nous écrivons les versions minimales pour node et npm dans le champ engines
. Les fichiers collectés et exportés seront placés dans le répertoire dist
. Nous l'indiquons dans le champ des files
. Nous créons une bibliothèque de composants react, nous espérons donc que les utilisateurs auront les packages nécessaires - nous peerDependencies
versions minimales requises de peerDependencies
react-dom
dans le champ peerDependencies
.
Installez maintenant les packages react et react react-dom
et les types nécessaires (puisque nous scierons des composants sur TypeScript) en tant que devDependencies (comme tous les packages de cet article):
npm install --save-dev react react-dom @types/react @types/react-dom
Installez TypeScript:
npm install --save-dev typescript
Créons des fichiers de configuration pour le code principal et les tests: tsconfig.json
et tsconfig.test.json
. Notre target
sera en es5
, nous générerons sourceMap
, etc. Une liste complète des options possibles et leur signification peut être trouvée dans la documentation . N'oubliez pas d' include
répertoire source dans l' include
, et ajoutez les node_modules
et dist
dans l' exclude
. Dans package.json
nous indiquons dans le champ typings
où trouver les types pour notre bibliothèque - dist/index
.
Créez le répertoire src
pour les composants source de la bibliothèque. Ajoutez toutes sortes de petites choses, comme .gitignore
, .editorconfig
, un fichier avec une licence et README.md
.
Rollup
Nous utiliserons Rollup pour l'assemblage, comme suggéré par CRL. Paquets et config requis, j'ai aussi espionné CRL. En général, j'ai entendu l'opinion que Rollup est bon pour les bibliothèques et Webpack pour les applications. Cependant, je n'ai pas configuré Webpack (ce que CRA fait pour moi), mais Rollup est vraiment bon, simple et beau.
Installer:
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
Dans package.json
ajoutez les champs avec la distribution des ensembles de bibliothèques collectés, comme le recommande le rollup
- pkg.module :
"main": "dist/index.js", "module": "dist/index.es.js", "jsnext:main": "dist/index.es.js"
Créez le fichier de configuration 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 config est un fichier js, ou plutôt un objet exporté. Dans le champ de input
, spécifiez le fichier dans lequel les exportations de notre bibliothèque sont enregistrées. output
- décrit nos attentes sur la sortie - dans quel module compiler dans le format et où le mettre.
Vient ensuite le terrain avec une liste et la configuration des plugins- rollup-plugin-peer-deps-external - vous permet d'exclure
peerDependencies
du bundle
pour réduire sa taille. Ceci est raisonnable, car peerDependencies
est attendu de l'utilisateur de la bibliothèque. - rollup-plugin-postcss - intègre PostCss et Rollup . Ici, nous désactivons les modules css, incluons css dans le package d'exportation de notre bibliothèque, le minimisons et activons la création de sourceMap. Si vous n'exportez aucun css autre que celui utilisé par les composants de la bibliothèque, l'
extract
peut être évitée - les css nécessaires dans les composants seront ajoutés à la balise head sur la page si nécessaire à la fin. Cependant, dans mon cas, il est nécessaire de distribuer des CSS supplémentaires (grille, couleurs, etc.), et le client devra se connecter explicitement à la bibliothèque css-bundle. - rollup-plugin-url - vous permet d'exporter diverses ressources, telles que des images
- svgr - transforme svg en composants React
- rollup-plugin-node-resolution - définit l'emplacement des modules tiers dans node_modules
- rollup-plugin-typescript2 - connecte le compilateur TypeScript et offre la possibilité de le configurer
- rollup-plugin-commonjs - convertit les modules de dépendance commonjs en modules es afin qu'ils puissent être inclus dans le bundle
Ajoutez une commande dans le champ scripts
package.json
à construire ( "build": "rollup -c"
) et démarrez l'assemblage en mode veille pendant le développement ( "start": "rollup -c -w && npm run prettier-watch"
) .
Le premier composant et fichier d'exportation
Nous allons maintenant écrire le composant React le plus simple pour vérifier le fonctionnement de notre assemblage. Chaque composant de la bibliothèque sera placé dans un répertoire séparé du répertoire parent - src/components/ExampleComponent
. Ce répertoire contiendra tous les fichiers associés au composant - tsx
, css
, test.tsx
et ainsi de suite.
Créons un fichier de styles pour le composant et tsx
fichier tsx
du composant lui-même.
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;
De plus, dans src
vous devez créer un fichier avec des types communs aux bibliothèques, où un type sera déclaré pour css et svg (jeté un œil à 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 }; }
Tous les composants exportés et css doivent être spécifiés dans le fichier d'exportation. Nous l'avons - src/index.tsx
. Si certains css ne sont pas utilisés dans le projet et ne sont pas répertoriés comme faisant partie de ceux importés dans src/index.tsx
, ils seront alors rejetés de l'assembly, ce qui est correct.
src / index.tsx import { ExampleComponent, Props } from './ExampleComponent'; import './export.css'; export { ExampleComponent, Props };
Vous pouvez maintenant essayer de construire la bibliothèque - npm run build
. En conséquence, le rollup
démarre et collecte notre bibliothèque en bundles, que nous trouverons dans le répertoire dist
.
Ensuite, nous ajoutons quelques outils pour améliorer la qualité de notre processus de développement et son résultat.
Je déteste dans une révision de code pour souligner une mise en forme négligente ou non standard pour un projet, et encore plus pour en discuter. De telles failles devraient naturellement être corrigées, mais les développeurs devraient se concentrer sur ce que fait et comment le code fait, plutôt que sur son apparence. Ces correctifs sont le premier candidat à l'automatisation. Il y a un paquet merveilleux pour cette tâche - plus joli . Installez-le:
npm install --save-dev prettier
Ajoutez une configuration pour un petit raffinement des règles de formatage.
.prettierrc.json { "tabWidth": 3, "singleQuote": true, "jsxBracketSameLine": true, "arrowParens": "always", "printWidth": 100, "semi": true, "bracketSpacing": true }
Vous pouvez voir la signification des règles disponibles dans la documentation . WebStrom après avoir créé le fichier de configuration lui-même suggérera une utilisation prettier
lors du démarrage du formatage via l'EDI. Pour éviter que le formatage ne perde du temps, ajoutez les /node_modules
et /dist
aux exceptions à l'aide du fichier .prettierignore
(le format est similaire à .gitignore
). Vous pouvez maintenant exécuter prettier
en appliquant des règles de mise en forme au code source:
prettier --write "**/*"
Afin de ne pas exécuter la commande explicitement à chaque fois avec vos mains et pour être sûr que le code des autres développeurs de projet sera également plus prettier
, ajoutez la prettier
exécution sur le precommit-hook
de precommit-hook
pour les fichiers marqués comme intermédiaires (via git add
). Pour une telle chose, nous avons besoin de deux outils. Premièrement, il est hasky , responsable de l'exécution des commandes avant de valider, de pousser, etc. Et deuxièmement, il est par étapes , qui peut exécuter différents linters sur les fichiers intermédiaires. Nous devons exécuter une seule ligne pour livrer ces packages et ajouter des commandes de lancement à package.json
:
npx mrm lint-staged
Nous ne pouvons pas attendre le formatage avant de nous engager, mais assurez-vous que le prettier
travaille constamment sur les fichiers modifiés dans le processus de notre travail. Oui, nous avons besoin d'un autre package - onchange . Il vous permet de surveiller les modifications apportées aux fichiers dans le projet et d'exécuter immédiatement la commande nécessaire pour eux. Installer:
npm install --save-dev --save-exact onchange
Ensuite, nous ajoutons aux commandes de champ de scripts
dans package.json
:
"prettier-watch": "onchange 'src/**/*' -- prettier --write {{changed}}"
Sur ce point, tous les litiges concernant la mise en forme dans le projet peuvent être considérés comme clos.
Éviter les erreurs avec ESLint
ESLint est depuis longtemps devenu un standard et peut être trouvé dans presque tous les projets js et ts. Il nous aidera aussi. Dans la configuration ESLint, je fais confiance à CRA, alors prenez simplement les packages nécessaires de CRA et branchez-les dans notre bibliothèque. De plus, ajoutez des configurations pour TS et prettier
(pour éviter les conflits entre ESLint
et 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
à l'aide du fichier de configuration.
.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" } }
Ajoutez la commande lint
- eslint src/**/* --ext .ts,.tsx --fix
au champ scripts
de package.json
. Vous pouvez maintenant exécuter eslint via npm run lint
.
Tester avec Jest
Pour écrire des tests unitaires pour les composants de bibliothèque, installez et configurez Jest , une bibliothèque de test de Facebook. Cependant, depuis nous compilons TS non via babel 7, mais via tsc, nous devons également installer le paquet ts-jest :
npm install --save-dev jest ts-jest @types/jest
Pour que Jest accepte correctement les importations de fichiers CSS ou autres, vous devez les remplacer par Mokami. Créez le répertoire __mocks__
et créez-y deux fichiers.
styleMock.ts
:
module.exports = {};
fileMock.ts
:
module.exports = 'test-file-stub';
Créez maintenant la configuration de plaisanterie.
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' } };
Nous allons écrire le test le plus simple pour notre ExampleComponent
dans son répertoire.
ExampleComponent.test.tsx import { ExampleComponent } from './ExampleComponent'; describe('ExampleComponent', () => { it('is truthy', () => { expect(ExampleComponent).toBeTruthy(); }); });
Ajoutez la commande test
- npm run lint && jest
au champ scripts
de package.json
. Pour plus de fiabilité, nous conduirons également le linter. Vous pouvez maintenant exécuter nos tests et vous assurer qu'ils réussissent - npm run test
. Et pour que les tests ne tombent pas dans dist
lors de l'assemblage, ajoutez le champ d' exclude
du plug-in de configuration Rollup au champ d' exclude
- ['src/**/*.test.(tsx|ts)']
. Spécifiez l'exécution des tests dans le husky pre-commit hook
avant d'exécuter lint-staged
- "pre-commit": "npm run test && lint-staged"
.
Concevoir, documenter et admirer des composants avec un livre d'histoires
Chaque bibliothèque a besoin d'une bonne documentation pour une utilisation réussie et productive. Quant à la bibliothèque de composants d'interface, non seulement je veux en savoir plus, mais aussi voir à quoi ils ressemblent, et il vaut mieux les toucher et les changer. Pour prendre en charge une telle liste de souhaits, il existe plusieurs solutions. J'avais l'habitude d'utiliser un Styleguidist . Ce package vous permet d'écrire de la documentation au format markdown, ainsi que d'y insérer des exemples des composants React décrits. En outre, la documentation est collectée et à partir de celle-ci, un catalogue de site-vitrine est obtenu, où vous pouvez trouver le composant, lire la documentation à son sujet, connaître ses paramètres et y pousser une baguette.
Cependant, cette fois, j'ai décidé de regarder de plus près son concurrent - Storybook . Aujourd'hui, il semble plus puissant avec son système de plugins. De plus, il est en constante évolution, possède une grande communauté et commencera bientôt également à générer ses pages de documentation à l'aide de fichiers de démarques. Un autre avantage du Storybook est qu'il s'agit d'un bac à sable - un environnement pour le développement de composants isolés. Cela signifie que nous n'avons pas besoin d'exemples d'applications à part entière pour le développement de composants (comme le suggère CRL). Dans le livre d'histoires, nous écrivons des stories
- des fichiers ts dans lesquels nous transférons nos composants avec des props
entrée vers des fonctions spéciales (il est préférable de regarder le code pour le rendre plus clair). En conséquence, une application vitrine est assemblée à partir de ces stories
.
Exécutez le script qui initialise le livre d'histoires:
npx -p @storybook/cli sb init
Maintenant, faites-vous des amis avec TS. Pour ce faire, nous avons besoin de quelques packages supplémentaires, et en même temps, nous mettrons quelques modules complémentaires utiles:
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
Le script a créé un répertoire avec la configuration du storybook
- .storybook
et un exemple de répertoire que nous .storybook
impitoyablement. Et dans le répertoire de configuration, nous changeons les extensions et la config
en ts
. Nous ajoutons des addons au fichier addons.ts
:
import '@storybook/addon-actions/register'; import '@storybook/addon-links/register'; import '@storybook/addon-knobs/register';
Maintenant, vous devez aider le livre d'histoires à l'aide de la configuration du .storybook
dans le répertoire .storybook
.
webpack.config.js module.exports = ({ config }) => { config.module.rules.push({ test: /\.(ts|tsx)$/, use: [ { loader: require.resolve('awesome-typescript-loader') },
config.ts
un peu la configuration config.ts
, en ajoutant des décorateurs pour connecter des modules complémentaires à toutes nos histoires.
config.ts import { configure } from '@storybook/react'; import { addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; import { withKnobs } from '@storybook/addon-knobs';
Nous allons écrire notre première story
dans le répertoire des composants 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" /> ~~~ ` });
Nous avons utilisé des addons:
- boutons - permet de changer les accessoires en temps réel dans le composant affiché dans le livre d'histoires. Pour ce faire, enveloppez les accessoires dans des fonctions spéciales dans les histoires
- info - vous permet d'ajouter de la documentation et une description des accessoires à la page d'histoire
Notez maintenant que le script d'initialisation du livre d'histoires a ajouté la commande storybook à notre package.json
. Utilisez-le pour exécuter le npm run storybook
. Le livre de contes sera assemblé et exécuté à l' http://localhost:6006
. N'oubliez pas d'ajouter à l'exception pour le module typescript
dans la configuration Rollup
- 'src/**/*.stories.tsx'
.
Nous développons
Ainsi, après vous être entouré de nombreux outils pratiques et les avoir préparés pour le travail, vous pouvez commencer à développer de nouveaux composants. Chaque composant sera placé dans son répertoire dans src/components
avec le nom du composant. Il contiendra tous les fichiers qui lui sont associés - css, le composant lui-même dans le fichier tsx, les tests, les histoires. Nous commençons le livre d'histoires, créons des histoires pour le composant, et là nous écrivons la documentation pour celui-ci. Nous créons des tests et testons. L'import-export du composant fini est écrit dans index.ts
.
De plus, vous pouvez vous connecter à npm
et publier votre bibliothèque en tant que nouveau package npm. Et vous pouvez le connecter directement à partir du référentiel git à la fois du maître et d'autres branches. Par exemple, pour ma pièce, vous pouvez faire:
npm i -s git+https://github.com/jmorozov/react-library-example.git
Pour que dans l'application consommateur de bibliothèque du répertoire node_modules
, il n'y ait que le contenu du répertoire dist
à l'état assemblé, vous devez ajouter la commande "prepare": "npm run build"
au champ scripts
.
De plus, grâce à TS, l'auto-complétion dans l'EDI fonctionnera.
Pour résumer
À la mi-2019, vous pouvez commencer assez rapidement à développer votre bibliothèque de composants sur React et TypeScript, à l'aide d'outils de développement pratiques. Ce résultat peut être obtenu à la fois à l'aide d'un utilitaire automatisé et en mode manuel. La deuxième façon est préférable si vous avez besoin de packages actuels et de plus de contrôle. L'essentiel est de savoir où creuser, et à l'aide d'un exemple dans cet article, j'espère que cela est devenu un peu plus facile.
Vous pouvez également emporter la pièce résultante ici .
Entre autres choses, je ne prétends pas être la vérité ultime et, en général, je suis engagé dans le front-end dans la mesure où. Vous pouvez choisir d'autres packages et options de configuration et réussir à créer votre bibliothèque de composants. Je serais heureux si vous partagez vos recettes dans les commentaires. Bon codage!