Transformez vos SVG en composants React avec SVGR

Photo par Daniele Levis Pelusi sur Unsplash
Cet article est aussi disponible en : English

Transformez vos SVG en JSX simplement avec SVGR pour créer des applications React avec des icônes SVG.

En tant que développeur React, vous vous êtes certainement retrouvé à devoir intégrer des SVG dans vos applications. Il existe plusieurs manières de procéder, la plus élégante est de transformer vos images SVG en composant React. On va voir ici comment s'y prendre.

Comment intégrer des SVG ?

Il y a plusieurs manières pour intégrer des SVG dans votre application React :

1. Utiliser un tag img

const Star = props => (
<img src="star.svg" alt="Star" width="20" height="20" {...props} />
)

Cette solution est tout à fait viable. Vous pouvez vous servir de SVGO pour nettoyer un peu votre SVG et file-loader si vous utilisez webpack. Elle présente cependant quelques limites :

  • Une requête HTTP par SVG
  • Impossible de styliser le SVG (la couleur notamment)

2. Utiliser du JSX

const Star = props => (
<svg viewBox="0 0 512 512" width="20" height="20" {...props}>
<path
fill="currentColor"
d="M256 13l79 160 177 26-128 124 30 176-158-83-158 83 30-176L0 199l177-26 79-160z"
/>
</svg>
)

Cette solution consiste à intégrer le SVG directement dans le composant React en JSX. Elle présente plusieurs avantages :

  • Aucune requête en plus, c'est bundlé avec votre JS
  • On peut styliser chaque partie du SVG
  • "currentColor" nous permet d'hériter directement de la couleur du texte

Intégrer vos SVG directement en JSX est la solution à privilégier pour une maintenance facile et un contrôle total. Cependant elle nécessite une opération un peu délicate, passer d'un fichier SVG à un composant React. À première vue, c'est un copier-coller, mais le JSX n'est pas exactement du HTML et ça s'avère très fastidieux quand on a un grand nombre d'icônes.

Passer d'un SVG à un composant React

C'est là que SVGR entre en scène, il vous permet d'automatiser la génération de composants React à partir de vos SVG. Prenons un exemple concret en partant d'un SVG généré via Sketch :

<?xml version="1.0" encoding="UTF-8"?>
<svg
width="48px"
height="1px"
viewBox="0 0 48 1"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Rectangle 5</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g
id="19-Separator"
transform="translate(-129.000000, -156.000000)"
fill="#063855"
>
<g id="Controls/Settings" transform="translate(80.000000, 0.000000)">
<g id="Content" transform="translate(0.000000, 64.000000)">
<g id="Group" transform="translate(24.000000, 56.000000)">
<g id="Group-2">
<rect id="Rectangle-5" x="25" y="36" width="48" height="1"></rect>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

En tapant la ligne de commande SVGR :

npx svgr --no-semi --icon --replace-attr-value "#063855=currentColor" my-icon.svg

J'obtiens le code du composant suivant:

import React from 'react'
const SvgComponent = props => (
<svg width="1em" height="1em" viewBox="0 0 48 1" {...props}>
<path d="M0 0h48v1H0z" fill="currentColor" fillRule="evenodd" />
</svg>
)
export default SvgComponent

Plutôt cool non 😏 ?

Intégrer SVGR dans mon projet

Plusieurs solutions s'offrent à vous pour intégrer SVGR dans votre projet. À vous de choisir celle qui correspond le mieux à vos habitudes de travail.

1. Ligne de commande

Avant toute chose, installez @svgr/cli:

npm install @svgr/cli

Une fois installé, SVGR expose une ligne de commande. Une bonne manière de procéder est d'ajouter un script à votre package.json pour automatiser la transformation des SVG.

// package.json
{
"scripts": {
"build:svg": "svgr svg/ -d components/
}
}

Le script suivant permet de transformer chacun des SVG du dossier "svg/" en composant React dans "components/".

Pour le lancer : npm run build:svg

2. En utilisant webpack

Pour les utilisateurs de webpack, SVGR propose un loader. Pour commencer, installez @svgr/webpack:

npm install @svgr/webpack

Vous pouvez donc directement importer vos SVG dans votre code et laisser la magie de webpack opérer. Il vous suffit d'ajouter le loader comme ceci :

// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
use: ['babel-loader', '@svgr/webpack'],
},
],
},
}

A partir maintenant, vous pouvez importer directement vos SVG sous forme de composant React. Ils seront automatiquement transformés par SVGR.

import React from 'react'
import Star from 'svg/star.svg'
const App = () => (
<div>
<Star />
</div>
)

3. Via Node.js

Le dernier usage, destiné aux utilisateurs plus avancés, vous permet d'utiliser SVGR directement dans un script Node.js. Pour cet usage vous devez installer @svgr/core:

npm install @svgr/core

Voici un exemple de code qui transforme un SVG de manière programmatique:

import svgr from '@svgr/core'
const svgCode = `
<?xml version="1.0" encoding="UTF-8"?>
<svg width="88px" height="88px" viewBox="0 0 88 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Dismiss</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Blocks" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
<g id="Dismiss" stroke="#063855" stroke-width="2">
<path d="M51,37 L37,51" id="Shape"></path>
<path d="M51,51 L37,37" id="Shape"></path>
</g>
</g>
</svg>
`
svgr(svgCode, { prettier: false, componentName: 'MyComponent' }).then(
jsCode => {
console.log(jsCode)
},
)

Allez plus loin avec SVGR

SVGR vous permet d'aller très loin dans la transformation. Un grand nombre d'options sont disponibles : optimisation du SVG, formatage code, ajout d'un titre... Je ne vais pas toutes les énoncer mais vous pouvez les retrouver dans la documentation.

Transformer vos icônes

Une "font-icon" est une police dont on a remplacé certains caractères par des icônes. Les icônes bénéficient de la flexibilité d’une police et on peut changer leur taille et leur couleur via CSS. Transformez vos SVG en JSX simplement avec SVGR pour créer des applications React.

Il est possible d'obtenir le même comportement avec SVGR, voici un exemple :

svgr --icon --replace-attr-value "#000000=currentColor" my-icon.svg

On exploite deux options de SVGR :

  • --icon : spécifie "width" et "height" à "1em"
  • --replace-attr-value "#000000=currentColor" : remplace "#000000" par "currentColor" dans tous les attributs du SVG

Avec ces deux options vous obtiendrez le même comportement qu'une "font-icon" classique !

Comment ça marche ?

SVGR permet de transformer du SVG en JSX. Pour y arriver, il délègue chaque partie du travail à des outils spécialisés :

Attardons-nous un peu sur la phase la plus délicate, la transformation. Elle permet de passer d'un code SVG/HTML à du JSX compris par React. Là où la plupart des librairies passent par une série d'expressions régulières, Babel nous permet d'appliquer des transformations directement sur l'AST du HTML à savoir le DOM. Autrement dit, il décompose la structure du code et applique la transformation.

Cela permet à SVGR d'appliquer des transformations compliquées telles que "--replace-attr-value" sans aucun effet de bord !

Le code suivant correspondant à la transformation opérée lorsque vous activez l'option "replace-attr-value". Ceux qui sont familiers avec les plugins Babel et la manipulation d'AST ne devraient pas être perdus.

const replaceAttrValue = ({ types: t, template }, opts) => {
function getAttributeValue(value, literal) {
if (typeof value === 'string' && literal) {
return t.jsxExpressionContainer(template.ast(value).expression)
}
if (typeof value === 'string') {
return t.stringLiteral(value)
}
if (typeof value === 'boolean') {
return t.jsxExpressionContainer(t.booleanLiteral(value))
}
if (typeof value === 'number') {
return t.jsxExpressionContainer(t.numericLiteral(value))
}
return null
}
return {
visitor: {
JSXAttribute(path) {
const valuePath = path.get('value')
if (!valuePath.isStringLiteral()) return
opts.values.forEach(({ value, newValue, literal }) => {
if (!valuePath.isStringLiteral({ value })) return
valuePath.replaceWith(getAttributeValue(newValue, literal))
})
},
},
}
}
export default replaceAttrValue

Cette transformation permet de remplacer la valeur d'un attribut sans aucun effet de bord. C'est beaucoup plus safe qu'une expression régulière !

Pour les plus curieux vous pouvez retrouver le code du projet H2X sur GitHub.

J'espère que SVGR vous fera gagner autant de temps qu'il m'en fait gagner chaque jour !

Discuter sur TwitterÉditer sur GitHub