Loupe

Lazy-Loading : soyez paresseux avec React !

Aujourd’hui on se retrouve pour parler de lazy-loading dans un contexte d’application React afin d’améliorer les performances de navigation à l’intérieur de notre application.

Kesako ?

Lorsque nous créons une application React, nous avons plusieurs possibilités en ce qui concerne la façon d’injecter du code React dans notre template HTML.

Nous pouvons injecter du code React de façon brute :

<!DOCTYPE html>
<html>

<head></head>

<body>
  <div id="root"></div>

  <!-- La lib React -->
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  
  <!-- La lib ReactDOM également nécessaire au fonctionnement de React -->
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  
    <!-- Babel afin de transformer du code récent comme la syntaxe ES6 en du javascript pouvant être interpréter par la majorité des navigateurs -->
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

  <script type="text/babel">

      class Hello extends React.Component{
        render() {
          return <p>Hello {this.props.name}</p>;
        };
      }

      ReactDOM.render(
        <Hello name='World'/>,
        document.getElementById('root'),
      );
  </script>
</body>

</html>

Ou bien du code React bundlé.

Pour bundler du code React, il nous faut un bundler.
Un bundler est un outil qui va nous permettre d’écrire du code sous forme de modules indépendants et qui va ensuite transformer et concaténer ces différents modules en un ou plusieurs fichiers nommés bundles qui contiendront toute notre application js.

L’objectif aujourd’hui n’est pas de rentrer dans le détail de ce qu’est un bundler js. Cela pourra être l’objet d’un futur article. Retenez simplement que lorsque vous utilisez un bundler, que vous écrivez du code js dans plusieurs fichiers et que vous utilisez des termes comme export ou import ou encore require, tous ces fichiers sont ensuite transformés et concaténés par le bundler en un ou plusieurs fichiers .js et qui seront ensuite inclus dans votre template html via une simple balise <script /> comme ceci :

<script src="/dist/bundle.js"></script>

Et quand on parle de bundler javascript, impossible de ne pas parler de Webpack, qui est certainement le plus connu et le plus utilisé aujourd’hui. Il se démarque principalement de ces concurrents par ses excellentes performances, sa bonne documentation, son très grand nombre de plugins dont de nombreux sont des plugins officiels, ainsi que par sa capacité à pouvoir utiliser en tant que module beaucoup d’autres types de fichiers que des fichiers javascript comme des images ou des feuilles de style. Mais il a une fonctionnalité qui en revanche elle est commune à tous les bundler : le lazy-loading.

Pour le reste de cet article, nous allons rester avec Webpack comme bundler. Il est le bundler fourni par défaut dans les applications react créés via create-react-app. Je précise également que nous allons uniquement voir l’approche du lazy-loading lorsqu’il s’agit de modules écrits sous la syntaxe ESModule.

Le code-splitting et l’import dynamique

Comme nous l’avons dit tout à l’heure, lorsque nous écrivons du code javascript, celui-ci sera ensuite transformé dans un fichier bundle et sera inclus dans mon template html. Cela sous-entend que lorsque nous chargeons notre page index.html qui contient notre bundle, nous nous retrouvons à télécharger l’intégralité de notre application javascript d’un seul coup. Si notre application est lourde, cela peut avoir des conséquences sur les performances !

Le code-splitting va nous permettre de pouvoir découper notre application en plusieurs bundles distincts. Il existe plusieurs façons de splitter notre code, que ce soit via le fichier de configuration de webpack webpack.config.js ou bien via l’import dynamique fournit par webpack :

// Import dynamique
import('./my_math.js').then((my_math) => console.log("2 + 2 = " my_math.add(2 + 2));

Lorsque webpack rencontrera ce morceau de code, il créera automatiquement un nouveau bundle qui contiendra le contenu du fichier my_math.js et le chargera seulement lorsque l’utilisateur rencontrera cette ligne.

A noter que comme le précise la documentation, il n’est possible de n’importer que les propriétés exportées en export default. Si vous avez besoin d’importer des propriétés nommées, vous pouvez, en tant que solution alternative, passer par un module qui importe les propriétés que vous désirez et les exporte en  export default.

React.lazy

Le lazy-loading est l’action de loader un module seulement quand l’utilisateur en a besoin. Que ce soit lorsque l’utilisateur clique sur un bouton ou via une action de navigation. Ces actions vont déclencher une requête qui ira récupérer le bundle en question. 

React nous donne, via React.lazy, la possibilité de déclarer un composant qui sera importé dynamiquement. La fonction prend en paramètre un callback contenant notre import conditionnel.

Ainsi, pour lazy-loader un composant React :

const MyComponent = React.lazy(() => import('./MyComponent'));

Vous pouvez ensuite vous servir de ce composant comme un composant importé statiquement :

const TotoComponent = () => {
    return (
	    <>
		    <h3>Hello World</h3>
		    <Suspens fallback={<span>Loading...</span>}>
			    <MyComponent />
		    </Suspens>
	    </>
    );
}

Ainsi, lorsque l’utilisateur rencontrera pour la première fois le composant, le navigateur effectuera une requête serveur pour aller chercher le bundle contenant le code de notre composant !
Vous noterez également l’ajout d’un composant nommé Suspens qui nous vient de React.Suspens et qui fonctionne avec React.lazy. Il est obligatoire au bon fonctionnement du lazy-loading et doit toujours être le parent des composants lazy-loadés. Ainsi, Suspens, en plus de nous permettre d’utiliser le lazy-loading, nous permet notamment d’afficher quelque chose en attendant que le composant importé dynamiquement ait finit d’être téléchargé via sa propriété fallback qui peut prendre n’importe quel noeud React ! (Pratique pour afficher des loaders ! :p )

Et niveau Architecture ?

Comme nous avons pu le voir précédemment, l’utilisation du lazy-loading n’a aucune conséquence sur l’architecture de notre code, à l'exception de l'ajout d'un composant Suspens plus haut dans notre arborescence. Cependant, et si l’on suit la documentation de React sur le code-splitting, il est conseillé de gérer le lazy-loading au niveau du routing. En effet, le but premier du lazy-loading est d’avoir un bundle principal suffisamment léger contenant les pages que la majorité des utilisateurs consulteront à chaque visite. L’application ira ensuite loader dynamiquement les pages plus spécifiques comme des pages de paramètres ou une section d’administration. Voici pourquoi le routing semble être l’endroit tout trouvé pour définir ce qui sera chargé dynamiquement ou non ! :)

Ainsi, et pour reprendre le snippet de la documentation React :

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Et voilà ! Je pense qu’on a fait le tour de ce que propose React pour gérer le lazy-loading ! On se dit à plus tard pour un prochain article :p

Happy Coding !

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus