logo

La programmation réactive

Le tag Potions est écrit en programmation réactive et se fonde sur une librairie interne qui, elle-même, se fonde sur la librairie RxJS.
Ainsi l’ensemble des actions de stockage, d’analyse, de modifications d’interface et d’envoi d’événements sont déclarées sous forme de souscriptions à des observables.
Ce paradigme de programmation est complexe au premier abord mais présente de nombreux avantages :
  • l’aspect “déclaratif” nécessite de partir de l’objectif et donc limite les effets de bords indésirables et autres bugs.
  • les “observables” manipulés en RxJS permettent la gestion précise des opérations asynchrones
  • l’intégralité du code est constituée de fonctions pures facilement compréhensibles, modifiables et testables.

Exemple simple

Voici une version simplifiée du code pour intégrer une bannière de recommandations à une pop-up d’ajout panier.
javascript
fromQuerySelectorAll$("#add-to-cart-pop-up", document).pipe( mergeMap((popup : HTMLElement) => { const productId = extractProductIdFromPopUp(popup); const recommendationUrl = buildRecommendationUrl(productId); return fromFetch(recommendationUrl).pipe( tap((response : Response) => { const recommendationElement = document.createElement("div"); recommendationElement.innerHTML = response.text(); popup.appendChild(recommendationElement); }) ) }) ).subscribe()
On constate que le code est “pyramidal”. Il tient en quelques lignes (au sommet de la pyramide) et se compose principalement de fonctions importées.
Analysons sa structure :
  • fromQuerySelectorAll est une fonction utilitaire qui prend un sélecteur en argument et retourne un observable d’éléments répondant à ce sélecteur. Cet observable retourne directement les éléments déjà présents sur la page (comme un querySelectorAll classique) mais également les éléments ajoutés par la suite (!!!) qui répondent à ce sélecteur. Cette fonction utilitaire utilise l’une des multiples “sources” de la librairie Potions qui encapsulent des API du navigateur (ici le MutationObserver) dans des objets observables.
  • mergeMap est un opérateur de la librairie RxJS . Dans notre cas, il prend des éléments HTML, les pop-ups panier, et retourne un observable qui extrait le productId de l’élément, va chercher les recommandations de façon asynchrone puis les ajoute à la fameuse popup.
Image without caption
  • extractProductIdFromPopUp, buildRecommendationUrl sont des fonctions pures très simples dont on peut facilement imaginer le contenu
  • fromFetch est un opérateur de RxJS qui encapsule la méthode fetch du navigateur pour retourner la réponse dans un observable. Notre exemple ne traite pas les erreurs mais le code de Potions Tag oui 🙂. La réponse attendue du serveur Potions est ici le code HTML de la bannière.
  • Enfin tap est un opérateur de la librairie RxJS qui permet de mettre en lumière les effets de bords, ici l’ajout de l’élément de la bannière de recommendations à l’élément de la pop-up.

La structure du code

Le code du tag Potions est structuré de la façon suivante.
javascript
sourceXXX.pipe( operationXXX, tap(storeIn(XXX)) ).subscribe() store.XXX.pipe( tap(renderXXX(elementXXX)) ).susbscribe()
graph BT Store --> id(Reactive elements) Sources --Subscriptions--> Store

Store

Le code de l’exemple simple précédent tient en une souscription, une forme de “ligne droite” entre :
  • l’événement “le document contient une pop-up”
  • et l’effet “ajoute un élément à la pop-up”
Dans le code du Potions Tag nous avons complexifié ce fonctionnement pour ajouter un concept clé, le store. Le store est un observable contenant l’état de notre application.
Ainsi l’intégration de la bannière de recommandation est dans le Potions Tag, le fruit de deux souscriptions :
lors de l’événement “le document contient une pop-up” effectue…
lors de l’apparition d’éléments réactifs “potions-element” effectue…
Le store a plusieurs avantages :
  • permettre de déclarer en HTML des liens entre un élément et un contenu réactif, éventuellement en spécifiant un template, lui aussi contenu dans le store.
javascript
<div class="potions-element" data-content="XXX" data-template="YYY"></div>
  • synchroniser avec indexedDB des propriétés du store et donc permettre de persister des états d’une session à l’autre
  • disposer d’une couche de données facilement accessible dans l’ensemble de l’application à la manière d’un dataLayer.
Le store est un objet complexe et assez fascinant : un proxy d’un BehaviorSubject
Il s’assure que :
  • l’ensemble de ses propriétés sont eux même des stores
  • l’appel d’une propriété non existante renvoie un store vide (et pas undefined)
  • que le store souscrive à ses propriétés pour être mis à jour à chaque changement de ses stores enfants.
Nous pensons chez Potions qu’il est sans doute la glue qui manque à javascript. Rien que ça.
En aperçu, il permet de “lier” des objets js entre eux :
javascript
import {store} from 'Potions' store.a = store.b.c store.b = {c:1} console.log(store.a.value) // logs 1

Sources

Les navigateurs disposent de nombreuses API susceptibles de fournir de la donnée via des callbacks, des retours de fonctions, des listeners, des promesses…
Potions a construit toute une série de sources qui permettent d’obtenir ces données sous forme de subject RxJS.
Voici une liste non exhaustive
  • fetch$$ : observe tous les appels et les réponses de la méthode window.fetch
  • mutations$$ : observe tous les appels à une callback d’un mutationObserver
  • dataLayer$$ : observe toutes les données qui sont poussées dans le dataLayer
  • events$$ : observe tous les événements (click, focus, keyup…)
  • history$$ : observe tous les changements d’URL…
De cette manière Potions est capable d’observer tout ce qui se passe sur une page avec un impact minimal sur les performances du site.

Subscriptions

Les souscriptions (subscriptions) constituent le coeur du réacteur et représentent la logique des expériences Potions.
Une expérience se résume bien souvent à une souscription.
Nous lançons par ailleurs des souscriptions communes à l’ensemble de nos clients comme la souscription en charge de souscrire les reactive elements aux données et templates dont ils dépendent.

Reactive elements

Les reactive elements ne sont que des coquilles vides qui ne sont remplies qu’une fois les données et les templates dont ils dépendent existent.

Les expériences

Chacune des expériences Potions peut être synthétisée en une souscription à un observable, lui même composé de plusieurs observables, eux mêmes composés… et ainsi de suite.
Nous tâchons ici de vous fournir les grandes lignes du code pour quelques expériences.

La reprise de session

Le ciblage comportemental

La wishlist et les derniers produits consultés

La recommandation de produits