Les bonnes pratiques du développement d'applications spécifiques

[Dev Web] Comment découper votre code en module et le charger comme une librairie JavaScript indépendante ?

Posted by CHARLES BONNISSENT on 1 mars 2016 10:56:37
Find me on:

En 2016, les pages web deviennent de véritables applications. De plus, le JavaScript initialement cantonné à la partie client web, devient présent côté serveur (NodeJS). Le besoin de découpage du code en modules indépendants et testables se fait de plus en plus ressentir. Comment faire ?

Un bref historique du JavaScript


history.pngHistoriquement, le JavaScript a été mis en place pour animer avec peu de code des pages web simple.

Ce code servait pour apporter un peu de dynamique à un page staique.

Ce code était donc contenu soit directement dans le HTML, soit dans un fichier à part. 

Quelques années plus tard, les pages sont de plus en plus interactives et nécessitent de plus en plus de code JavaScript, deux design patterns émergent pour gérer ce cas :

 

  1. le fichiers, chaque partie de l'application est dans fichiers à part qui partagent des variables et des définitions globales, 

  2. le pattern module, l'ensemble du code est inclut dans une IIFE, une fonction englobe le module et permet de rendre certaines de ces parties privées, etc.


Avec l'avancée des techniques, les pages deviennent de véritables applications et utilisent des patterns de plus en plus inspirés de la programmation événementielle traditionnelle (MVC, MVVM, etc.).

De plus, le JavaScript initialement cantonné à la partie client web devient présent côté serveur (NodeJS), dans de l'embarqué, des applications mobiles, etc. 
Le besoin de découpage du code en modules indépendants testable se fait de plus en plus ressentir. 

Plusieurs standards de modules apparaissent :

  • commonJS ce standard définit des modules pour l'utilisation en dehors du browser,

  • AMD ce standard définit des modules chargés manière asynchrone dans le browser,

  • ES6 ce standard est directement inclut dans la version ES6 de JavaScript et décrit une manière de créer et de charger des modules. Il n'est actuellement pas encore disponible dans les navigateurs et nécessite un transpiler.

 

Le cas Dynacase : découpage AMD

amd.pngL'an dernier, chez Anakeen éditeur de Dynacase, nous avons refondu les interfaces utilisateurs (IHM) pour les moderniser.  

Lors de cette phase de refonte, nous avons privilégié les points suivant :

  • une conception modulaire par élément d'interface indépendant,

  • un système de test unitaire sur chacun des éléments,

  • la possibilité d'utiliser de manière indépendante chacun des éléments d'interface au sein d'un autre projet.


Pour ce faire, nous avons décidé d'utiliser les éléments suivants :

  • le pattern de widget factory de jQuery UI, chaque élément d'IHM est donc inclut dans jQuery et utilisable directement via la notation `$(selecteur).<element>(<parametre d'initialisation)`

  • le pattern AMD et un loader AMD : RequireJS pour gérer les dépendances entre les modules.

Chaque élément d'IHM est donc présenté de la manière suivante, un fichier `<nom_du_widget>.js` contenant :


Le système fonctionne très bien et a permis d'obtenir les résultats suivants :

  • chaque fichier contient un élément d'interface et un seul, ce qui permet de séparer les fonctionnalités et les responsabilité,

  • chaque élément d'interface possède une interface unifiée avec des fonctions commune (`setValue`, `getValue`, etc...),

  • les dépendances sont gérées automatiquement via RequireJS,

  • en production, les différents fichiers sont concaténés et minifiés en un seul fichier pour obtenir de meilleurs performances.

 

Une ombre au paradis, l'usage en dehors des interfaces standards

paradise.jpgUn problème subsiste,  si dans les interfaces standards de Dynacase nous pouvons inclure RequireJS de manière systématique reste que dans les interfaces réalisées par d'autres développeurs (nos clients développeurs ou nos partenaires intégrateurs), il est nécessaire de proposer un fonctionnement plus simple via l'intégration d'un seul fichier contenant l'ensemble des dépendances.

From AMD to UMD

Il est possible de définir les modules de tel manière que celui-ci soit utilisable
avec et sans le loader AMD.

Le pattern est le suivant :

Le module est alors chargé avec le pattern AMD (define, require) si celui là est disponible, sinon manuellement.

En utilisant, le mécanisme de concaténation des modules via RequireJS, il est possible de définir un seul fichier qui sera directement intégrable dans une page sans RequireJS.

 

From AMD to UMD avec des modules asynchrones


Certains modules sont asynchrones, c'est à dire que leur chargement nécessite des ressources qui sont disponibles après une requête asynchrone (XHR, promise, etc...).

Il est possible d'émuler le comportement asynchrone en utilisant le pattern suivant :



Le `$.when` permet d'attendre que l'ensemble des promises soient résolues pour charger le contenu du widget.

 

Conclusion

JavaScript est devenu un langage à part entière et comme pour tout langage mature, des techniques de développement émergent améliorant les précédentes.

Que ce soit pour un développement d'application métier spécifique à son entreprise, pour un client dans le cas d'un intégrateur ou lorsque l'on est un éditeur avec des contraintes exogènes non maitrisables, il est possible en utilisant les patterns adéquats d'avoir des modules qui sont utilisables aussi bien avec un loader AMD que sans, permettant un découpage propice à un projet maintenable et une extensibilité à posteriori.

 

Topics: Développement spécifique