Par Spike Brehm
Cet article a été posté de manière croisée sur VentureBeat.
A Airbnb, nous avons beaucoup appris au cours des dernières années en construisant des expériences web riches. Nous avons plongé dans le monde des apps à page unique en 2011 avec notre site Web mobile, et nous avons depuis lancé Wish Lists et notre page de recherche nouvellement remaniée, entre autres. Chacune d’entre elles est une grande application JavaScript, ce qui signifie que la majeure partie du code s’exécute dans le navigateur afin de prendre en charge une expérience plus moderne et interactive.
Cette approche est courante aujourd’hui, et des bibliothèques comme Backbone.js, Ember.js et Angular.js ont permis aux développeurs de construire plus facilement ces applications JavaScript riches. Nous avons toutefois constaté que ces types d’applications présentent certaines limites critiques. Pour expliquer pourquoi, faisons d’abord un rapide détour par l’histoire des apps web.
JavaScript Grows Up
Depuis l’aube du Web, l’expérience de navigation a fonctionné comme suit : un navigateur web demandait une page particulière (disons, « http://www.geocities.com/ »), amenant un serveur quelque part sur Internet à générer une page HTML et à la renvoyer par câble. Cela a bien fonctionné parce que les navigateurs n’étaient pas très puissants et que les pages HTML représentaient des documents essentiellement statiques et autonomes. JavaScript, créé pour permettre aux pages web d’être plus dynamiques, ne permettait pas grand-chose de plus que des diaporamas d’images et des widgets de sélection de date.
Après des années de progrès dans l’informatique personnelle, les technologues créatifs ont poussé le web à ses limites, et les navigateurs web ont évolué pour suivre. Maintenant, le Web a mûri pour devenir une plateforme d’application complète, et les runtimes JavaScript rapides et les normes HTML5 ont permis aux développeurs de créer des applications riches qui, auparavant, n’étaient possibles que sur des plateformes natives.
L’application à page unique
Il n’a pas fallu longtemps pour que les développeurs commencent à construire des applications entières dans le navigateur en utilisant JavaScript, en profitant de ces nouvelles capacités. Des applications comme Gmail, l’exemple classique de l’application à page unique, pouvaient répondre immédiatement aux interactions de l’utilisateur, n’ayant plus besoin de faire un aller-retour vers le serveur juste pour rendre une nouvelle page.
Des bibliothèques comme Backbone.js, Ember.js et Angular.js sont souvent appelées bibliothèques MVC (Model-View-Controller) ou MVVM (Model-View-ViewModel) côté client. L’architecture MVC côté client typique ressemble à quelque chose comme ceci:
L’essentiel de la logique de l’application (vues, modèles, contrôleurs, modèles, internationalisation, etc.) vit dans le client, et il parle à une API pour les données. Le serveur peut être écrit dans n’importe quel langage, comme Ruby, Python ou Java, et il s’occupe principalement de servir une page initiale de HTML. Une fois que les fichiers JavaScript sont téléchargés par le navigateur, ils sont évalués et l’application côté client est initialisée, récupérant les données de l’API et rendant le reste de la page HTML.
C’est génial pour l’utilisateur car une fois que l’application est initialement chargée, elle peut prendre en charge la navigation rapide entre les pages sans rafraîchir la page, et si elle est bien faite, elle peut même fonctionner hors ligne.
C’est génial pour le développeur parce que l’app idéalisée à une seule page a une séparation claire des préoccupations entre le client et le serveur, favorisant un bon flux de développement et empêchant le besoin de partager trop de logique entre les deux, qui sont souvent écrits dans des langues différentes.
En pratique, cependant, il y a quelques défauts fatals avec cette approche qui l’empêchent d’être correcte pour de nombreux cas d’utilisation.
SEO
Une application qui ne peut s’exécuter que du côté client ne peut pas servir du HTML aux crawlers, elle aura donc un mauvais référencement par défaut. Les crawlers web fonctionnent en faisant une requête à un serveur web et en interprétant le résultat ; mais si le serveur renvoie une page blanche, cela n’a pas beaucoup de valeur. Il existe des solutions de contournement, mais pas sans sauter à travers quelques cerceaux.
Performance
De même, si le serveur ne rend pas une page complète de HTML mais attend plutôt que le JavaScript côté client le fasse, les utilisateurs connaîtront quelques secondes critiques de page blanche ou de spinner de chargement avant de voir le contenu de la page. De nombreuses études montrent l’effet radical d’un site lent sur les utilisateurs, et donc sur les revenus. Amazon affirme que chaque réduction de 100 ms du temps de chargement des pages augmente les revenus de 1 %. Twitter a passé un an et 40 ingénieurs à reconstruire son site pour effectuer le rendu sur le serveur au lieu du client, affirmant une amélioration de 5 fois le temps de chargement perçu.
Maintenabilité
Bien que le cas idéal puisse conduire à une belle séparation propre des préoccupations, inévitablement, certains bits de logique d’application ou de logique de vue finissent par être dupliqués entre le client et le serveur, souvent dans des langues différentes. Les exemples les plus courants sont le formatage des dates et des devises, les validations de formulaires et la logique de routage. Cela fait de la maintenance un cauchemar, en particulier pour les apps plus complexes.
Certains développeurs, moi y compris, se sentent mordus par cette approche – ce n’est souvent qu’après avoir investi le temps et les efforts nécessaires pour construire une app à page unique que l’on se rend compte des inconvénients.
Une approche hybride
En fin de compte, nous voulons vraiment un hybride de la nouvelle et de l’ancienne approche : nous voulons servir du HTML entièrement formé à partir du serveur pour la performance et le SEO, mais nous voulons la vitesse et la flexibilité de la logique d’application côté client.
À cette fin, nous avons expérimenté chez Airbnb des applications « Isomorphic JavaScript », qui sont des applications JavaScript qui peuvent fonctionner à la fois côté client et côté serveur.
Une application isomorphe pourrait ressembler à ceci, surnommée ici « Client-server MVC »:
Dans ce monde, une partie de votre application et de votre logique de vue peut être exécutée à la fois sur le serveur et le client. Cela ouvre toutes sortes de portes – des optimisations de performance, une meilleure maintenabilité, le SEO par défaut, et des applications web plus stateful.
Avec Node.js, un runtime JavaScript rapide et stable côté serveur, nous pouvons maintenant faire de ce rêve une réalité. En créant les abstractions appropriées, nous pouvons écrire notre logique d’application de telle sorte qu’elle fonctionne à la fois sur le serveur et sur le client – la définition du JavaScript isomorphe.
Le JavaScript isomorphe dans la nature
Cette idée n’est pas nouvelle – Nodejitsu a écrit une excellente description de l’architecture JavaScript isomorphe en 2011 – mais son adoption a été lente. Il y a déjà eu quelques frameworks isomorphes à surgir.
Mojito a été le premier framework isomorphe open-source à obtenir une certaine presse. C’est un framework avancé et complet basé sur Node.js, mais sa dépendance à YUI et les bizarreries spécifiques à Yahoo!n’ont pas conduit à une grande popularité dans la communauté JavaScript depuis qu’ils l’ont mis en source ouverte en avril 2012.
Meteor est probablement le projet isomorphe le plus connu aujourd’hui. Meteor est construit de A à Z pour supporter les apps en temps réel, et l’équipe construit un écosystème entier autour de son gestionnaire de paquets et de ses outils de déploiement. À l’instar de Mojito, il s’agit d’un cadre Node.js de grande envergure et aux opinions bien arrêtées, mais il a fait un bien meilleur travail en s’engageant auprès de la communauté JavaScript, et sa version 1.0, très attendue, n’est plus très loin. Meteor est un projet à suivre – il a une équipe de stars, et il a levé 11,2 millions de dollars auprès d’Andreessen Horowitz – du jamais vu pour une entreprise entièrement axée sur la publication d’un produit open-source.
Asana, l’application de gestion des tâches fondée par le cofondateur de Facebook, Dustin Moskovitz, a une histoire isomorphe intéressante. Ne souffrant pas de financement, compte tenu du statut de Moskovitz en tant que plus jeune milliardaire du monde, Asana a passé des années en R&D à développer leur framework fermé Luna, l’un des exemples les plus avancés de JavaScript isomorphe autour. Luna, construit à l’origine sur v8cgi, avant l’existence de Node.js, permet d’exécuter une copie complète de l’application sur le serveur pour chaque session utilisateur. Il exécute un processus de serveur distinct pour chaque utilisateur, en exécutant le même code d’application JavaScript sur le serveur que sur le client, ce qui permet toute une classe d’optimisations avancées, telles qu’un support hors ligne robuste et des mises à jour en temps réel accrocheuses.
Nous avons lancé une bibliothèque isomorphe de notre cru plus tôt cette année. Appelée Rendr, elle vous permet de construire une application monopage Backbone.js + Handlebars.js qui peut également être entièrement rendue côté serveur. Rendr est le fruit de notre expérience dans la reconstruction de l’application web mobile Airbnb afin d’améliorer considérablement les temps de chargement des pages, ce qui est particulièrement important pour les utilisateurs de connexions mobiles à forte latence. Rendr s’efforce d’être une bibliothèque plutôt qu’un framework, donc il résout moins de problèmes pour vous par rapport à Mojito ou Meteor, mais il est facile à modifier et à étendre.
Abstraction, Abstraction, Abstraction
Le fait que ces projets ont tendance à être de grands frameworks web full-stack témoigne de la difficulté du problème. Le client et le serveur sont des environnements très dissemblables, et nous devons donc créer un ensemble d’abstractions qui découplent notre logique d’application des implémentations sous-jacentes, afin que nous puissions exposer une API unique au développeur d’applications.
Routage
Nous voulons un ensemble unique de routes qui mappent les modèles URI aux gestionnaires de routes. Nos gestionnaires de route doivent pouvoir accéder aux en-têtes HTTP, aux cookies et aux informations URI, et spécifier des redirections sans accéder directement à window.location (navigateur) ou req et res (Node.js).
Fetching and persisting data
Nous voulons décrire les ressources nécessaires pour rendre une page ou un composant particulier indépendamment du mécanisme de fetching. Le descripteur de ressources pourrait être un simple URI pointant vers un point de terminaison JSON, ou pour des applications plus importantes, il peut être utile d’encapsuler les ressources dans des modèles et des collections et de spécifier une classe de modèle et une clé primaire, qui, à un moment donné, serait traduite en URI.
Rendu de vue
Que nous choisissions de manipuler directement le DOM, de nous en tenir à la modélisation HTML basée sur des chaînes de caractères, ou d’opter pour une bibliothèque de composants d’interface utilisateur avec une abstraction DOM, nous devons être en mesure de générer du balisage de manière isomorphe. Nous devrions être en mesure de rendre n’importe quelle vue sur le serveur ou le client, en fonction des besoins de notre application.
Construction et packaging
Il s’avère que l’écriture de code d’application isomorphe n’est que la moitié de la bataille. Des outils comme Grunt et Browserify sont des parties essentielles du flux de travail pour obtenir réellement l’application et la faire fonctionner. Il peut y avoir un certain nombre d’étapes de construction : compilation des modèles, inclusion des dépendances côté client, application des transformations, minification, etc. Le cas le plus simple est de combiner tout le code de l’application, les vues et les modèles en un seul paquet, mais pour les grandes applications, cela peut se traduire par des centaines de kilo-octets à télécharger. Une approche plus avancée consiste à créer des bundles dynamiques et à introduire le chargement paresseux des ressources, mais cela devient vite compliqué. Des outils d’analyse statique comme Esprima peuvent permettre aux développeurs ambitieux de tenter une optimisation avancée et une métaprogrammation pour réduire le code passe-partout.
Composer ensemble de petits modules
Etre le premier à commercialiser un framework isomorphe signifie que vous devez résoudre tous ces problèmes en même temps. Mais cela conduit à des frameworks volumineux et peu maniables qui sont difficiles à adopter et à intégrer dans une app déjà existante. Au fur et à mesure que les développeurs s’attaqueront à ce problème, nous verrons une explosion de petits modules réutilisables qui peuvent être intégrés ensemble pour construire des apps isomorphes.
Il s’avère que la plupart des modules JavaScript peuvent déjà être utilisés de manière isomorphe avec peu ou pas de modification. Par exemple, des bibliothèques populaires comme Underscore, Backbone.js, Handlebars.js, Moment, et même jQuery peuvent être utilisées sur le serveur.
Pour démontrer ce point, j’ai créé un exemple d’application appelé isomorphic-tutorial que vous pouvez consulter sur GitHub. En combinant ensemble quelques modules, chacun pouvant être utilisé de manière isomorphe, il est facile de créer une app isomorphe simple en seulement quelques centaines de lignes de code. Elle utilise Director pour le routage basé sur le serveur et le navigateur, Superagent pour les requêtes HTTP et Handlebars.js pour la création de modèles, le tout construit au-dessus d’une application Express.js de base. Bien sûr, au fur et à mesure qu’une appli devient plus complexe, il faut introduire plus de couches d’abstraction, mais mon espoir est qu’au fur et à mesure que plus de développeurs expérimentent avec cela, il y aura de nouvelles bibliothèques et de nouveaux standards à émerger.
La vue d’ici
A mesure que plus d’organisations sont à l’aise pour exécuter Node.js en production, il est inévitable que de plus en plus d’applications web commencent à partager du code entre leur code client et serveur. Il est important de se rappeler que le JavaScript isomorphe est un spectre – il peut commencer par le simple partage de modèles, progresser pour devenir la couche d’affichage d’une application entière, jusqu’à la majorité de la logique commerciale de l’application. La nature exacte et la manière dont le code JavaScript est partagé entre les environnements dépendent entièrement de l’application en cours de construction et de son ensemble unique de contraintes.
Nicholas C. Zakas a une belle description de la manière dont il envisage que les applications commencent à tirer leur couche d’interface utilisateur vers le bas sur le serveur à partir du client, permettant des optimisations de performance et de maintenabilité. Une application n’a pas besoin d’arracher son backend et de le remplacer par Node.js pour utiliser le JavaScript isomorphe, ce qui reviendrait à jeter le bébé avec l’eau du bain. Au lieu de cela, en créant des API judicieuses et des ressources RESTful, le backend traditionnel peut vivre aux côtés de la couche Node.js.
A Airbnb, nous avons déjà commencé à réoutiller notre processus de construction côté client pour utiliser des outils basés sur Node.js comme Grunt et Browserify. Notre application Rails principale ne sera peut-être jamais entièrement supplantée par une application Node.js, mais en adoptant ces outils, il devient de plus en plus facile de partager certains bouts de JavaScript et de modèles entre les environnements.
Vous l’avez entendu ici en premier – d’ici quelques années, il sera rare de voir une application web avancée qui n’exécute pas un peu de JavaScript sur le serveur.
Learn More
Si cette idée vous enthousiasme, venez découvrir l’atelier Isomorphic JavaScript que j’enseignerai à DevBeat le mardi 12 novembre à San Francisco, ou à General Assembly le jeudi 21 novembre. Nous piraterons ensemble sur l’exemple d’application isomorphique-tutorielle Node.js que j’ai créé pour démontrer à quel point il est vraiment facile de commencer à écrire des applications isomorphiques.
Suivez également l’évolution des applications web d’Airbnb en me suivant sur @spikebrehm et l’équipe d’ingénierie d’Airbnb sur @AirbnbEng.
.