Mettre React KO avec knockoutjs
Dans l’écosystème javascript, lorsque l’on souhaite réaliser une application web élaborée et maintenable, on se tourne généralement vers les gros framework Front très connus comme React, Vue ou Angular. Ils sont très complets, biens conçus, et maintenus par une grosse communauté.
Que se passerait-il si on essayait aujourd’hui de réaliser un site web avec un framework qui n’est plus maintenu mais tout léger ? Va-t-on se retrouver limité, bloqué, ralenti, perdu ?
Dans de nombreuses situations, on n’utilise qu’un faible pourcentage des capacités des framework Front, et finalement on embarque une solution beaucoup plus lourde qu’elle ne devrait pour un besoin basique.
J’ai donc voulu me lancer un défi : réaliser un site web en expérimentant une alternative légère, sans prétention, et théoriquement dépassée : knockoutjs
C’est quoi knockoutjs ?
Knockoutjs est un framework javascript open source sorti en 2010, développé par Steve Sanderson, un employé de Microsoft. Il n’est plus maintenu depuis 2019 mais n’a pas pour autant été déprécié officiellement. La communauté l’a juste tranquillement délaissé au profit d’autres solutions plus modernes.
C’est là le plus gros risque dans mon projet : être bloqué par un bug non corrigé ou une incompatibilité future.
Coté technique, il implémente un pattern Modèle–Vue–VueModèle avec une syntaxe ressemblant à Angular ou Vue par certain points.
Point fort : Leur site web intègre un tutoriel interactif très bien fait pour apprendre à l’utiliser, ce qui n’est pas toujours le cas.
Pour le découvrir plus en détail : tutoriel interactif.
En tant que développeur React au quotidien, je pense que la prise en main devrait donc être rapide.
Est-ce que c’est utilisé aujourd’hui ?
Je n’ai pas trouvé beaucoup d’informations sur les sites existants qui implémenteraient aujourd’hui knockoutjs. Mais il existe au moins un projet d’envergure intégralement développé sur ce framework : pokeclicker
Si vous ne connaissez pas, c’est un jeu de type “clicker” (comme cookie clicker) basé sur l’univers de pokémon. Il est toujours maintenu aujourd’hui, avec des mises à jour assez régulières.
Ce projet m’a rassuré sur la viabilité de knockoutjs pour un projet conséquent. Le jeu est extrêmement riche (pour un clicker, oui c’est surprenant), avec de nombreuses interactions variées, des notifications, et même la possibilité de customiser l’interface utilisateur. J’ai été très impressionné de l’étendue des possibilités mises en oeuvre ici pour un framework aussi ancien et léger.

Et pour couronner le tout, le code source est disponible sur Github, Ce sera une excellente source d’exemples d’implémentation pour mon projet.
Comment fonctionne ce framework ?
Knockoutjs est basé sur une syntaxe utilisant des string attribute comme on va en trouver dans Angular ou Vue.
<video data-bind="click: (data, event) => $root.goToImage(data, $parent, event), attr:{ src: `/images/${$parent.folder}/${$data}`, alt: `Vidéo de réponse pour ${$parent.title}`, class: $root.isImageActive($parent.title, $data) ? 'bigImage blured' : 'blured' }" height="400" style="cursor: pointer"/>Pour “activer” le moteur de knockoutjs sur une page, il faut appeler la fonction ko.applyBindings(new AppViewModel()) en lui passant une classe AppViewModel qui contiendra les données et fonctions à lier à la vue.
import ko from "knockout";
class AppViewModel { // Data constructor() { //Dans le constructeur je défini les données spécifiques utilisées par mon interface //Ici "duels" vient d'un fichier importé contenant des données statiques, que je charge dans l'instance de knockoutjs) this.duels = duels.sort((a, b) => (a.date < b.date ? 1 : -1)); this.chosenImage = ko.observable(); //...
// Je défini également dans le constructeur les méthodes pour manipuler ces données this.goToImage = (image: string, duel: Duel, event) => { if (this.chosenImage() !== image) { //... } }; }
// Dans la classe mais hors du constructeur, je peux définir des fonctions utilitaires ne dépendant pas de l'instance isImage(filename: string) { return !!filename.match(/\.(jpg|jpeg|png|gif)$/i); };}Le principe des variables observable()permet de gérer de façon assez transparente le double-binding (une donnée qui change modifie la vue, une action dans la vue modifie la donnée).
Knockoutjs permet des re-rendus localisés au sein d’une page web, comme le fait par exemple React avec son DOM virtuel.
Faire mon site avec knockoutjs
Pour tester ce framework, voici un petit projet très simple avec un objectif précis.
J’ai un jeu idiot avec des amis, dont le principe est assez simple à décrire : Une personne prend une photo aléatoire et la partage. Ensuite les autres tentent d’y répondre rapidement en envoyant une nouvelle photo qui ressemblerait le plus possible à la photo initiale. Les photos se retrouvant finalement en vrac sur un groupe de discussion partagé, je veux réaliser un site pour diffuser entre nous les “duels” de photos de façon simple.
Un projet très léger donc : afficher une liste d’informations avec des images, des interactions pour l’affichage, quelques inputs et c’est à peu près tout.
Il correspond exactement à l’idée de départ, mais en profitant tout de même des avantages en terme de code qu’auraient fourni un React ou un Vue.

Ici on voit la liste des duels de photos, chacun ayant un thème et une date. Le duel “Pince” est actuellement déployé. Sa photo source est affichée clairement, la photo réponse est floutée et il faut la survoler pour la révéler.
Le bilan est très positif.
Le résultat est disponible ici :Photo-Duel
Le code source est disponible ici : Github - Photo-Duel
Le site a été réalisé en quelques heures. L’expérience de développement était très agréable, et intuitive.
Typescript est parfaitement supporté, ce qui m’a permis d’avoir un code bien typé et maintenable.

Le site final ne pèse quasiment rien : moins de 100ko, en plus du poids des images, qui sont chargées à la volée.
Par ailleurs, nul besoin de configurer un bundler complexe comme Webpack ou Vite, puisque knockoutjs peut être importé et traité directement dans le navigateur.


J’ai tout de même rencontré quelques particularités, rien d’insurmontable, mais qui méritent d’être évoquées.
Quelques points sensibles à prendre en compte
Le routing
Point crucial : knockoutjs n’intègre aucun routeur.
J’ai donc dû intégrer un second outil à mon projet pour gérer la navigation, en cherchant à garder la même contrainte :
Choisir un outil très léger et pas forcément très maintenu.
J’ai choisi pour ça : Navigo, un routeur minimaliste qui fait le job sans fioritures.
L’implémentation est facile à prendre en main, et fonctionne parfaitement avec knockoutjs. Ça n’a pas été un frein lors de la réalisation du projet.
import Navigo from 'navigo';
/** **/let router = new Navigo("/", { hash: true });/** **/router.on("/:title/:image", (params) => { const { title, image } = params?.data as ImageRouteParams; this.chosenDuel(duels.find((duel) => duel.title === title)); this.chosenImage(image);});Les string attributes
Un élément gênant vient des attributs html dynamiques sous forme de string. Pour un développeur React, c’est souvent une pratique inhabituelle et plus risquée, puisque le code de ces attributs n’est pas vérifiable au compile time.
Pour un développeur Vue ou Angular, c’est courant, et ne pose pas de problème particulier. Toutefois, Vue et Angular disposent des outils nécessaires pour que l’interface de développement soit capable d’appliquer une coloration syntaxique rendant ces chaînes de caractères parfaitement lisibles.
Pour knockoutjs, je n’ai pas réussi a trouver une solution pour avoir une coloration syntaxique dans mon IDE, et j’ai donc dû travailler tout le long en manipulant des longues chaînes monochromes assez indigestes.
<li data-bind="click: $root.goToDuel, attr: { class: $root.isDuelActive($data) ? 'duelBloc openDuelBloc' : 'duelBloc'}">Binding contexts
Un détail très spécifique et inattendu qui m’a posé problème est le binding de sous-objets.
Par exemple, si j’ai un objet duel avec une propriété title, je ne peux pas faire un binding direct sur duel.title.
Pour manipuler des sous-propriétés au sein de la vue, et dans beaucoup d’autres cas, une solution assez puissante est de passer par les binding contexts. Ce sont des variables spéciales accessibles dans la vue, qui permettent de remonter dans la hiérarchie des objets, ou d’accèder à des points d’entrée spécifiques.
Exemples :
- $root : Le ViewModel racine accessible depuis n’importe quel élément
- $parent : Le parent direct de l’élément courant
- $data : L’objet courant
<li data-bind="if: $root.isVideo($data.filename)" > //....</li>Ils fournissent un accès rapide à des objects situés à différents niveaux du modèle, ainsi qu’un moyen simple et efficace de référencer des sous-objets du contexte courant.
Des composants conditionnés toujours dans le DOM
Il faut être prudent avec la gestion des binding conditionnés.
<div data-bind="if: someCondition" >Ici someCondition ne retire pas l’élément du DOM si la condition est fausse, il enlève seulement le contenu de cet élément.
C’est un phénomène qui peut surprendre lorsqu’on code habituellement en React, où un composant conditionné n’est pas du tout rendu dans le DOM si la condition est fausse.
{ someCondition && <MyComponent /> }On peut donc avoir des mauvaises surprises car le composant étant toujours présent, les classes css qui s’appliquent dessus le sont aussi.
<div data-bind="if: $root.isDuelActive($data)" class="imageBloc"> <img ...> </div>Dans l’exemple ci dessus, si jamais la classes imageBloc définit une taille fixe et une couleur bleue, alors on va rendre à l’écran un bloc bleu dans tout les cas, même si la condition de binding n’est pas remplie.
Une fois ce phénomène identifié, j’ai simplement eu à adapter l’application de mes règles css en conséquence.
Conclusion
De mon point de vue, la conclusion est sans appel : un vieux framework très léger comme knockoutjs peut parfaitement convenir pour réaliser un site web simple, avec une expérience de développement agréable. Knockout.js, en particulier, a la chance d’avoir une documentation et un tutoriel très bien faits, ce qui facilite grandement la prise en main.
Il a des particularité a connaître, et auxquels il faut juste s’adapter, comme le besoin d’ajouter un routeur externe, ou la gestion des bindings contexts pour pointer des sous-objets.
Il a un avantage indéniable : sa légèreté et sa simplicité (pas de bundler nécessaire).
Et il a ses risques, dont on doit être conscient, comme le fait qu’il ne soit plus maintenu officiellement. Le fait qu’il soit encore utilisable aujourd’hui ne garanti en rien qu’une évolution future du web ne va pas un jour le rendre totalement inapplicable, là ou un framework bien maintenu saura probablement s’adapter.
Au final, pour ceux qui en douteraient : Non, on ne mettra pas React KO en le remplaçant par knockoutjs. Les deux outils n’ont pas du tout la même envergure ni les mêmes ambitions. Le premier est un mastodonte moderne parfaitement maintenu et évoluant régulièrement, le second un petit framework léger et ancien.
Mais je pense qu’il ne faut pas hésiter à explorer des alternatives légères et moins connues que les traditionnels React, Vue ou Angular, surtout pour des projets simples. Cela permet de réduire la taille des applications web, et d’élargir nos connaissances et nos pratiques en découvrant des outils différents. Et l’écosystème Javascript en regorge !
Authors
Full-stack web developer at marmelab, Julien follows a customer-centric approach and likes to dig deep into hard problems. He also plugs Legos to computers. He doesn't know who is Irene...