Builder une seule fois son application Javascript et la déployer à l’infini

Publié le

illustration de l'article

Lorsque l’on veut livrer régulièrement et rapidement des fonctionnalités de qualité, on utilise une usine logicielle (Gitlab, Jenkins, Github, etc).

Que ce soit pour une API REST développée en Go ou pour une application Front en Javascript, les grandes étapes restent les mêmes : builder, tester et déployer.

Gitlab CD/CD pipeline

Avant de déployer en production, il est vivement recommandé de tester l’application dans un ou plusieurs environnement(s) de recette.

Cela implique de pouvoir dynamiser certains paramètres en fonction de l’environnement.

Quelques exemples de paramètres :

  • URL de l’API utilisée car cette dernière n’est pas toujours sur le même domaine ou n’est tout simplement pas accessible hors production
  • certains codes de mesure d’audience ou support client doivent être omis hors production afin de ne pas fausser les rapports
  • les favicons peuvent varier pour différencier plus facilement la production de l’environnement de recette

Quelles sont les recommandations des librairies Javascript ?

Avant de déployer sur votre serveur les fichiers générés, vous devez builder votre application en indiquant le fichier de configuration à utiliser.

Les bonnes valeurs de configuration sont alors injectées dans votre code source et utilisées lors de la génération des fichiers.

// organisation des configurations par environnement dans Angular
└──myProject/src/environments/
                   └──environment.ts
                   └──environment.prod.ts
                   └──environment.stage.ts

Les sites des projets Angular, Vue ou React indiquent les arguments à passer pour builder avec la bonne configuration.

//angular
ng build --prod
//vuejs
vue-cli-service build --mode development
// react avec env-cmd
env-cmd -f .env.staging npm run build

Cependant, avec cette approche il est nécessaire de regénérer toute votre application autant de fois que vous avez d’environnements vers lesquels déployer.

Pourquoi ne pas injecter les paramètres au run ?

Contrairement à une application en Go, NodeJs ou encore Java, le code HTML est statique.

Il n’y a pas de processus qui fait tourner le code HTML, le code est interprété par votre navigateur. Il n’est donc pas possible d’utiliser des variables d’environnements dans le code HTML généré.

Cette notion est souvent rendue confuse car les frameworks et librairies sont souvent livrées avec beaucoup d’outils et configuration “out of the box” pour accélérer les développements

Ci-après quelques outils et optimisations qui peuvent prêter à confusion.

Le serveur local et les outils de développement

Un serveur HTTP en NodeJs (ex: webpack dev server) est souvent préconfiguré pour faciliter les développements en local (Hot reload configuration proxy, certificats SSL, etc).

On retrouve alors très souvent un fichier .env qui permet de surcharger le fonctionnement par défaut de ce serveur (ex: changer le port sur lequel écoute le serveur HTTP) ou encore définir des variables personnalisées.

Votre serveur local peut lire les variables d’environnement depuis le fichier .env ou depuis votre terminal car c’est un processus qui est lancé sur votre machine.

Site statique vs Server Side Rendering (SSR)

La plupart des frameworks fournissent désormais une configuration qui permet de faire du Server Side Rendering.

Le code Front peut alors être exécuté côté client mais également côté serveur avec Node JS.

On parle alors d’application isomorphique ou universelle.

On retrouve alors ce fichier “.env” car le site est servi par un process NodeJS et peut donc injecter des variables d’environnement.

Attention, lors de chaque build, vous devrez toujours fournir les variables d’environnements à injecter à la commande.

Pourquoi vouloir builder une seule fois ?

Les paquets NPM peuvent varier entre deux builds

Les versions des paquets NPM sont souvent faiblement définies et le build d’une même application à deux moments différents peut engendrer une mise à jour non souhaitée d’un des paquets.

// exemple de définition de dépendances
"dependencies": {
  "my_dep": "^1.0.0",
  "another_dep": "~2.2.0"
}

Même si l’on utilise les “^” ou “~”, il est toujours possible qu’une dépendance d’une des librairies importées ne soit pas fixée…

Au-delà d’éventuelles régressions ou incompatibilités entre certaines librairies utilisées, cela peut même introduire de nouvelles failles de sécurité.

La meilleure solution est de builder une seule fois votre application, analyser les fichiers utilisés et réutiliser ces mêmes fichiers dans tous vos environnements.

Focus sécurité NPM

Afin de vérifier qu’aucune dépendance requise n’est vulnérable, il est conseillé d’ajouter des vérifications dans la pipeline de votre usine logicielle.

La commande npm audit permet par exemple d’alerter si certaines vulnérabiltiés ont été détectées.

npm run audit
NPM audit

Il est aussi possible de scanner le code à la recherche de secrets ou données sensibles.

Inutile de rappeler qu’il n’y a rien de secret dans votre navigateur et qu’il ne faut pas faire confiance au client !

Bonne pratique énoncée dans le manifeste “12 Factor App”

Lors de l’apparition du Cloud, beaucoup d’applications web ont été déployées sans penser aux bonnes pratiques inhérentes à ce type d’infrastructure (élasticité, mise à l’échelle, création d’environnement à la volée, etc). Aussi, afin d’aider à l’adoption du cloud et à la mise en place de bonnes pratiques de conception, des développeurs (en particulier ceux d’Heroku) ont écrit en 2012 un manifeste qui énonce douze grands principes: 12 Factor App

La règle qui nous intéresse particulièrement est la suivante :

Dev/Prod parity

Keep development, staging, and production as similar as possible

Afin de livrer le plus rapidement possible les fonctionnalités à l’utilisateur final, l’idée est d’automatiser au maximum les process de livraison et réduire, voire supprimer, les interventions humaines.

Aussi, limiter au maximum les changements de code entre le poste du développeur et le code qui tourne sur les serveurs de production permet de détecter et corriger rapidement les anomalies.

Comment dynamiser mon application avec un seul build ?

Chercher et remplacer les valeurs

Une fois le code généré, la solution consiste à chercher toutes les valeurs de configuration et les remplacer par les bonnes valeurs.

Il faudra donc utiliser un script qui détecte les motifs à trouver et les remplacer par les bonnes valeurs en fonction de l’environnement.

Une solution consiste à utiliser la commande “envsubst”

DOMAIN=”whatever.com”
API_URL=”api.fake.com”
envsubst $DOMAIN,$API_URL < "source.txt" > "source_replaced.txt"

Attention aux erreurs de syntaxe si vous ne voulez pas remplacer toute une partie de votre applicatif par mégarde…

Charger la configuration depuis un fichier Javascript

L’idée est de charger un fichier de configuration qui changera pour chaque environnement.

Si nous définissons le chargement d’un script “env.js” suffisamment tôt dans le DOM HTML, le contenu sera alors disponible pour le code de notre application.

// index.html
<script src="env.js" />
 
// env.js
(function (window) {
  window.env = window.env || {};
  window.env.apiUrl = 'http://localhost:8080';
}(this));

// app.js 
const apiUrl  = window.env.apiUrl

C’est cette dernière solution que nous recommandons d’utiliser.

Elle permet de charger les bonnes variables sans risque d’écraser ou supprimer des lignes de code.

Le fonctionnement est facilement compréhensible et déclinable par des ops si jamais vous n’avez pas accès à la production.

Conclusion

A travers cet article, nous avons vu comment implémenter une logique de déploiement continu sans effet de bords grâce à un build unique.

L’ajout d’un nouvel environnement de recette est très facile et la configuration devient explicite.

A noter que cette technique n’est pas liée à un framework ou librairie, elle est déclinable pour toute application HTML qui contient du code Javascript.

Vous avez dit Web Component et Vanilla JS ?

Pour aller plus loin

Dans de prochains articles, nous verrons des exemples d’implémentation de cette technique avec des frameworks comme Angular ou React.

#craft #javascript

D'autres articles à lire