17 novembre 2020
Améliorez vos composants avec Storybook
8 minutes de lecture
Connaissez-vous Storybook ? Cet outil open-source offre un environnement de travail first class pour développer vos composants et permet la création d'un référentiel. La dernière version 6 se focalise sur l'expérience développeur (DX) et facilite grandement son installation avec une approche « zéro config ». Les anciennes versions demandaient un investissement non négligeable pour maintenir à jour son Storybook ce qui freinait son adoption.
Il est désormais très facile d'ajouter un Storybook à son projet grâce à la commande :
npx sb init
• Detecting project type. ✓
• Preparing to install dependencies. ✓
• Done ✓
Celle-ci va analyser les dépendances (React, Vue, Angular…) de vos projets afin de créer au mieux le projet Storybook. Vous pouvez ensuite lancer votre Storybook :
yarn storybook
╭───────────────────────────────────────────────────╮
│ │
│ Storybook 6.0.26 started │
│ 12 s for manager and 13 s for preview │
│ │
│ Local: http://localhost:6006 │
│ On your network: http://192.168.1.33:6006 │
│ │
╰───────────────────────────────────────────────────╯
Alors pourquoi utiliser Storybook ? Cela ne freine-t-il pas le développement en ajoutant un outil supplémentaire dans votre workflow ? Afin de (vous) convaincre, je vais détailler 5 bonnes raisons d'utiliser Storybook dans vos projets.
Pour illustrer cet article, nous allons nous baser sur ce composant tiré de la page d'accueil de Chakra UI (notre librairie de composants préférée chez Premier Octet) :
1. Créer des composants en isolation
Lorsque vous avez un Storybook, il est conseillé de développer vos composants directement dans celui-ci, cela force à les développer en isolation de la logique applicative (comme la récupération des données). C'est seulement une fois le composant développé et validé visuellement que vous allez l'intégrer dans votre application et récupérer les données dans un container parent. De cette manière vos composants seront principalement stateless et se contenteront d'afficher les données. Il sera également beaucoup plus facile de tester leur rendu et interaction s'ils n'ont pas toute la logique de récupération des données embarquée (pas besoin de faire des mocks).
Voici l'interface de notre composant Card :
<Card
isPremium
rating={4.87}
price={199}
caption="Verified - Cape Town"
title="Modern, Chic Penthouse with Mountain, City & Sea Views"
/>
Et voici comment l'intégrer dans Storybook :
// Card.stories.tsx
import React from 'react'
import { Story, Meta } from '@storybook/react/types-6-0'
import Card, { IProps } from './Card'
export default {
title: 'App/Card',
component: Card,
} as Meta
const Template: Story<IProps> = args => <Card {...args} />
export const BasicCard = Template.bind({})
BasicCard.args = {
isPremium: false,
rating: 4.87,
price: 199,
caption: 'Verified - Cape Town',
title: 'Modern, Chic Penthouse with Mountain, City & Sea Views',
}
Il est recommandé de créer la story (fichier Card.stories.tsx
) dans le même répertoire que votre composant afin d'en faciliter la maintenance.
Le composant sera alors rendu avec les props passées via l'attribut args dans notre Storybook :
L'attribut title
de notre story permet de définir l'arborescence de votre storybook avec des slashs App/Card
.
Votre Storybook est décorrélé de votre application, de ce fait vos composants dépendants de providers (thème, contexte) ne marcheront pas. Voici comme les initialiser dans votre Storybook
Ici nous utilisons la librairie Chakra UI, il nous faut donc injecter le ThemeProvider de Chakra afin d'avoir les bons styles. Pour cela il faut définir un décorateur dans le fichier .storybook/preview.js
:
// .storybook/preview.js
import React from 'react'
import { ChakraProvider } from '@chakra-ui/core'
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
}
const withThemeProvider = (Story, context) => {
return (
<ChakraProvider resetCSS>
<Story {...context} />
</ChakraProvider>
)
}
export const decorators = [withThemeProvider]
De cette manière chaque story sera bien rendu dans le provider de Chakra UI. Si vos composants reposent sur une librairie i18n, il vous faudra par exemple rajouter son provider :
<IntlProvider>
<ChakraProvider resetCSS>
<Story {...context} />
</ChakraProvider>
</IntlProvider>
2. Forcer à typer son composant
En définissant précisément les props de votre composant, Storybook va pouvoir inférer sur vos typages TypeScript afin de générer automatiquement les contrôles de vos composants. Voici le typage des props de notre composant :
export interface IProps {
isPremium?: boolean
caption: string
title: string
price: number
rating: number
}
const Card = ({ isPremium = false, caption, title, price, rating }: IProps) => (
<Box>
{/* markup… */}
</Box>
)
Ainsi, en passant notre type, Storybook va pouvoir générer les contrôles automatiquement :
Par défaut, Storybook génère des contrôles génériques (toggles pour des booléens, listes pour des énumérations) mais il est possible de personnaliser ceux-ci pour mieux les adapter à votre logique. Prenons l'exemple de la props rating
qui accepte une valeur entre 0 et 5. Nous pouvons dire à Storybook de générer un slider :
export default {
title: "App/Card",
component: Card,
argTypes: {
rating: { control: { type: "range", min: 0, max: 5, step: 0.1 } },
},
} as Meta
Vous pouvez retrouver la liste complète des types de contrôles ici.
3. Générer une documentation
En plus de l'onglet Canvas permettant de jouer avec le composant, Storybook propose un onglet Docs regroupant les informations de votre composant et un snippet du code pour l'intégrer :
Vous pouvez commenter vos types afin d'étoffer la documentation (colonne Description sur l'image ci-dessus) :
export interface IProps {
isPremium?: boolean
offerType: 'rental' | 'sell'
title: string
/**
* Week USD price
*/
price: number
/**
* Must be between 0 and 5
*/
rating: number
}
4. Tester plus facilement les cas de bord
Trop souvent les composants sont testés avec les mêmes données : les fixtures retournées par votre API. Développer un composant directement dans l'application nous rend dépendant de ces données, nous empêchant de bien tester tous les cas de bord (edge cases). Ces derniers peuvent-être :
- Comportement lors d'une erreur ;
- Comportement lors des chargements ;
- Comportement lorsqu'il n'y a pas de donnée (empty state) ;
- Comportement lorsque les données textuelles sont longues.
Ajoutez à chacun de ces cas la dimension responsive (mobile, tablette et desktop) et le nombre de cas à vérifier s'envole vite. L'un des grands intérêts de Storybook est de nous permettre de dégager ces angles facilement en nous donnant la main sur les données du composant.
Une story d'un composant doit présenter tous les états du composant (happy path, erreur, chargement…) :
Voici un exemple de story présentant un composant CardList dans les trois états (en chargement, sans résultat et avec résultats) :
import React from 'react'
import { Story, Meta } from '@storybook/react/types-6-0'
import CardList, { IProps } from './CardList'
export default {
title: 'App/CardList',
component: CardList,
} as Meta
const Template: Story<IProps> = args => <CardList {...args} />
export const Basic = Template.bind({})
Basic.args = {
isLoading: false,
cards: [
{
isPremium: true,
rating: 4.2,
price: 199,
offerType: 'rental',
title: 'Modern, Chic Penthouse with Mountain, City & Sea Views',
},
{
isPremium: false,
rating: 4.9,
price: 490,
offerType: 'rental',
title: 'White House',
},
],
}
export const Loading = Template.bind({})
Loading.args = {
isLoading: true,
}
export const Empty = Template.bind({})
Empty.args = {
cards: [],
}
Nous avons ainsi tous les états à portée de quelques clics et pouvons tester chaque état dans les différents viewports directement depuis Storybook :
4. Être plus concentré sur son composant
Les points précédents amènent à ce point-ci : il est plus facile de développer un nouveau composant dans un projet existant. Le fait de développer en isolation dans Storybook permet de s'affranchir du bruit du reste de l'application (chargement des données, logique métier, authentification…) qui peut être pénible sur des applications complexes au quotidien. Le développeur gagne alors en concentration et en productivité, aboutissant à une amélioration de la qualité des composants. Storybook offre en quelque sorte un cadre de développement « zen ».
5. Permettre à des profils variés d'intervenir
Enfin, en isolant les composants de l'application, Storybook permet à des profils plus variés d'intervenir sur la base de code : un profil plus expérimenté en intégration peut intervenir dans le développement du composant sans avoir à être expert en GraphQL ou optimisation de performances. Une fois le composant développé, un profil plus technique peut l'intégrer dans l'application et brancher les tuyaux avec les sources de données.
Les product owners, designers peuvent ainsi être plus impliqués dans le cycle de développement : Storybook peut-être déployé lors de la création d'une PR grâce aux GitHub Actions et permettre une validation visuelle des composants.
Intégrer son Storybook dans son workflow
Un autre sujet important concerne l'intégration de son Storybook dans son workflow de développement. Les créateurs de Storybook propose un outil SaaS répondant à cette problématique : Chromatic. Cet outil offre deux fonctionnalités majeures : l'hébergement du Storybook et un système de review afin de valider les changements.
Son usage passe par une simple commande permettant d'envoyer les changements de son Storybook sur Chromatic :
"scripts": {
"chromatic": "npx chromatic --project-token YOUR_CHROMATIC_TOKEN"
},
Dès que des changements sont détectés, Chromatic propose alors une interface de review :
L'équipe peut alors valider les modifications grâce à un diff visuel :
Chromatic propose un free-tiers généreux comportant :
- Hébergement des Storybook illimité
- Collaborateurs illimités
- 5 000 snapshots de composants (par mois)
- Intégration avec git et les CI
Vous pouvez seulement utiliser Chromatic pour héberger vos Storybooks sans la fonctionnalité de snapshots qui peut se révéler à terme payante.
Pour conclure
Voici donc un rappel des avantages de Storybook :
- Créer les composants en isolation ;
- Inciter à bien typer ses composants ;
- Générer une documentation / référentiel ;
- Faciliter les tests des cas de bord ;
- Faciliter le rendu sous différents viewports ;
- Avoir tous les états des composants à portée de quelques clics ;
- Offrir au développeur un espace isolé du bruit de l'application et de sa complexité.
Storybook est un outil très utile dans le cycle de développement d'une application avec une approche composant. Il permet un gain qualitatif au niveau de vos composants et offre un référentiel. Mais attention : maintenir un Storybook demande l'implication de l'ensemble de l'équipe. Il faut bien veiller à le mettre à jour au fil des développements (modification des composants, ajout de props…) sous peine de voir son Storybook tomber aux oubliettes…
Ceci dit, la dernière version permet de réduire considérablement les efforts de maintenance du Storybook et insuffle un vent vertueux au développement de vos composants !