10 points par GN⁺ 2025-04-23 | 3 commentaires | Partager sur WhatsApp
  • Writing JavaScript Views the Hard Way : un article qui explique comment construire des vues en JavaScript pur, sans framework
  • Une approche impérative directe permet d’assurer les performances, la maintenabilité et la portabilité
  • Il sépare clairement les mises à jour d’état et les mises à jour du DOM, en suivant des conventions de nommage strictes et des modèles structurels selon chaque rôle
  • Cette approche est facile à déboguer, garantit la compatibilité avec tous les navigateurs et offre le grand avantage de 0 dependencies
  • Elle peut être difficile pour les débutants, mais apporte à l’apprentissage une compréhension profonde du fonctionnement réel des systèmes

Écrire des vues JavaScript à la « Hard Way »

De quoi s’agit-il ?

  • Cette approche est un modèle pour construire des vues uniquement en JavaScript, sans framework comme React, Vue, lit-html
  • Il s’agit du modèle de code lui-même, et non d’une bibliothèque ou d’un outil spécifique, ce qui évite les problèmes de code spaghetti
  • En utilisant une approche impérative directe, elle réduit les abstractions et améliore l’intuitivité

Avantages par rapport aux frameworks

  • Performances : grâce au code impératif, elle fonctionne sans calculs inutiles et convient aussi bien aux hot paths qu’aux cold paths
  • 0 dependencies : pas de contraintes liées aux mises à jour de bibliothèques ou aux problèmes de compatibilité
  • Portabilité : le code écrit peut être porté vers n’importe quel framework
  • Maintenabilité : la structure claire en sections et les conventions de nommage facilitent la localisation du code
  • Support navigateur : compatible avec la plupart des navigateurs à partir d’IE9, et même avec IE6 moyennant quelques adaptations
  • Facilité de débogage : fournit des stack traces peu profondes sans couche intermédiaire
  • Structure fonctionnelle : sans être immutable, l’ensemble des composants repose sur des fonctions

Explication de la structure

Structure générale

  • Composée de templateclone() → fonction init()
  • La fonction init() crée une instance de vue comprenant variables d’état, références DOM, fonctions de mise à jour, event listeners, etc.

Exemple de structure de code (Hello World)

const template = document.createElement('template');  
template.innerHTML = `<div>Hello <span id="name">world</span>!</div>`;  
  
function clone() {  
  return document.importNode(template.content, true);  
}  
  
function init() {  
  let frag = clone();  
  let nameNode = frag.querySelector('#name');  
  let name;  
  
  function setNameNode(value) {  
    nameNode.textContent = value;  
  }  
  
  function setName(value) {  
    if(name !== value) {  
      name = value;  
      setNameNode(value);  
    }  
  }  
  
  function update(data = {}) {  
    if(data.name) setName(data.name);  
    return frag;  
  }  
  
  return update;  
}  

Composition interne de la fonction init()

1. Variables DOM

  • frag est le fragment de template généré par clone()
  • Les éléments internes sont référencés avec querySelector(), et les noms de variables suivent la forme fooNode

2. Vues DOM

  • Partie qui inclut d’autres vues (sous-vues réutilisables)
  • Exemple :
let updateChildView = childView();  
  • Les fonctions de mise à jour de vue sont nommées sous la forme updateFoo

3. Variables d’état

  • Valeurs de données susceptibles d’être modifiées dans la vue
  • Pour rendre les mises à jour du DOM efficaces, on compare avec la valeur courante et on ne modifie le DOM qu’en cas de besoin

4. Fonctions de mise à jour du DOM

  • Utilisées pour modifier l’état des éléments du DOM
  • Exemple :
function setNameNode(value) {  
  nameNode.textContent = value;  
}  
  • Les manipulations du DOM doivent impérativement être effectuées uniquement dans ces fonctions

5. Fonctions de mise à jour d’état

  • Elles incluent la logique de changement d’état et sa répercussion dans le DOM
  • Les valeurs inchangées sont ignorées afin d’éviter les modifications inutiles du DOM
  • Exemple :
function setName(value) {  
  if(name !== value) {  
    name = value;  
    setNameNode(value);  
  }  
}  

template et fonction clone()

template

  • Création d’une structure HTML statique avec l’élément <template>
  • Il n’est pas inséré directement dans le DOM ; une copie est créée via clone

clone()

  • Duplication via document.importNode(template.content, true)
  • Si nécessaire, il est possible de retourner l’élément racine avec .firstElementChild

Mode d’interaction

Flux de données parent → enfant

  • Le parent appelle init() de l’enfant pour obtenir une fonction de mise à jour, puis l’appelle sous la forme update({ name: 'foo' })

Propagation des données par événements

  • Suit fondamentalement le modèle props down, events up
  • Les vues enfants communiquent en dispatchant des événements vers le niveau supérieur

Comparaison avec React

  • constructor() (React)init() (Hard Way)
    • Gère l’initialisation du composant
  • render() (React)update(data) (Hard Way)
    • Assure le rafraîchissement de l’affichage et la mise à jour de l’UI
  • this.setState() (React)setX(value) (Hard Way)
    • Remplacé par une méthode de définition directe de l’état
  • props (React)valeurs transmises via update(data) (Hard Way)
    • Manière de traiter les données transmises par le composant parent
  • JSX / Virtual DOM (React)template HTML + API DOM (Hard Way)
    • Au lieu d’une UI déclarative, on utilise des manipulations manuelles du DOM et des templates

Conclusion

  • Cette approche présente une barrière à l’entrée plus élevée que les frameworks habituels, mais offre les points forts suivants :
    • Optimisation des performances
    • Contrôle total
    • Compréhension approfondie par l’apprentissage
  • Grâce à la séparation des fonctions par rôle et aux conventions de nommage, il est possible de construire une UI maintenable sans framework

Compatibilité

  • Les exemples récents utilisent des API destinées aux navigateurs modernes, mais le support peut être étendu jusqu’à IE9 et au-delà via des remplacements basés sur des fonctions
  • En transmettant des fonctions via les props au lieu d’utiliser des événements, l’approche peut même être étendue jusqu’à IE6

3 commentaires

 
wfedev 2025-04-24

Au final, avec des composants web...

 
ahwjdekf 2025-04-23

Félicitations. Un autre framework js vient de naître.

 
GN⁺ 2025-04-23
Avis Hacker News
  • Pour beaucoup de développeurs JS, ce sera peut-être hérétique, mais je pense que les variables de "state" sont un anti-pattern

    • En utilisant des Web Components, au lieu d'ajouter des variables de state pour des types de variables « plats », j'utilise value/textContent/checked des éléments DOM, etc., comme unique source de vérité
    • J'ajoute des setters et des getters si nécessaire
    • Avec moins de code, beaucoup de choses fonctionnent naturellement comme il faut
    • Avec les Web Components, le template HTML adjacent à l'objet est séparé, ce qui donne une granularité façon fusilli ou macaroni plutôt qu'un code spaghetti
  • La documentation explique que cette approche est très maintenable, mais je ne suis pas d'accord

    • Le design pattern repose uniquement sur des conventions
    • Quand plusieurs développeurs travaillent en même temps sur une application complexe, il y a de fortes chances qu'au moins l'un d'eux s'écarte de ces conventions
    • Les frameworks UI orientés classes comme UIKit sur iOS imposent à tous les développeurs d'utiliser un ensemble standard d'API, ce qui rend le code prévisible et facile à maintenir
  • J'écris récemment des applications en TypeScript "vanilla" pur avec vite, et je remets de plus en plus en question les "meilleures" pratiques du frontend

    • Impossible de conclure sur la scalabilité, mais en termes de performances, il y a de gros avantages
    • C'est amusant, on apprend beaucoup, le débogage est simple et l'architecture est facile à comprendre
    • Le templating est ce qui me manque le plus
  • Cette approche me rappelle l'ancienne bibliothèque backbone js

    • Il existe aussi un dépôt GitHub avec un exemple de pattern MVC adapté à la plateforme web
  • J'ai récemment imaginé quelque chose de similaire, mais sans utiliser d'éléments template

    • J'utilise des fonctions et des template literals pour renvoyer des chaînes, que j'insère dans le innerHTML d'un élément existant ou dans un nouvel élément div
    • Les fonctions s'imbriquent et il est difficile de les organiser de manière raisonnable
  • Ce code ressemble exactement au code de mise à jour manuelle que les bibliothèques de vues réactives cherchent à remplacer

  • Cela fait environ 20 ans que je programme, mais je ne me suis jamais habitué aux frameworks frontend

    • Je suis plus fort côté backend, donc je pense que les interactions liées à la sécurité devraient passer par le serveur
    • Je vois JS comme un moyen d'ajouter des fonctionnalités côté client sur une base solide en HTML et CSS
  • J'utilise un helper similaire à React.createElement

    • Il existe un exemple fonctionnel d'un tableau de bord de serveur simulé
  • Je travaille sur deja-vu.junglecoder.com pour tenter de construire une boîte à outils JS pour des outils basés sur HTML

    • Je n'ai pas encore résolu correctement le binding de données réactif/bidirectionnel, mais grab/patch est plutôt pas mal
    • La manière d'utiliser les templates permet de déplacer très facilement certaines parties d'un template
  • Dans mon premier emploi officiel après l'université, j'ai travaillé sur une version web d'un logiciel Delphi

    • L'équipe en était déjà à la troisième réécriture du frontend et il fallait changer de framework
    • J'ai soutenu qu'il fallait écrire notre propre framework, mais l'équipe n'a pas aimé ma proposition
    • Je suis ensuite parti après avoir reçu une meilleure offre dans une autre entreprise
    • J'ai ensuite essayé un autre framework appelé tiny.js, que j'utilise pour des projets personnels