Créer un chargeur de squelette pour Discourse

Bonjour :waving_hand:

L’idée de base

L’objectif était de créer un chargeur squelette généré à partir de l’interface utilisateur réelle de Discourse, plutôt que de s’appuyer sur un modèle de squelette codé en dur.

L’outil de construction permet à un administrateur de sélectionner des éléments réels sur la page et de les transformer en régions de squelette.

Par exemple :

.title
.avatar
.topic-excerpt
.btn
.category-breadcrumb

Le composant utilise ensuite ces sélecteurs pour générer le squelette à l’exécution.

Aperçu du squelette

La partie intéressante est que l’administrateur n’a pas à écrire les sélecteurs manuellement. L’outil analyse l’élément sélectionné et génère plusieurs sélecteurs candidats.


Générer des sélecteurs utiles s’est avéré plus difficile que prévu

L’un des premiers problèmes auxquels je suis confronté était la génération de sélecteurs.

Une implémentation naïve peut facilement produire quelque chose comme :

.container.list-container.--topic-list .row.full-width .contents ...

Techniquement valide, mais beaucoup trop spécifique pour une configuration de squelette réutilisable.

Cela peut devenir encore pire avec les icônes, où le sélecteur généré peut inclure des détails d’implémentation tels que des classes liées aux SVG.

Ce que je voulais vraiment, c’était quelque chose de plus proche de :

.badge-category__name

ou :

.badge-category__wrapper .d-icon

plutôt qu’un sélecteur décrivant tout le chemin du DOM.

L’outil génère donc maintenant plusieurs candidats et les évalue en fonction de critères tels que :

  • la profondeur du sélecteur
  • le nombre de classes
  • les correspondances répétées
  • les classes liées à l’état
  • les classes techniques SVG/icône
  • si le sélecteur correspond toujours à l’élément sélectionné

Le résultat est une liste de sélecteurs recommandés que l’administrateur peut choisir ou modifier manuellement.


Éléments masqués

Il y a aussi un sélecteur séparé pour les éléments qui doivent simplement disparaître pendant l’affichage du squelette.

Par exemple :

.alert.alert-info

Cela s’est avéré utile pour des éléments tels que les bannières d’annonce ou les avis temporaires qui existent pendant la construction/test mais qui ne doivent pas affecter la mise en page du squelette.

Un problème intéressant ici est que masquer un élément ne doit pas laisser d’espace vide derrière.

Les éléments exclus ne sont donc pas traités comme une simple liste display: none - le calcul de la géométrie doit également comprendre que l’élément ne fait pas partie de la mise en page finale.

Aperçu du squelette


Navigation

La plus grande difficulté a probablement été la navigation.

Le comportement souhaité était le suivant :

clic

  ↓

afficher le squelette immédiatement

  ↓

Discourse change de route

  ↓

le DOM de destination apparaît

  ↓

masquer le squelette

La solution tentante était de s’immiscer en profondeur dans le cycle de vie de la navigation et d’attendre que le DOM soit complètement stabilisé.

Cela s’est avéré être la mauvaise approche.

À un moment donné, le squelette pouvait rester visible pendant plusieurs secondes après que le contenu réel était déjà là.

La leçon était simple :

Le squelette ne devrait pas devenir une porte d’entrée pour la préparation du DOM.

Dès que la destination contient suffisamment de contenu réel pour prendre le relais, le squelette devrait se retirer.

Cela a fait une énorme différence dans la vitesse perçue de la navigation.


Vues (Viewports)

Discourse dispose déjà d’un système de vues responsive, le composant utilise donc désormais la même abstraction de points de rupture :

xs
sm
md
lg
xl
2xl

La configuration du squelette peut en outre être regroupée comme suit :

mobile → xs / sm
tablette → md
ordinateur → lg / xl / 2xl
tous → tout

Cela signifie que le composant n’a pas besoin de connaître les valeurs de pixels réelles.

Si Discourse modifie les valeurs des points de rupture, le composant squelette n’a pas à être réécrit autour de nouveaux nombres codés en dur.


Mise en cache de la géométrie

Les sélecteurs nous disent ce qui doit être rendu, mais ils ne nous disent pas exactement les formes du squelette doivent apparaître.

Pour cela, j’ai ajouté la capture de géométrie.

L’outil peut mesurer les régions réellement rendues et stocker leur géométrie afin que le chargeur puisse afficher un squelette de destination immédiatement lors de la navigation SPA.

Il y a aussi une option de verrouillage de géométrie explicite pour les cas où je ne veux pas que les visites ultérieures modifient continuellement la géométrie de référence.

C’était une autre distinction importante :

définition du sélecteur et géométrie rendue sont deux choses différentes.


Brouillons

Une autre chose qui est devenue nécessaire était l’état de brouillon.

Je ne voulais pas ce flux de travail :

ouvrir l'outil

→ passer 10 minutes à le configurer

→ fermer l'outil

→ tout est perdu

L’outil conserve donc un brouillon en cours séparément du réglage de thème réel.

Le brouillon est limité à la combinaison page/route/vue, de sorte que, par exemple :

liste-des-sujets / lg
liste-des-sujets / md
liste-des-sujets / xs

ne s’écrasent pas accidentellement les uns les autres.

Fermer l’outil ne détruit pas le travail effectué.


Annuler

Une fois que l’outil est devenu plus interactif, un système d’annulation est devenu presque inévitable.

L’outil stocke des instantanés de son état de configuration :

{
  "regions": \[\],
  "excludes": \[\]
}

plutôt que d’essayer de maintenir un historique des opérations DOM.

Cela rend le système d’annulation beaucoup plus facile à comprendre et le garde également indépendant du DOM de la page réelle.


Ce projet est en développement actif. J’espère qu’il sera bientôt prêt pour un composant de thème ! :slightly_smiling_face:

3 « J'aime »