Retour aux articles

L'agence

WanadevStudio

Les frameworks front, tous les mêmes !

C'est une phrase que j'ai osé sortir un jour dans la salle de pause de Wanadev. Je ne sais plus exactement avec quel collègue je discutais, j’essayais de le rassurer, il possédait déjà une certaine expérience avec React et allait devoir, en arrivant sur le projet sur lequel je travaille, se mettre à Vue.
Il a malheureusement fallu qu'un autre collègue de passage nous entende pour ne pas trouver la conversation inintéressante et suggérer que j'en fasse un petit talk pour nos réu du lundi. Et, de fil en aiguille, me voilà en train d'en faire un article de blog. Comme quoi, note pour moi-même, il faut toujours se méfier des discussions dans les salles de pause.

Bon, passons au vif du sujet, qu'on en finisse. « Les frameworks front », disais-je, « tous les mêmes ! ». En fait, on pourrait tout aussi bien dire « Les frameworks back, tous les mêmes ». En poussant l'abstraction au maximum, ça pourrait donner « Les outils qui servent à faire un truc en particulier, tous les mêmes ».
Du coup, je vais très logiquement commencer par parler de voiture.

Du framework front à la voiture, il n'y a qu'un pas

Et comme il n'y a qu'un pas, autant faire un tableau comparant l'incomparable.

Voiture Framework front
Finalité Déplacer des trucs d'un endroit à un autre. Permet un déplacement rapide de choses potentiellement lourdes ou encombrantes, comme votre lave-linge, vos enfants ou vous-même. Présenter de l'information de manière interactive, le plus souvent dans un navigateur
Fonctionnalités - De quoi accélérer, sinon ça ne sert à rien - de quoi freiner, sinon c'est dangereux - de quoi tourner, sinon c'est stupide - des espaces de stockage (coffre, habitacle, etc), sinon on ne peut pas y ranger des choses potentiellement lourdes ou encombrantes - Ce qu'il faut pour structurer l'information présentée - Ce qu'il faut la présenter ce manière stylée - Ce qu'il faut pour interagir avec
Confort d'utilisation - Un toit - Des portes - Des sièges - Un radiocassette avec auto-reverse - Divers sucres syntaxique - De la coloration syntaxique (au travers d'un IDE) - Des outils de dev et de debug
Compétences techniques - Il faut savoir que quand on appuie sur le frein, ça freine - Il faut aussi savoir quand et pour quelles raisons appuyer dessus - Il faut savoir démarrer en côte. Avec ou sans frein à main. - Il faut connaître les langages qui le composent - Il faut savoir comment ils fonctionnent ensemble - Il faut savoir quand et comment utiliser telle ou telle fonctionnalité du framework
Bonnes pratiques - Si on ne met pas le bon truc dans le réservoir, ça bousille le moteur - Les bonnes pratiques peuvent varier légèrement d'un modèle à l'autre (on n'accélère/freine pas de la même manière avec une traction ou une propulsion) - SOLID - DRY - Les bonnes pratiques peuvent varier légèrement d'un framework à l'autre (passage de fonctions vs réaction à des évènements)
L'environnement - Des routes - Des stations de ravitaillement (essence, électricité, charbon de bois) - des garagistes - Navigateur - Desktop - Mobile - Intégration continue
L'expérience de l'utilisateur Se bonifie avec le temps (« Tiens, ton démarrage en côte sur du verglas avec un âne mort dans le coffre ! ») Se bonifie avec le temps (« Je changeais déjà les pointeurs de souris au survol avec JQuery, alors c'est pas ton Svelte JS qui va m'apprendre la vie ! »)

En résumé, une voiture comme un framework front, c'est une finalité, des fonctionnalités, des compétences techniques, des bonnes pratiques d'utilisation, un confort d'utilisation et un environnement. Et au plus un utilisateur pratique (la conduite comme le dev), au plus il pexe, peu importe la voiture/framework front.
De plus, l'objet « voiture » comme l'objet « framework front » évolue dans le temps. On est bien d'accord qu'une Ford T et une Tesla actuelle, ou qu'un JQuery et un React, c'est pas du tout pareil ! Sauf qu'en fait, si. La finalité n'a pas changé. Les fonctionnalités ou le confort d'utilisation, n'ont pas drastiquement évolué sur le fond.
On pourra toujours me dire que si, qu'il n'y avait pas de radiocassette avec auto-reverse sur les Ford T et qu'il ne risque pas d'y en avoir sur une Tesla de maintenant ; ou encore qu'il serait malvenu de mettre le levier de vitesses sur le tableau de bord, comme sur les 4L. De la même manière, on pourra me dire que les frameworks front n'ont pas toujours eu de bundlers ou de transpileurs.
Et je répondrais que le confort et sa perception sont avant tout une histoire d'époque et de culture (pour faire simple. Sinon, les plus sceptiques pourront lire l'article de wikipedia sur le confort) ; que peu importe le modèle, peu importe l'époque, une voiture sert toujours à aller d'un point A à un point B, et un framework front à faire une jolie interface offrant une belle expérience à son utilisateur.

Toujours pas convaincu ? Alors faisons un peu d'histoire, histoire d'étayer ce que je raconte. Ça nous permettra de voir apparaître les principaux outils des frameworks/libs front.

Histoire (vite fait, inexacte, pleine de raccourcis) des frameworks/libs front

Javascript, HTML et CSS

C'est en 1991 que naissent HTML et CSS (qui ne gèrent à l'époque que la typographie (font, couleur, taille, graisse...). Ils sont présents dans le premier navigateur : WorldWideWeb. On peut noter que tout cela est sorti des cerveaux de l'équipe Tim Berners-Lee, alors qu'ils étaient en train d'inventer le web.
Quatre plus tard, en août 1995, apparaît LiveScript pour le navigateur Mosaic ; qui sera renommé JavaScript quatre mois plus tard, en décembre de la même année. Ce nouveau langage accompagnera le navigateur Netscape Navigator 2, en mars 1996.

Le besoin d'écrire une spécification commune pour tous les navigateurs se fait rapidement sentir, puisque chaque navigateur l'implémentait à sa sauce, aux mépris le plus total des petites mains du web, à savoir nous, les devs web (Je tiens à préciser qu'aujourd'hui encore, je fais des cauchemars à cause de JScript).
C'est donc en 1997 qu'apparaît la première spécification d'ECMAScript. S'en suivent, un an plus tard, la deuxième, puis la troisième encore un an plus tard (ce qui nous amène en 1999).

Et puis plus rien. Traversée du désert. Qui dure 10 ans. Mais qui aboutit à ECMAScript 5 (le fameux !) en 2009.
Qu'est-il arrivé à la version 4, me demanderiez-vous ? À peu près, je pense, le même genre de truc qui est arrivé à PHP6 : trop complexe, trop de trucs d'un coup, trop de gens pas d'accord entre eux, et d'autres chats à fouetter.
Si vous voulez plus de précisions et que lire de l'anglais ne vous effraie pas, je vous suggère la lecture de cet article.
Tiens, un petit fun fact, comme ça, en passant : saviez-vous qu'avant ES5, le JSON n'était pas géré nativement par le langage ?
Tiens, un autre fun fact ! Saviez-vous que (je cite cet article) « À ses débuts en 2009, AngularJS devait être un logiciel manipulant des données au format JSON » ?

Ensuite, traversée du désert. Encore. Mais moins longue. Seulement 6 ans de plus pour arriver à ECMAScript 6 en 2015. Je pense sincèrement qu'ils se sont sérieusement bougé les fesses, chez Ecma international, à cause de la sortie d'HTML 5, en octobre 2015. Parce que HTML 5 sans ES6, ç'aurait été comme du fromage sans moustache.

Depuis, on s'est lassé des déserts et on sort une nouvelle spec par an.

Jquery, le poc qui fait évoluer les mentalités

C'est sorti en 2006, et ça été une vraie révolution. On pouvait faire ça :

<div class="trololo"></div>
var $trololo = $(".trololo");
// oui, on utilisait `var`, à l'époque :)

En plus, ça marchait sur tous les navigateurs (parce qu'un plugin JQuery qui ne fonctionnait pas partout, personne n'en voulait) ! Pardon my JScript, mais qu'est-ce que ça a fait du bien !

Première fonctionnalité des frameworks/libs front : l'interopérabilité entre les navigateurs

Les moteurs de template

Je ne saurais pas dire quel est le vrai premier moteur de template, ni de quand il date. Par contre, quand Mustache ou Underscore sont sortis (en 2009), le développement front a été grandement facilité !

var view = {
  name: 'Michel'
};

// avec Mustache
var mustacheOutput = Mustache.render('Hello <span class="trololo-name">{{ name }}</span>', view);

// avec underscore
var compiledTemplate = _.template('hello <span class="trololo-name"><%= name %></span>')
var underscoreOutput = compiledTemplate(view)

Un peu plus de confort avec les frameworks/lib front : les moteurs de template

backbone, Jquery et underscore/lodash

MVVM

Backbone date de 2010. C'est la première implémentation d'un pattern MVVM (enfin, pas tout à fait, mais on pouvait l'utiliser comme tel). Et, contrairement à ce que beaucoup de monde pourrait penser, c'est très facile à utiliser :

<div class="trololo"></div>
// Récupération du noeud qui nous intéresse avec Jquery
var $trololo = $(".trololo");

// Préparation des templates (pour la vue) avec underscore
var trololoTemplate = _.template("Anonymous user");
var trololoAnonymousTemplate = _.template(
  'hello <span class="trololo-name"><%= name %></span>'
);

// Préparation du model avec Backbone
var trololoModel = Backbone.Model.extend({
  name: null,
});

// Création de la fonction de rendu
function renderTrololo() {
  var updatedHTML;
  if (trololoModel.name) {
    updatedHTML = trololoTemplate(trololoModel);
  } else {
    updatedHTML = trololoAnonymousTemplate(trololoModel);
  }
  $trololo.html(updatedHTML);
}

// Création des events
trololoModel.on("change", renderTrololo);

// Effectuer un premier rendu
renderTrololo();

// Et un second
trololoModel.name = "Robert";

Validation du pincipe de MVVM pour lier (binder) du HTML à du JS

Router

Backbone embarque également un router, permettant d'interagir avec l'historique et la barre d'URL. Et accessoirement de faire des SPAs (Single Page Application), même si ce principe n'avait pas de nom commercial à l'époque.

var Workspace = Backbone.Router.extend({
  routes: {
    help: "help", // #help
    "search/:query": "search", // #search/kiwis
    "search/:query/p:page": "search", // #search/kiwis/p7
  },

  help: function () {
    // ...
  },

  search: function (query, page) {
    // ...
  },
});

Validation du pincipe de router

Store

Afin de conserver de la donnée indépendante d'un composant, les models de backbone sont utilisés.
On note qu'il ne s'agit pas là d'avoir un seul gros store pour tout le monde, mais bien plusieurs stores.

var currentUserStore = Backbone.Model.extend({
  name: null,
  firstName: null,
  isLogged: false,
  //...
});

// ...

function renderTrololo() {
  var updatedHTML;
  if (currentUserStore.name) {
    // ici, on regarde le currentUserStore plutôt que le trololoModel
    updatedHTML = trololoTemplate(trololoModel);
  } else {
    updatedHTML = trololoAnonymousTemplate(trololoModel);
  }
  $trololo.html(updatedHTML);
}

currentUserStore.on("change", function () {
  trololoModel.trigger("change");
});

Validation du pincipe de store

RequireJS, puis Grunt et Gulp, et enfin Webpack

Avant, il fallait coder tout son code dans un seul fichier pour être sûr que tout serait exécuté dans le bon ordre (et pas en fonction de l'ordre dans lequel se sont chargés les fichiers, premier arrivé, premier exécuté).
La lib RequireJS (2009 ou 2010, c'est pas très clair), articulée autour de AMD (Asynchronous Module Definition) a permis de couper le code en plusieurs fichiers, organisés par modules, en s'assurant que tout serait chargé dans le bon ordre.

<html>
  <head>
    <title>My Sample Project</title>
    <!-- `data-main` attribute tells require.js to load
          scripts/main.js after require.js has been loaded. -->
    <script data-main="scripts/main" src="scripts/require.js"></script>
  </head>
  <body>
    <h1>My Sample Project</h1>
  </body>
</html>
requirejs(["helper/util"], function (util) {
  // This function is called when scripts/helper/util.js has been loaded.
});

(Oui, j'avoue, j'ai juste copié/collé la doc du site officiel)

On peut voir que ce n'est pas très très pratique. Par conséquent, des builders sont apparus pour simplifier soit l'encapsulation du code dans du RequireJS, soit la concaténation de tous les fichiers en un seul (ou en plusieurs, pour un peu plus de subtilité, reliés par RequireJS).
On y a rapidement inclus d'autres tâches, comme la minification, l'uglyfication, la compilation du sass/less... jusqu'à du déploiement automatique. Du coup, on a appelé ça des tasks builders ou des tasks managers.

Une autre famille de builders ne fonctionnant pas sur le principe de la suite explicite de tâche existe. On appelle ça des web bundlers.

Historiquement, pour les principaux, on a :

  • Gulp en septembre 2013
  • Grunt en avril 2016 (mais avec une première version utilisable dès 2013)
  • webpack en février 2014

Validation du principe de tasks managers/web bundlers

AngularJS : un framework, pas une lib

À sa sortie, en juin 2012, il s'appelait juste « Angular ». C'est à l'arrivée de la version 2 qu'on l'a renommé « AngularJS ».

Là encore, je me suis contenté de copier/coller la doc depuis le site officiel.

<html ng-app="phonecatApp">
  <head>
    ...
    <script src="lib/angular/angular.js"></script>
    <script src="app.js"></script>
    <script src="phone-list.component.js"></script>
  </head>
  <body>
    <!-- Utilisation d'un composant custom -->
    <phone-list></phone-list>
  </body>
</html>
// app.js

// Define the `phonecatApp` module
angular.module("phonecatApp", []);
// phone-list.component.js

// Créer le controller de notre composant et son injection de service
function PhoneListController($http) {
  this.phones = [
    {
      name: "Nexus S",
      snippet: "Fast just got faster with Nexus S.",
    },
    {
      name: "Motorola XOOM™ with Wi-Fi",
      snippet: "The Next, Next Generation tablet.",
    },
    {
      name: "MOTOROLA XOOM™",
      snippet: "The Next, Next Generation tablet.",
    },
  ];

  this.clickOnPhone = function () {
    console.log(phone.name);
  };

  var self = this; // heureusement qu'on a inventé les fonctions fléchées depuis
  $http.get("phones/phones.json").then(function (response) {
    self.phones = response.data;
  });
}
PhoneListController.$inject = ["$http"];

// Enregistrement du composant `phoneList` dans le module `phonecatApp`
// C'est aussi à cet endroit qu'on renseigne le template du composant
angular.module("phonecatApp").component("phoneList", {
  // This name is what AngularJS uses to match to the `<phone-list>` element.
  template:
    "<ul>" +
    '<li ng-repeat="phone in $ctrl.phones">' +
    '<span onclick="$ctrl.clickOnPhone(phone)">{{phone.name}}</span>' +
    "<p>{{phone.snippet}}</p>" +
    "</li>" +
    "</ul>", // heureusement qu'on a inventé les template string depuis
  controller: PhoneListController,
});

// Mise en place du routing
angular.module("phonecatApp").config([
  "$routeProvider",
  function config($routeProvider) {
    $routeProvider
      .when("/phones", {
        template: "<phone-list></phone-list>",
      })
      .when("/phones/:phoneId", {
        template: "<phone-detail></phone-detail>",
      })
      .otherwise("/phones");
  },
]);

Il y a beaucoup de choses nouvelles ici. C'est la première fois qu'on va penser avec des tags HTML custom, autrement appelés « composants », disposant d'un embryon de cycle de vie (qu'on ne voit pas dans cet exemple, car assez compliqué à utiliser).
C'est aussi le premier vrai virtual DOM et le premier vrai double binding (le fait qu'une donnée puisse être changée depuis le template par l'utilisateur et que cela se répercute sur le modèle du composant, ou au contraire qu’elle soit changée depuis le modèle du composant et que cela se répercute sur le template).
On note aussi, dans les nouveautés, un outil en ligne de commande permettant de démarrer son projet dans les règles de l'art (notamment en configurant Grunt) et de générer du code (squelette de classes, de modules, etc). De plus, on utilise bien un système de routing, déjà validé par Backbone.

Dans les choses « bizarres » (selon moi), on peut s'apercevoir qu'il n'y a pas de système de store (il fallait passer par des services), qu'il y a un système d'injection de dépendance explicite (inutile selon moi en javascript), et surtout que l'on utilise un pattern MVC beaucoup plus compliqué à utiliser (toujours selon moi) dans le cadre du développement front qu'un pattern MVVM)

Angular premier du nom a apporté la validation du principe de tag custom, du principe de virtual DOM, de double binding, de l'outillage en ligne de commande et, dans une moindre mesure, celui de cycle de vie. Bref, ça a été un véritable monument.

React

Sorti un an aprés Angular, en 2013, React à réussi à vraiment simplifier l'utilisation d'un framework/lib front.

var user = var React = require('react');
var ReactDOM = require('react-dom');

var UserComponent = React.createClass({
  name : '',
  render: function() {
    return (<div>Hello {this.name}</div>);
  },
  componentDidMount: function() {
      fetch('/connected-user')
        .then(function (result) {
          return result.json()
        })
        .then(function (result) {
          this.name = result.name
        })
  }
});

ReactDOM.render(<UserComponent />, getElementById('react-app'));

React devient vite populaire car bien plus simple qu'Angular (personnellement, je pense que l'utilisation du pattern MVVM, par opposition au pattern MVC, n'y est pas pour rien) et qu’il s'intègre bien avec CoffeeScript (une lib qui a beaucoup inspiré Ecma International pour sa 6ème version), puis ES6.

React apporte le premier vrai cycle de vie des composants et propose le JSX comme système de templating. Cela implique que le HTML n'est pas strictement décorrelé du JS (voire du CSS, si on utilise quelque chose comme la lib styled-components).
D'ailleurs, je profite du fait qu'on en parle pour vous laisser mon avis personnel sur le JSX : dans le cadre de minuscules composants, c'est très très pratique ; mais on risque de vite tourner, sans une hygiène de code stricte, à l'immonde gloubi-boulga rappelant les heures les plus sombres du PHP4.

Du côté du store, c'est simple, React n'en a pas. Par contre, sa plasticité permet d'en ajouter indépendamment un. Parmi les plus utilisés, on peut citer Redux ou MobX.

Enfin, on commence à voir des outils de développement directement sous la forme d'extension de navigateur, apportant un véritable confort aux devs.

React apporte la simplicité et le confort au développement, valide le principe de cycle de vie et permet de multiples expérimentations concernant le store

Donc, tous les frameworks front sont les mêmes...

...car on y retrouvera les mêmes fonctionnalités, soit dans le framework, soit dans une lib à utiliser conjointement :

  • une certaine interopérabilité entre les navigateurs (disons qu'il n'existe pas de vue-firefox ou de react-chrome)
  • de quoi utiliser des templates HTML
  • un pattern qui permet l'utilisation de double binding (MVC ou, plus souvent, MVVM)
  • un système de router
  • un système de store pour stocker de la donnée
  • des tags custom et des cycles de vie pour les composants

...mais également de quoi apporter du confort au développeur :

  • de quoi builder/bundler son code
  • de l'outillage tiers (souvent en ligne de commande), tel que ESlint, prettier...

Le reste, enfin, est plutôt lié au développeur en lui-même (compétences techniques, respect des bonnes pratiques) et à l'environnement ; au plus le développeur pratique, au plus il devient bon, peu importe le framework utilisé.

En conclusion, peu importe le framework front que vous utilisez, vous trouverez peu ou prou les mêmes outils. N'ayez donc pas peur de changer de framework, voyez plutôt ça comme une opportunité de faire la même chose qu'avec votre ancien outil, mais de manière légèrement différente. Cela fera du bien à votre expérience. Si vous souhaitez avoir un comparatif toujours pertinent, je peux vous conseiller (instant auto-promo) de (re)lire l'article React, Vue, Angular : quel framework front choisir en 2021 ?, sur ce même blog. Enfin, si vous avez eu le courage de lire cet article jusqu'au bout, merci :

Commentaires

Il n'y a actuellement aucun commentaire. Soyez le premier !