Accueil Programmation Partager du code Javascript entre node.js et navigateur web


Partager du code Javascript entre node.js et navigateur web


Une fois n’est pas coutume, on ne va pas parler d’application strictement orientée météo, mais plutôt de programmation Javascript et de node.js. Il me parait intéressant de discuter de quelques subtilités de programmation dans ces environnements que j’ai découvert en travaillant sur mon projet de modélisation PIFO, dont je reparlerais plus spécifiquement en détail ultérieurement.

Sur ce projet, j’ai initialement développé sur navigateur web, en structurant mon code avec des modules au format ES6 (ECMAScript 6). A un certain stade s’est posée la question de pouvoir faire tourner les modèles en batch automatique à l’aide de node.js. Ce partage entre deux environnements a soulevé quelques problématiques, je vous propose donc de voir comment les résoudre ici.

Node et ES6, des modules à priori incompatibles

Un module au format ES6 (ou ES2015) se présente de la façon suivante :

export function pouet() {
//...
};

Ces exports sont ensuite utilisables par d’autres scripts, par exemple pour utiliser la fonction pouet() :

import { pouet } from "./pouet.js";
pouet();

Node a son propre format de modules. La même chose s’écrirait de la façon suivante en node :

var pouet = function () {
//...
}
exports.pouet = pouet;

Et l’importation se ferait de la façon suivante :

var monModulePouet = require("pouet.js");
monModulePouet.pouet();

Ces deux formats sont incompatibles, par la syntaxe d’une part, et par certains détails de chargement du module. Et il n’existe pas de solution de contournement, même avec des bidouilles conditionnelles

Il s’avère que les dernières versions de node permettent d’activer une fonctionnalité de chargement de modules ES6 en activant le flag –experimental-module. La contrainte est qu’il faut renommer les fichiers .js en .mjs. Ceci n’était pas envisageable pour moi, mes outils de développement préférés ne reconnaissant pas cette extension, et je n’étais pas sûr non plus que les navigateurs la supportaient. Dupliquer le code ou utiliser un transpileur type Babel auraient pu être des options, mais alourdirait le workflow de travail, sans compter le risque d’erreurs ou d’oublis.

Le module ESM comme solution

Il existe malgré tout une solution bien plus simple : le module ESM. C’est un module loader qui remplace la fonction require de Node en y ajoutant le chargement de modules ES6. Son utilisation est des plus simple, il suffit de l’ajouter à son projet via l’outil npm :

npm install esm

Ensuite, il faut utiliser un petit fichier Javascript “wrapper”  qui remplace votre main :

require = require("esm")(module/*, options*/)
module.exports = require("./main.js")

Le fichier main.js est le module ES6 principal. Les instructions import et export y sont alors utilisables tout-à-fait normalement, ainsi que dans toutes les dépendances. Simple, efficace.

Gérer le code spécifique à la plateforme

Maintenant le problème de l’utilisation des modules réglé, il ne reste plus qu’à pouvoir traiter de manière spécifique à la plateforme certaines opérations. Par exemple la gestion de fichiers, qui se fait en Ajax côté navigateur, et via l’API fs côté node. Il nous faut donc un moyen de détecter l’environnement. C’est chose plutôt facile puisqu’il suffit de tester l’existence de l’objet module :

if (typeof module !== 'undefined' && module.exports)
{
// code pour Node
}
else
{
// code pour navigateur
}

Le code spécifique node peut comporter des instructions require() et appeler les API standard. Cela ne pose pas de problème côté navigateur  : l’instruction require() sera juste considérée comme un appel de fonction syntaxiquement correct, et aucune vérification ne sera faite tant que ce code n’est pas exécuté. 

L’inverse est vraie pour le code spécifique navigateur, il sera par exemple possible d’utiliser des librairies comme jQuery. Il faut juste garder à l’esprit que les modules ES6 (instruction import) qui sont utilisés dans cette partie spécifique ne peuvent pas être chargés de manière conditionnelle à ce jour, bien que des propositions sont en cours de normalisation pour changer cela. Ils seront donc chargés dans les deux environnements quoi qu’il arrive.

Enfin, il faudra prendre garde à des librairies tierces non ES6 que vous pourriez être amenés à utiliser. Il pourrait s’avérer nécessaire de les encapsuler d’une manière ou d’une autre pour éviter certains problèmes ou erreurs d’exécution.

Voilà pour aujourd’hui. Cet article s’écarte un peu du sujet météo, mais cela illustre certaines problématiques que l’on peut rencontrer quand un projet se structure et grossit. J’espère que cela vous aura intéressé, ne vous inquiétez pas il y aura de quoi faire sur la modélisation numérique. En attendant, vous pouvez toujours aller (re)visiter les tutoriels sur le modèle Barotrope.