Chargement paresseux avec webpack

La capture d’écran ci-dessous montre le premier chargement d’une fiche détail (popin) sur notre projet actuel pour un utilisateur donné. Le contenu et le composant React censé l’afficher sont chargés en parallèle.

Lazy loading

Le plus fort dans cette approche, c’est la simplicité de mise en œuvre une fois le projet configuré. Avant de rentrer dans les détails techniques, quelques rappels sur les notions de code splitting et de lazy loading.

Lazy loading et code splitting, quels bénéfices ?

Le code splitting consiste à séparer le code de votre frontend web en plusieurs modules distincts qui encapsulent chacun une ou plusieurs fonctionnalités. Cela permet d’éviter de télécharger côté client l’intégralité du code JavaScript sur chaque page.

On ne parle pas forcément ici de Single Page Application, le code splitting peut concerner aussi bien une application React qu’un site propulsé par PHP et dynamisé avec jQuery.

Le lazy loading nécessite la mise en œuvre du code splitting. Cela consiste à charger dynamiquement coté client les modules JavaScript nécessaires au fur et à mesure des actions de l’utilisateur.

Ces techniques permettent d’améliorer le temps de chargement client et le temps avant que l’application devienne interactive (FMP ou First Meaningful Paint). En contexte mobile, où la bande passante est faible et la latence élevée, ce type d’optimisation peut s’avérer cruciale. De plus avec la généralisation d’HTTP2 et la parallélisation des requêtes, l’ancien modèle du “tout dans un fichier” devient caduque.

OK, mais c’est un cauchemar à maintenir.

Le chargement asynchrone de module est aujourd’hui dans le standard d’ECMASCript ! Pourquoi s’en priver :

// ES6
import("./myModule.js").then(myModule => myModule.hello());

// ES7 avec async / await
const myModule = await import("./myModule.js");
myModule.hello();

Diaboliquement simple. Alors oui on va être honnête, dans un projet réel le lazy loading et le code splitting sont difficiles à maintenir. Sans outils.

Heureusement les projets open-source les plus populaires pour le développement frontend ont aujourd’hui bien intégré ces techniques. Et l’approche par composants de la vaste majorité des frameworks en 2018 va nous permettre de préserver le confort développeur et vos neurones.

Cas concret : Webpack & react-loadable

Les fonctionnalités de webpack 3 & 4 permettent d’utiliser la syntaxe native d’ES6 dans vos projets frontend sans aucune configuration supplémentaire. Testez-le, ça fonctionne !

Dans le cadre d’un projet React, le paquet react-loadable est une petite merveille. Il permet de lazy-loader des composants à l’aide d’un HoC. Cela permet de découper l’application très facilement, à l’aide de HoC (High Order Component) d’une dizaine de lignes :

import Loadable from "react-loadable";
import Loading from "./my-loading-component";

const LoadableComponent = Loadable({
  loader: () => import("./my-component"),
  loading: Loading
});

export default class App extends React.Component {
  render() {
    return <LoadableComponent />;
  }
}

High Order Component : un HoC est un composant qui encapsule de la logique réutilisable. C’est une approche de développement couramment utilisée avec React.

Dans cet exemple (tiré de la démo react-loadable), my-component est importé à l’aide d’un import() asynchrone. De cette manière React va pouvoir créer un fichier supplémentaire constitué de my-component et des modules importés par ce dernier. De plus, react-loadable s’occupe pour vous de faire du lazy-loading. my-component ne sera chargé que si il est rendu !

Avec Webpack et react-loadable, nous avons pu facilement inclure des bibliothèques très lourdes telles que d3.js dans certaines parties de notre application sans alourdir le fichier principal. Lazy-loader un composant est transparent pour le développeur une fois la mise en place effectuée. Voici un extrait de log de build webpack pour illustrer le découpage opéré par webpack :

LoadableCreativeMediaConnected~LoadableDetailContent-b2422220f66a67338f96.css 3.22 KiB 0 [emitted]
LoadableCreativeMediaConnected~LoadableDetailContent.13222af644630af74736.js 11.1 KiB 0 [emitted]
LoadableLiveTvConnected-236e8ab15e421d1772cf.css 2.75 KiB 1 [emitted]
LoadableLiveTvConnected.13222af644630af74736.js 14.6 KiB 1 [emitted]
LoadableDetailContent-5add46afd6f8f4b079cb.css 43 KiB 2 [emitted]
LoadableDetailContent.13222af644630af74736.js 119 KiB 2 [emitted]
LoadableCreativeMediaConnected-60e4e7e1d8de178c7e60.css 17.8 KiB 3 [emitted]
LoadableCreativeMediaConnected.13222af644630af74736.js 61.5 KiB 3 [emitted]
LoadableSettingsConnected-84d92b4fe18d6b54bf3a.css 2.33 KiB 4 [emitted]
LoadableSettingsConnected.13222af644630af74736.js 9 KiB 4 [emitted]
LoadableUserProfileConnected-87d99271daea79c4ce46.css 6.2 KiB 5 [emitted]
LoadableUserProfileConnected.13222af644630af74736.js 625 KiB 5 [emitted] [big]
ServicePage-e2560b12f5c6ed8e9569.css 252 bytes 6 [emitted]
ServicePage.13222af644630af74736.js 717 bytes 6 [emitted]
MonitoringPage-f7547f0e1fefcf075b46.css 953 bytes 7 [emitted]
MonitoringPage.13222af644630af74736.js 70.8 KiB 7 [emitted]
main-25a6e91964228109d0bb.css 122 KiB 8 [emitted] main
main.13222af644630af74736.js 1.18 MiB 8 [emitted] [big] main

Quelques points à noter

  • Les fichiers CSS sont également découpés (avec webpack@4)
  • Webpack isole les fichiers qui sont nécessaires uniquement dans les parties lazy-loadées dans des fichiers spécifiques (e.g. LoadableCreativeMediaConnected~LoadableDetailContent)

Aller plus loin

Cache et mise en production

Il n’y a pas de précautions particulière à part la définition de l’attribut output.chunkFilename dans la configuration Webpack. Pensez à inclure [hash] dans le nom du fichier.

Server Side Rendering

Si vous faites du Server Side Rendering, vous voudrez probablement injecter dans la page les balises <script /> qui correspondent aux parties de l’application dont l’utilisateur aura besoin immédiatement.

Cette article n’abordera pas de stratégie particulière pour ce besoin, néanmoins react-loadable dispose de fonctionnalités dédiées au SSR. Nous l’utilisons sur notre projet actuel et cela fonctionne à merveille !

Avoir le contrôle sur le découpage des fichiers

Parfois (souvent) vous aurez besoin d’avoir un certain contrôle sur la façon dont Webpack découpe votre code. Heureusement la directive optimization.splitChunks existe et tout ce qui est abordé dans cet article (comme react-loadable) est entièrement compatible !