Node.js côté frontend ?!
Le web c’est simple
Il y a bien longtemps, quand j’ai découvert le développement web frontend, mon modèle mental était simple :
-
HTML décrit le contenu d’une page : un titre par-ci, un paragraphe par-là, un formulaire à deux boutons, …
-
CSS sert à styliser ledit contenu : le titre est en gras, le bouton Annuler en italique, …
-
JS permet d’exécuter du code côté client : e.g. remplir dynamiquement le contenu de telle div en réaction à tel parcours utilisateur, …
Appelons ce modèle le modèle "triforce" — parce que je suis en train de faire découvrir Ocarina of Time à mon fils aîné. Avec ce modèle triforce, le web c’est simple : je créé ces 3 fichiers statiques, je fais tourner un serveur qui les sert statiquement, et réagit aux requêtes POST du formulaire, et c’est good.
Le web moderne, c’est simple aussi
Évidemment, le web moderne est beaucoup plus compliqué, avec whatmille frameworks, des features et patterns javascript de plus en plus complexes, des usages toujours plus évolués…
Mais même quelque chose comme une SPA rentre dans ce modèle triforce : une SPA, c’est rien d’autre qu’un HTML à peu près vide que le code javascript remplit dynamiquement en réponse aux actions utilisateur.
So far so good.
Et un beau jour tu entends parler de node.js, 'paraît que c’est la huitième merveille du monde, et ça permet de faire tourner du javascript où tu veux (alors qu’auparavant le javascript ne s’exécutait que dans le navigateur). Génial, les devs frontend qui maîtrisent bien le javascript pourront aussi utiliser leur langage favori côté backend, les boîtes peuvent n’embaucher qu’un type de profil si elles le souhaitent, tout le monde est content.
Ici non plus, pas de contradiction avec mon modèle mental, puisque le sujet est totalement décorrélé : le modèle triforce régit ce qui tourne sur le navigateur du client, et node.js sert à faire tourner un serveur en backend.
Mais en fait pas tant que ça
Et puis quand tu commences à t’intéresser de plus près à node.js (et npm, qui est son package-manager), on te dit que c’est utilisé certes côté serveur, mais également côté client :
The idea of using Node.js for frontend is a natural extension of the various features that it provides.
Availability of Packages and Libraries: There are many libraries available to use in the frontend
On te parle de frameworks node.js pour les devs frontend :
Comparing top Node.js frameworks for frontend developers
Et là, tu es perdu… node.js est pourtant un moteur javascript qui tourne côté serveur, donc quel rapport avec le frontend ?
Est-ce que node.js peut tourner sur serveur ET dans un browser, comme le laissent à entendre les phrases du genre "on exécute node.js côté client et côté serveur" ?
Est-ce qu’on peut créer un site avec node.js/npm de sorte que le navigateur interprète le fichier package.json pour charger les dépendances dynamiquement ?
Comment diable puis-je faire npm install telle-dépendance chez moi sur mon poste, et voir la dépendance utilisée par le navigateur de mon client ?
Explications
En fait, dire qu’on peut utiliser node.js ou npm côté frontend c’est un abus de langage. Je sais, je sais, c’est scandaleux, welcome to the real world Jeremy.
Même en utilisant node.js ou npm pour le dev frontend, le modèle triforce reste tout à fait vrai : le navigateur se contente d’exécuter des fichiers statiques HTML/CSS/JS, que le serveur commence par lui servir statiquement.
Ce qui change, c’est la façon dont sont créés ces fichiers statiques :
-
avant, on codait ces fichiers à la mano, et le navigateur interprètait directement les fichiers qu’on avait codés
-
maintenant, on code toujours des fichiers à la mano, mais on passe par une étape intermédiaire de transformation, et le navigateur interprète plutôt le résultat de cette étape intermédiaire
Du coup, node.js et npm sont, certes, utilisés par des développeurs frontend, mais ils sont utilisés uniquement pour DÉVELOPPER le code frontend pas pour l’exécuter, ce sont des outils dev-side only. Dit autrement : au moment de l’exécution du javascript par le navigateur, celui-ci n’a pas connaissance de node.js ou npm !
Une phrase telle que celle-ci me paraît donc inutilement trompeuse :
Let us understand why the use of Node.js enhances the developer experience […] : availability of Packages and Libraries: There are many libraries available to use in the frontend. For example, the ‘moment’ is a library that processes dates in the specified format.
En effet, elle laisse à entendre que grâce à node.js/npm, on peut dorénavant utiliser moment.js côté frontend. Or, c’est trompeur puisqu’on peut tout à fait utiliser moment.js côté frontend même sans l’intervention de node.js/npm.
Exemple concret avec moment.js
Allez, soyons fous, mettons les mains dans le cambouis en codant un exemple concret utilisant moment.js, la librairie mentionnée par la citation que j’ai donnée.
Côté frontend, sans node.js/npm
On peut tout à fait utiliser moment.js côté frontend, sans aucune intervention de node.js/npm. D’ailleurs, il vous suffit de copier-coller ce bout de code dans un fichier pouet.html, puis de l’exécuter avec un browser firefox pouet.html pour voir apparaître une magnifique popup utilisant moment.js :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Look Mom, I'm using moment.js without npm</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.3/moment.min.js"></script>
<script>
alert(`Today is ${moment().format('dddd')}, yay !`);
</script>
</head>
<body>
<p>Pouet</p>
</body>
</html>
Côté backend, avec node.js/npm
Bien sûr, on peut également utiliser moment.js côté backend, grâce à node.js — c’est bien à ça que sert node.js :
mkdir /tmp/mysupertest && cd /tmp/mysupertest
npm install moment
echo '''
let moment = require("moment");
console.log(`Today is ${moment().format("dddd")}, yay !`);
''' > main.js
node main.js
# Today is Thursday, yay !
Côté frontend, avec npm
Et la partie qui nous intéresse aujourd’hui : on peut aussi utiliser npm/node.js pour BUILDER une app frontend utilisant moment.js (illustré ici avec le builder vite mais d’autres alternatives existent) :
C’est un brin plus complexe, il faut plusieurs fichiers et quelques commandes. Commençons par le fichier HTML, index.html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:;base64,=">
<title>Look Mom, this time I'm using npm to build the frontend code</title>
</head>
<body>
<script type="module" src="/src/main.js"></script>
<p>Pouet</p>
</body>
</html>
Puis le javascript src/main.js :
import moment from 'moment';
alert(`Today is ${moment().format('dddd')}, yay !`);
Et enfin package.json qui explicite la dépendance de notre site à moment.js, et décrit comment builder le site grâce à vite :
{
"name": "pouet",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"moment": "^2.29.3"
},
"devDependencies": {
"vite": "^2.9.5"
}
}
Avec ces trois fichiers, notre répertoire de travail ressemble à ceci :
.
├── src
│ └── main.js
├── index.html
└── package.json
Derrière, on peut builder et charger le site avec quelques commandes :
# télécharger le code de moment.js dans le répertoire node_modules :
npm install
# lancer un serveur de dev pour tester son code :
npm run dev
# builder le site à servir à l'utilisateur final :
npm run build
# servir le site buildé :
python3 -m http.server --directory dist/ 9000
# consulter le site buildé :
firefox localhost:9000
Et paf, on utilise moment.js côté frontend \o/
Mais au fait, on utilise quoi exactement ?
Si tu as répondu "node.js"… tu as tout faux ! Mais je ne te jette pas la pierre, déjà car le titre du post entretient cette confusion, et surtout car c’est un sacré bazar :
-
même dans notre troisième exemple, on n’utilise pas node.js directement mais plutôt npm, son gestionnaire de paquets
-
notamment, parmi les commandes qu’on a utilisées,
npm installutilise npm pour installermoment.jsdans le répertoirenode_modules -
et pour
npm run build? Si on regarde la doc de npm run de plus près, et qu’on jette un oeil aupackage.json, on se rend compte que npm ne builde PAS lui-même le site :npm run buildn’est qu’un wrapper autour devitequi est un outil pour le dev frontend ; au build,viteanalyse le projet et regroupe tout le code javascript dans un gros script concaténé. C’est ce gros script concaténé qui est interprété par le navigateur. -
de même,
npm run devutilisevitepour lancer un serveur de dev : en effet, si vous avez bien suivi, le navigateur n’a plus la possibilité d’exécuter directement le code qu’on développe, vu qu’il lui manquerait l’étape intermédiaire. Une solution serait de :-
développer son code
-
lancer
npm run build -
servir le site buildé
-
le lancer avec un navigateur pour voir ses développements
-
-
… mais comme c’est pas pratique,
npm run devautomatise tout ça en lançant un serveur qui fournit une version buildée du code en développement, et recharge la page quand on modifie les fichiers source. Z’ont pensé à tout !
À noter que j’ai illustré les exemples avec vite parce que c’est l’outil par défaut utilisé par vue.js avec lequel j’ai fait mumuse récemment, mais plein, plein (j’insiste : plein) d’autres outils existent pour adresser les besoins des devs frontend : l’écosystème javascript est un bordel sans nom très riche ; je laisse à d’autres le soin de présenter les outils existants et leur intérêt.
Et histoire d’avoir un twist final : beaucoup de ces outils sont eux-mêmes développés… avec node.js, et nécessitent node.js pour s’exécuter (un peu comme un script python nécessite un interpréteur python pour s’exécuter), on utilise donc bien node.js finalement !
Au final, pourquoi utiliser node.js/npm côté frontend ?
En résumé :
-
même si les sites modernes sont loin de se résumer à de simples fichiers statiques, le modèle triforce HTML+CSS+JS rendus et exécutés par le browser reste valide : le browser n’a pas connaissance de node.js ou de npm
-
node.js et npm sont en revanche utilisés pour DÉVELOPPER des applications frontend, conjointement avec une tétra-chiée d’autres outils
-
lorsqu’on utilise npm/node.js, on quitte le modèle "le browser exécute ce que j’ai codé" ; à la place, le browser exécute un site buildé par une étape intermédiaire qui prend en entrée ce que j’ai codé, et qui crache en sortie les fichiers à envoyer au navigateur
Reste une question : pourquoi ? Pourquoi se compliquer la vie avec un package.json, npm install ou npm run build pour construire notre site, là où une petite balise <script> faisait très bien le taf ?
Eh bien parce qu’ajouter une étape de traitement avant exécution par le navigateur a beaucoup d’avantages, surtout quand un projet grossit :
-
ça autorise l’utilisation de langages plus évolués que leurs contreparties HTML/CSS/JS (HAML pour HTML, less ou sass pour CSS, typescript pour javascript, …)
-
ça permet des traitements préparatoires, comme la minification du javascript ou la compression des images
-
le regroupement des différents fichiers javascript en un seul et unique script évite à l’utilisateur final d’attendre le retour de nombreuses requêtes pour rassembler toutes les dépendances du site qu’il consulte
-
et surtout… ça permet de gérer plus facilement les dépendances (qui peuvent vite devenir bordélique quand un projet grossit) : ne pas avoir à inclure (récursivement !) les dépendances d’une librairie, ou à gérer leurs versions manuellement, c’est inestimable !
Même si ce sujet est trivial pour les devs frontend, j’espère que ce post aidera les autres à mieux comprendre la place de node.js/npm dans le dev frontend. D’autant que c’est bien pratique d’avoir quelques compétences frontend pour faire des interfaces clean à ses projets persos : on peut faire des choses très utilisables sans être un expert.