L’isomorphisme fait beaucoup parler de lui (nous l’avions déjà évoqué dans un billet précédent « Full-js » et « no-js » avec React + Redux), principalement pour résoudre des problématiques de référencement en améliorant le SEO (Search Engine Optimization). La page de votre site sera disponible par le client, mais aussi par le serveur, sans aucune différence ou presque. Tout le DOM (Document Object Model) sera disponible immédiatement et définitivement au retour de la requête. Pas de code JavaScript à interpréter, donc plus facilement et rapidement analysable par les robots de référencement les plus basiques. La diminution de l’analyse du DOM permettra aux robots d’engranger un maximum d’information avant l’expiration de leurs “timeout”.
Outre le gain de référencement, le temps d’affichage de la première page sera aussi diminué. Par une meilleure maîtrise du cache par exemple, ou encore en évitant un dialogue supplémentaire entre client et serveur.
Mon objectif ici est de sortir du théorique et d’offrir une solution concrète, minimaliste, fonctionnelle et donc plus facile à comprendre. Il n’y a pas une seule façon de faire, mais voici celle que je propose, qui a l’avantage de n’utiliser que le strict minimum au travers des bibliothèques populaires.
Installation
Note : Le dépôt Github pouvant évoluer, je commente ici le code du commit suivant : e3a6cb91061bd43babb81bd13bcbe778913c343a
Explication des modules utilisés :
- express permet de gérer le serveur ;
- history pour retrouver l’état des pages lors des “backhistory” ;
- react pour le développement orienté composant ;
- react-redux pour la gestion du store ;
- react-router & react-router-redux pour “router” les URLs vers les composants avec le bon état du store.
Je ne suis volontairement pas passé par webpack pour ne pas ajouter de nouvelle notion qui ralentirait l’apprentissage. Du coup, tout est centralisé dans mon package.json, ligne de commande y comprise.
Pour le dev, j’ai ajouté plusieurs modules :
- babel => pour transpiler entièrement le code ECMAscript 6 en ECMAscript 5
- nodemon => pour faire tourner le “démon” serveur
- parallelshell => pour faire tourner des processus en parallèle (cf les commandes “scripts” dans package.json)
- redux-devtools => La géniale debugbar à utiliser avec le plugin chrome “redux-devtools”
Architecture
J’ai choisi d’organiser mon code sous cette arborescence, mais rien d’obligatoire.
“public” est explicitement rendu accessible par mon serveur express à destination du client et où je mets à disposition images et fichier transpilé (ici “bundle.js”)
“html” est un simple fichier statique utilisé uniquement pour la première construction du DOM. React ne permettant pas de faire un composant rendant une page entière (balise html, head, body comprises).
“js” contient toutes mes sources javascript en ECMAScript 6
Le serveur
On retrouve un fichier exécuté côté serveur : js/server/server.js par la commande :
Il permet :
- comme son nom l’indique de faire le serveur. C’est-à-dire d’interpréter les URLs et d’appeler les bons composants ;
- instancier un nouveau “store” à chaque requête (notion abordée au chapitre Redux) ;
- construire le DOM pour le retourner au client.
Un peu de code…
Le serveur retourne du DOM pour y placer le code transpilé du client.js dans un fichier externe, ici public/bundle.js :
Les composants React seront interprétés pour mettre leur rendu (le DOM) tel quel.
Et enfin une balise script avec la déclaration d’une variable globale “initialState” contenant l’état du store soit un objet JSON.
Et c’est tout. Le client se débrouille avec ça.
Le client
Le client est du code exécuté à partir de ce fichier transpilé public/bundle.js par la commande :
Mais dont les sources se trouvent, principalement, dans le fichier js/client/client.js :
Le client lit la variable Store et met à jour le DOM virtuel (uniquement à la réception de la réponse de la requête initiale envoyée au serveur).
Tout changement d’URL côté client n’engendrera aucune autre requête au serveur. C’est un événement Redux qui déclenche une action qui met à jour le store. Les composants réagiront avec ce changement du store et rafraichiront le DOM.
Mutualisation du code
Le reste du code est partagé entre le serveur et le client contrairement à la plupart des applications web, ou l’on code trois fois la même action :
- Le code côté serveur au premier affichage de la page,
- l’envoie de la requête XHR (XMLHttpRequest) et le traitement de la réponse côté client,
- et réception puis l’émission de cette même requête côté serveur.
Dans mon exemple, on ne code qu’une seule fois un comportement.
Redux
Redux sert de “store”, soit un arbre de données (objet JSON) unique pour centraliser tout l’état de nos composants. Chaque composant va s’abonner à une branche de cet arbre et suivre ses évolutions. À chaque modification le composant est informé du changement et va réagir en conséquence.
Ici la classe Page1 écoute les changements de la clé counter du store et rafraîchit ensuite son rendu automatiquement.
Je ne m’éternise pas sur le sujet, car les tutoriels sont extrêmement bien faits. Je vous invite à visionner celui-là : https://egghead.io/lessons/javascript-redux-implementing-store-from-scratch
Aussi, pour expliquer comment le store est géré entre le serveur et le client. Le store est créé à chaque requête par le serveur et continue d’évoluer dans le client. Aucune utilité de reporter les modifications du store client sur le store du serveur.
Dans cette application, vous pouvez consulter les logs du serveur qui montrent l’évolution du store à chaque action. Chaque action incrémente ou décrémente une variable “counter” dans le store.
Les actions sur le store côté serveur :
Vous pouvez également constater dans la console client que la même variable “counter” est reprise et modifiée à nouveau.
Les actions sur le store côté client :
Les logs côté client :
La débug bar
Un mot sur la débug bar du plugin chrome. Vous pouvez visualiser l’état de votre “state”/”store” Redux, visualiser toutes les actions jouées sur le store (côté client uniquement) et… les rejouer ! C’est génial et très pratique pour débugger.
Déplacer la barre de progression vers la gauche et visualiser le store Redux :
À chaque mouvement de la barre de progression, vous remontez dans le temps sur les actions :
Et, vous avez la possibilité d’observer le différentiel des modifications du store qu’a engendré le passage d’une action à une autre action :