Site icon Blog Zenika

Webapp isomorphique avec Webpack, React, Router & Redux

L’isomorphisme ou JavaScript universal c’est cool ! Cela permet de rendre une Single Page Application JavaScript aussi rapide au premier chargement qu’un site web standard. De plus, cela permet l’indexation du contenu par un moteur de recherche et on sait à quel point c’est important.
Voici comment le mettre en oeuvre concrètement dans le contexte d’une application React & Redux.

L’isomorphisme, ce n’est pas “juste” une fonctionnalité de plus pour les framework JavaScript modernes. C’est ce qui leur permet aujourd’hui de sortir du domaine unique de l’application et permet d’adresser n’importe quel type de site, notamment les sites institutionnels dont le SEO est un enjeu majeur.
Côté framework, Angular 1 ne permet pas cette fonctionnalité, Angular 2 le propose via le projet encore jeune Angular Universal et React le propose déjà depuis maintenant quelque temps.

Principe général

Pour faire de l’isomorphisme avec React, il faut réaliser plusieurs étapes.

Une fois toutes ces étapes réalisées. Vous avez l’url demandée par le client pré-générée côté serveur avec le même code que votre application cliente et la page s’affiche immédiatement. Ensuite, votre code est chargé par le navigateur, React est initialisé au même état qui a été préparé puis lancé sur le même endroit du DOM. React va alors reprendre là où il en était et être capable de continuer côté client comme si de rien n’était.

Mise en oeuvre

Adaptation de l’application

Dans le contexte de cette application React / Router / Redux / Webpack, voici les adaptations qu’il faut mettre en oeuvre.

new webpack.DefinePlugin({
  'process.env': {BROWSER: JSON.stringify(true)}
})
if (process.env.BROWSER) {
  require('../styles/component.scss');
}
componentDidMount() {
  if (_.isEmpty(this.props.trainings)) {
    this.props.fetchTrainingsList();
  }
}
Catalogue.needs = [fetchTrainingsList];

Middleware Express

Voici les principales étapes de génération du rendu côté serveur dans un middleware Express.

match({routes, location: req.url}, (error, redirectLocation, renderProps) => {
  //...
}
const store = configureStore();
// fetchComponentData parcourt tous les needs des composants en arguments
// dispatch les actions et rend une promesse résolue quand ils sont tous finis
fetchComponentData(store.dispatch, renderProps.components, renderProps.params)
  .then(() => {
    //...
  });
const html = renderToString(
  <Provider store={store}>
    <RouterContext {...renderProps}/>
  </Provider>
);
const initialState = store.getState();
// renderFullPage insère l'HTML et le state dans le template du index.html
res.send(renderFullPage(html, initialState));

Contraintes dans les développements

Il y a des contraintes à prendre en compte pour le développement d’une application React qui sera isomorphique. On peut tout de même dire qu’elles sont tout à fait raisonnables par rapport à la complexité de l’opération et les gains qu’elle peut apporter.

Au niveau du code

Les contraintes pour le développeur ont été abordées au cours des explications précédentes mais pour les résumer:

Au niveau tooling

Lorsque vous lancerez votre application avec le serveur de prérendu, Webpack ne sera pas mis en oeuvre et vous n’aurez aucune fonctionnalité de recompilation à chaud de votre code.
Si vous accompagnez votre serveur de la fonctionnalité watch de Webpack, vous aurez une situation un peu délicate. En effet, il va recompiler les sources automatiquement pour le client mais côté serveur, les sources sont chargées dans Node au démarrage, il n’y a pas de rechargement à chaud. S’il y a des développements de fait pendant que le serveur est démarré, il y aura un décalage entre le code client et le code serveur. Il faut redémarrer le serveur pour prendre les mise à jour en compte.
Il faudra encore ajouter nodemon pour que le serveur redémarre automatiquement sur tout changement de source pour bénéficier du rechargement à chaud des sources côté serveur et côté client.

Auteur/Autrice

Quitter la version mobile