open

Javascript : Import et export dans les modules ES6

Pour faire les exemples de ce tutoriel vous avez besoin d'un serveur que ce soit un serveur distant ou un serveur local tel que le Wamp.

#1 Des modules pour quoi faire ? open

On va parler ici des modules pour le code Javascript qui s'exécute du côté du navigateur. Ce sont les modules décrits dans la norme ECMAScript. Du côté serveur, Nodejs a son propre système de modularisation du code. Un système qui n'est donc pas dans la norme.

Au départ Javascript nous a permis de rendre nos documents interactifs. Ensuite on s'en est servi pour que, sans recharger le document, on puisse communiquer avec des serveurs. Les scripts sont devenus de plus en plus gros, ils sont devenus de véritables applications et aussi des librairies. On peut légitimement penser que les développeurs ont ressenti le besoin de structurer leur code. Pour faire cela un développeur commence par découper son application en plusieurs fichiers. Les modules Javascript ES6 ont adopté le principe d'un module par fichier. On va voir ce que les modules apportent de plus :

  1. Le mode strict est activé par défaut dans un module.
  2. Un module va avoir sa propre portée. Ca c'est capital. Ca veut dire que tout ce que vous allez déclarer dans le module sera par défaut local au module. Pour rendre visible une variable, une fonction ou une classe il faudra le dire, il faudra l'exporter. Cacher des informations dans un logiciel a toujours été nécessaire. Ca permet d'isoler les couches logicielles les unes des autres. Jusqu'à ES6, en Javascript on utilisait pour faire cela des closures qui étaient en fait un moyen détourné mais efficace.
  3. Un module vous permet de créer un espace de nommage. Lorsque vous importez des librairies ou lorsque vous travaillez à plusieurs développeurs sur une même application il arrive que vous rentriez en conflit avec une autre variable, fonction ou classe qui porte un nom que vous même vous utilisez. Avec les modules vous avez plusieurs façons de solutionner ce problème.
  4. Les modules vous simplifient le chargement de l'application. Généralement il vous suffira d'implanter un seul module "principal" au moyen d'une seule balise script dans votre html. Ce module "principal" ira chercher les autres modules et chargera l'ensemble de l'application.
  5. Vous pouvez charger un module de manière dynamique. Le module est chargé uniquement si une ressource du module est utilisée au moment de l'exécution de votre application. Cette possibilité pourra vous éviter de charger la totalité d'un gros code au premier chargement de votre page web.
  6. Vous pouvez charger un module de manière asynchrone.

#2 Un exemple pour expliquer le principe des modules open

Principe de l'export puis de l'import en Javascript
Principe de l'export puis de l'import en Javascript

Le principe est basé sur l'utilisation de deux mots réservés :

  1. Dans le Module A il faut utiliser le mot réservé export pour exporter une information. Ici x peut être une variable, une fonction ou une classe.
  2. Dans le Module B il faut utiliser le mot réservé import pour importer l'information qui a été exportée dans A.

Nous allons écrire un petit exemple à trois modules :

schéma de l'exemple
  1. tirage.js : il contient une fonction faireTirage(tab) qui prend en argument un tableau. La fonction est exportée. Pour cela il suffit d'écrire export devant function. Dans la fonction le tableau est parcouru et on procède à un tirage aléatoire pour affecter chaque valeur du tableau soit à true soit à false.
  2. affichage.js : il contient une fonction afficherResultat(tab) qui prend en argument un tableau. Ce tableau est affiché par un console.log(). La fonction est elle aussi exportée.
  3. main.js : dans ce module on importe les deux fonctions puis on déclare le tableau sur lequel on va travailler. Ensuite on appelle les deux fonctions en passant le tableau en argument. Pour importer les fonctions il faut écrire import suivi du nom de la fonction que l'on place entre une paire d'accolades. Ensuite on écrit from et on fait suivre par une chaîne de caractères qui contient le nom du fichier écrit en relatif. Commencez toujours vos chemins en relatif en citant le répertoire local avec ./. Notez qu'il n'est pas nécessaire d'exporter le tableau puisqu'il est passé en argument des fonctions.
  4. Pour finir on prend une structure minimale html pour implanter notre script. Notez que l'on se contente d'implanter main.js. C'est lui qui ira chercher les modules dont il a besoin pour les charger. Notez que c'est l'attribut type='module' qui informe le navigateur qu'il est en train de charger le code d'un module Javascript. En effet dans un fichier d'extension .js rien ne spécifie que le fichier contient le code d'un module.
Tester le code
Résultat de l'exécution

Ci-dessous voyons ce qui se passe lorsque l'on exécute le script. Avec le debugger on met un point d'arrêt dans chacune des fonctions des modules pour examiner le scope de l'application lorsque qu'elle est sur un point d'arrêt du module tirage puis lorsqu'elle est sur un point d'arrêt du module affichage. Vous constatez que chaque module a son propre scope (* sa propre unité locale de portée).

Ci-dessous une saisie d'écran de la console de Chrome lorsque le script est en pause sur un point d'arrêt dans le module contenu dans tirage.js.
Scope du module Tirage
Ci-dessous une saisie d'écran de la console de Chrome lorsque le script est en pause sur un point d'arrêt dans le module contenu dans affichage.js.
Scope du module Affichage

#3 Export d'une fonction open

Vous avez deux syntaxes à disposition pour exporter une fonction :

  1. Soit vous écrivez le mot réservé export devant la déclaration de la fonction.
  2. Soit vous écrivez export suivi du nom de la fonction placé entre une paire d'accolades.
  3. La syntaxe ne changera rien à la manière d'importer.

#4 Exports et imports groupés open

Vous pouvez faire toutes vos exportations de manière groupées en fin de fichier. C'est ce que je vous conseille. La démarche est logique. Une fois que vous avez terminé votre module vous décidez d'exporter ce que vous voulez.

Ci-dessous vous avez un exemple d'un module dans lequel je groupe l'export d'une variable, d'une fonction et d'une classe.
Arborescence de l'exemple

Dans la même logique vous pouvez grouper vos importations en début de fichier selon les besoins du module que vous allez coder.

N'oubliez pas que l'adresse du fichier ici './mod/module.js' est une adresse relative exprimée depuis l'emplacement du module main.js. Autrement dit c'est le chemin que main.js doit faire pour trouver module.js. Ici j'ai mis le fichier module.js dans un répertoire que j'ai appelé mod. Le chemin depuis main.js est donc './mod/module.js'.

Tester le code
Résultat de l'exécution

#5 Export et import par défaut. open

Vous avez droit à une et une seule exportation par défaut. Ca peut vous servir par exemple pour l'export d'une fonction anonyme.

Arborescence de l'exemple
Tester le code
Résultat de l'exécution

#6 Renommage des imports et des exports open

Vous pouvez avoir besoin de renommer pour résoudre des conflits de nommage. Ces conflits apparaissent le plus souvent lorsque vous utilisez une librairie ou lorsque vous travaillez à plusieurs développeurs sur un même projet.

Pour procéder à un renommage des imports vous n'avez pas besoin d'avoir le contrôle sur ce que vous importez. Je veux dire que c'est votre code que vous allez modifié par celui que vous importez. Ce qui fait que c'est la méthode la plus utilisée.

Je reprends l'exemple que j'avais utilisé pour expliquer le principe de l'import / export dans les modules au début du tutoriel. Je vais changer au moment de l'import le nom de la fonction faireTirage par celui de tirer. Pour cela j'écris import {faireTirage as tirer}. Je le fais dans main.js le fichier sur lequel j'ai la main. Je renomme également afficherResultat en afficher.Je ne touche ni à tirage.js ni à affichage.js. Donc si je n'avais pas la main sur tirage.js et affichage.js le renommage à l'import serait une solution en cas de conflit de nommage.

Tester le code

Pour procéder à un renommage des exports il faut que vous ayez la main sur le code dans lequel vous voulez justement placer vos exports. Ce n'est pas toujours le cas. Aussi vous serez souvent amené à plutôt renommer les imports avec la méthode que nous venons de voir. Toutefois ci-dessous je vous montre comment renommer des exports.

  1. Dans le fichier affichage.js j'ai placé une instruction export {afficherResultat as afficher}. Je renomme la fonction afficherResultat en afficher et j'exporte afficher.
  2. Dans le fichier tirage.js j'ai placé une instruction export {faireTirage as tirer}. Je renomme la fonction faireTirage en tirer et j'exporte tirer.
  3. Maintenant il me reste à importer dans main.js les deux fonctions en utilisant leurs nouveaux noms, tirer et afficher.
Tester le code

#7 Importer le module dans un objet open

Avec cette syntaxe, tous les exports sont récupérés et transformés soit en propriétés soit en méthodes qui doivent être préfixées par le nom du module. Ci-dessous j'ai exporté tout le contenu du fichier tirage.js dans un objet que j'ai appelé Tirage. J'en ai fait de même avec affichage.js dans un objet que j'ai appelé Affichage. Pour cela j'ai utilisé cette syntaxe import * as Tirage from './tirage.js'. Pareil pour affichage. Ensuite j'appelle mes fonctions en les préfixant avec le nom du module. Pour la fonction faireTirage ça donne Tirage.faireTirage. Je vérifie à la fin que Tirage est bien un objet avec un typeof de Tirage.

Résultat de l'exécution
Tester le code

#8 Charger un module dynamiquement open

Je vais ajouter un module supplémentaire que je vais charger en dernier et je vais le charger dynamiquement c'est à dire en cours d'exécution. Vous avez le code de ce module ci-dessous. Il fait juste un console.log d'une ligne de traits pointillés.

Je vais importer ce fichier dynamiquement dans main.js. Pour cela je mets en place une promesse.

Avec le debugger je mets en place un point d'arrêt sur le then de la promesse à la ligne 12 de main.js. Je lance l'exécution. Je vais ensuite dans l'onglet Network de la console je filtre et je coche JS. Vous pouvez constater sur l'image ci-dessous que seuls les fichiers tirage.js et affichage.js sont chargés. Le fichier soulignage.js lui n'est pas chargé.

Exécution au debugger avant point d'arrêt

Ensuite je reprends l'exécution et je passe le point d'arrêt et vous pouvez constatez sur l'image ci-dessous que maintenant le fichier soulignage.js est chargé. Vous profitez en fait de l'effet asynchrone de la promesse.

Exécution au debugger après point d'arrêt
Tester le code
Résultat final de l'exécution