Les Modules
Présentation
Avant l'arrivée des modules, on ajoutait plusieurs fichiers JavaScript directement dans le fichier HTML à l'aide de plusieurs balises "script". Chaque fichier était alors chargé sur la page.
Pour le navigateur, ce fonctionnement revenait presque à prendre le contenu de tous ces fichiers et à les copier-coller les uns à la suite des autres dans un seul grand fichier JavaScript.
Dans ce contexte, toutes les variables et fonctions se retrouvaient mélangées. Un fichier pouvait utiliser directement le contenu d'un autre simplement parce qu'ils étaient tous chargés sur la même page.
L'exemple suivant illustre cette logique. Même si les fichiers sont distincts, leur exécution se fait comme si leur contenu était enchaîné dans un seul script.
<!--
Les fichiers JavaScript sont chargés sur la page.
Du point de vue du navigateur, c'est presque comme
s'ils étaient copiés-collés les uns à la suite des autres.
-->
<script src="./js/script-02.js" defer></script>
<script src="./js/script-01.js" defer></script>
Dans ce fonctionnement, script-01.js peut utiliser directement les variables ou fonctions définies dans script-02.js. Il n'y a aucune séparation réelle entre les fichiers.
Ce comportement peut être pratique pour de petits scripts, mais il devient rapidement problématique lorsque le projet grandit. Le code devient difficile à suivre, et une modification dans un fichier peut avoir des effets inattendus dans un autre.
Les modules ont été introduits pour résoudre ce problème. Ils permettent de découper le code en plusieurs fichiers tout en évitant ce mélange automatique.
Avec les modules, le projet est mieux organisé. Chaque fichier correspond à une partie bien définie du programme, ce qui rend le code plus lisible et plus facile à maintenir.
Dans ce type d'organisation, on utilise généralement un fichier principal, souvent nommé app.js, qui sert de point d'entrée et rassemble les différentes parties du programme.
Comportement automatique des modules
Utiliser des modules ne change pas seulement l'organisation du code. Cela modifie aussi la façon dont le navigateur exécute le JavaScript.
Deux comportements importants sont activés automatiquement lorsqu'un fichier est chargé en tant que module : le chargement différé et le mode strict.
Chargement différé
Sans précaution particulière, une balise script bloque le chargement de la page. Lorsque le navigateur rencontre le script, il interrompt l'analyse du HTML, télécharge le fichier JavaScript si nécessaire, exécute le code, puis reprend l'analyse du HTML.
Ce comportement posait deux problèmes :
- La page mettait plus de temps à s'afficher ;
- Le JavaScript pouvait s'exécuter alors que certains éléments HTML n'étaient pas encore disponibles.
Pour éviter cela, on utilisait l'attribut defer.
<script src="script.js" defer></script>
Avec defer, le navigateur télécharge les fichiers JavaScript en parallèle du HTML, sans bloquer son analyse. Les scripts ne sont exécutés qu'une fois le HTML entièrement analysé.
Lorsque plusieurs scripts utilisent defer, ils sont exécutés dans l'ordre dans lequel ils apparaissent dans le HTML. Cela permet de gérer correctement les dépendances entre fichiers.
Les modules utilisent automatiquement ce comportement. Les fichiers JavaScript sont téléchargés sans bloquer le HTML et leur exécution est différée jusqu'à la fin de l'analyse du document. Il n'est donc pas nécessaire d'ajouter explicitement defer lorsqu'on utilise type="module".
Mode strict
Le mode strict modifie certaines règles de JavaScript afin de rendre le code plus rigoureux et plus prévisible. Il empêche des comportements ambigus qui, sans cela, peuvent produire des bugs difficiles à comprendre.
Par exemple, le mode strict :
- interdit l'utilisation de variables non déclarées,
- évite la création involontaire de variables globales,
- signale immédiatement certaines erreurs qui passaient auparavant inaperçues.
Avant l'arrivée des modules, il fallait activer ce mode explicitement en ajoutant la directive "use strict" en début de fichier.
"use strict";
x = 5; // Provoque l'erreur : ReferenceError x is not defined
Dans cet exemple, la variable x est utilisée sans avoir été déclarée. En mode strict, ce type d'écriture provoque immédiatement une erreur.
Sans le mode strict, ce code ne provoque pas d'erreur. JavaScript crée alors automatiquement une variable globale, accessible depuis n'importe quel fichier du projet.
Le problème est que cette variable globale est créée sans que le développeur l'ait voulu. Elle peut être modifiée ailleurs dans le code, écrasée par une autre variable portant le même nom, ou influencer le comportement du programme de manière imprévisible.
Ce type de variable globale involontaire est particulièrement dangereux, car il ne génère aucun message d'erreur et peut provoquer des bugs difficiles à comprendre. Le mode strict empêche précisément ce genre de situation en signalant l'erreur immédiatement.
Le fait que le mode strict génère une erreur ici est volontaire et bénéfique. Il empêche une mauvaise pratique dès le départ et pousse vers un code plus clair.
Avec les modules, le mode strict est activé automatiquement. Il n'est donc plus nécessaire d'écrire "use strict" manuellement.
Tester des modules en local
Pour tester un projet avec des modules, ouvrir index.html en double-cliquant ne suffit généralement pas. Dans ce cas, la page est chargée via une adresse qui commence par file:///.
Sur le web, le navigateur identifie une page par son origine (ex.: http://localhost:8000 ou http://mondomaine.test).
Une origine est définie par trois éléments :
- le protocole (ex.: "http"),
- le nom de domaine (ex.: "localhost", "mondomaine.test"),
- le port (ex.: 8000).
Lorsque ces trois éléments sont identiques, les ressources sont considérées comme provenant de la même origine. Dans ce cas, le navigateur n'applique pas de restrictions liées aux accès entre origines.
Par défaut, le navigateur applique la SOP (Same-Origin Policy). Une page web ne peut pas, via JavaScript, accéder librement aux réponses provenant d'une autre origine, sauf si l'accès est explicitement autorisé.
CORS (Cross-Origin Resource Sharing) est le mécanisme qui permet, dans certains cas, d'assouplir cette règle. Lors d'une requête entre origines, le navigateur indique l'origine de la page au serveur de destination. Le serveur peut alors autoriser ou refuser l'accès en fonction de sa configuration.
Avec file:///, la page ne possède pas une origine HTTP classique. Le navigateur la traite comme une origine opaque, souvent null, et bloque par sécurité certains chargements, dont le chargement de modules via import.
Sans ces protections, une page locale pourrait tenter d'accéder à d'autres fichiers du disque ou d'interagir avec des services web sans le consentement de l'utilisateur.
La solution consiste à servir le projet via un serveur web local. La page est alors accessible via une adresse comme http://localhost, ce qui définit une origine claire.
Lorsque la page et les modules sont servis depuis cette même origine HTTP, le navigateur peut charger les imports normalement.
Exemple
Sans serveur :
file:///C:/projet/index.html
→ import souvent bloqué
Avec serveur local :
http://localhost:8000/index.html
→ import autorisé
La solution est simple. Il faut afficher le projet via un serveur web local afin d'obtenir une adresse du type http://localhost.
Trois solutions courantes :
- Solution 1 : utiliser un environnement local complet comme Laragon, Wamp ou XAMPP.
- Solution 2 : utiliser le serveur intégré de VS Code avec l'extension Live Server.
- Solution 3 : lancer un petit serveur en ligne de commande dans le dossier du projet :
# Dans le dossier du projet
# Si PHP est installé
php -S localhost:8000
# Si Python est installé
python -m http.server 8000
Ensuite, ouvrez votre navigateur à l'adresse correspondante (ex.: http://localhost:8000) et non via file:///.
Point d'entrée et premier import
Un module JavaScript est un fichier JavaScript. Ce qui change, ce n'est pas l'extension ".js", mais la manière dont le navigateur gère le partage du code entre fichiers.
Dans le fichier HTML, on charge généralement un seul script avec type="module". Le nom du fichier n'a pas d'importance pour le navigateur, mais on l'appelle souvent "app.js", "main.js" ou "index.js" par convention, car il sert de point d'entrée.
On l'appelle point d'entrée car c'est le premier fichier exécuté. C'est lui qui va ensuite importer les autres fichiers nécessaires. Ces fichiers n'ont donc pas besoin d'etre écrits dans le HTML.
Exemple
Structure de l'application :
projet/
|-- index.html
└── js/
|-- app.js
└── modules/
└── salutations.js
Le projet est composé d'un fichier HTML, d'un fichier JavaScript principal, et d'un dossier qui contient les modules.
Fichier (./index.html) :
<!-- Un seul script, le point d'entrée -->
<script type="module" src="./js/app.js"></script>
Le fichier HTML ne contient qu'une seule balise script. Ce fichier correspond au point d'entrée de l'application. C'est lui qui démarre le JavaScript et qui importera les modules dont il a besoin.
Fichier (./js/modules/salutations.js) :
function direAurevoir(prenom)
{
console.log(`Au revoir ${prenom} !`);
}
function direBonjour(prenom)
{
console.log(`Bonjour ${prenom} !`);
}
/*
Exportation explicite des éléments que le module accepte de partager.
*/
export { direBonjour, direAurevoir };
Ce fichier est un module. Il contient du code JavaScript classique. Par défaut, son contenu est isolé et n'est pas accessible depuis les autres fichiers.
Pour rendre certaines fonctions utilisables ailleurs, on les exporte explicitement. Ici, seules les fonctions direBonjour et direAurevoir sont rendues accessibles à l'extérieur du module.
Il existe plusieurs façons d'exporter du code depuis un module, mais la forme la plus claire et la plus courante pour débuter est l'exportation nommée.
Fichier (./js/app.js) :
import { direBonjour } from "./modules/salutations.js";
direBonjour("Sayid"); // Affiche : Bonjour Sayid !
Dans le point d'entrée, on importe uniquement ce dont on a besoin. Ici, seule la fonction direBonjour est importée, même si le module met aussi à disposition direAurevoir.
Chemins d'import
Lorsqu'un programme charge un fichier, il doit toujours indiquer précisément où se trouve ce fichier dans l'arborescence. Le navigateur, comme n'importe quel autre environnement, ne peut pas deviner l'emplacement d'un fichier.
En JavaScript, comme dans d'autres langages, il existe deux grandes façons d'écrire un chemin, les chemins relatifs et les chemins absolus.
Chemins relatifs (conseillés)
Un chemin relatif est interprété à partir du fichier qui écrit l'import. Le point de départ est donc toujours le fichier JavaScript courant.
- ./ désigne le dossier courant.
- ../ désigne le dossier parent.
Ce type de chemin reflète directement la structure réelle des dossiers. Il rend les dépendances explicites et facilite le déplacement ou la réorganisation du projet.
Exemple : le fichier racineDuProjet/js/app.js souhaite importer racineDuProjet/js/modules/salutations.js. Depuis app.js, le chemin correct est donc "./modules/salutations.js".
Chemins absolus
Un chemin absolu commence par /. Il est interprété à partir de la racine du serveur web, et non du projet JavaScript.
Par exemple, "/js/modules/salutations.js" pointe vers js/modules/salutations.js à partir de la racine du site.
Ce type de chemin peut fonctionner, mais il rend le projet plus fragile. Un changement de configuration serveur ou de dossier racine peut casser tous les imports. Pour cette raison, les chemins relatifs sont généralement préférés dans un contexte pédagogique et local.
Erreurs courantes liées aux imports
La majorité des problèmes rencontrés avec les modules proviennent d'erreurs de chemin. Ces erreurs empêchent le module de se charger correctement.
-
Oublier les indicateurs de chemin relatif ./ ou ../
Exemple invalide "modules/salutations.js"
Exemple correct "./modules/salutations.js" -
Se tromper de dossier
Le chemin relatif est interprété depuis le fichier qui écrit l'import, pas depuis index.html. Déplacer un fichier implique de corriger les imports. -
Oublier l'extension .js
Dans le navigateur, le nom du fichier doit être écrit en entier. L'autocomplétion de VS Code peut parfois proposer un import sans extension, donc vérifie ce point si un import échoue. -
Confondre erreur de chemin et erreur de code
Chemin incorrect : le module ne se charge pas.
Chemin correct mais code erroné : le module se charge puis échoue à l'exécution.
En cas de problème, le bon réflexe consiste à ouvrir la console du navigateur, lire le message d'erreur, puis vérifier le chemin et l'arborescence réelle du projet.
Organiser son code avec des modules
Utiliser des modules ne consiste pas seulement à séparer le code en plusieurs fichiers. L'objectif est surtout de rendre le projet plus clair, plus lisible et plus facile à faire évoluer.
Un module doit répondre à une idée simple. Il regroupe du code qui a un rôle précis dans l'application.
On peut voir un module comme une petite boîte. Elle contient tout ce qui est nécessaire pour accomplir une tâche donnée, et elle expose uniquement ce qui est utile aux autres parties du programme.
Un module, une responsabilité
De manière générale, un module devrait avoir une responsabilité principale. Il s'occupe d'un aspect précis du programme et évite de mélanger des rôles différents.
Par exemple :
- un module pour gérer des salutations ou des messages,
- un module pour la logique d'un formulaire,
- un module pour des calculs ou des traitements de données,
- un module pour manipuler des éléments du DOM.
Ce découpage permet de savoir rapidement où se trouve une fonctionnalité et d'éviter les fichiers trop longs ou trop confus.
Éviter les modules fourre-tout
Un piège courant consiste à créer un seul module qui contient un peu de tout. Ce type de fichier devient rapidement difficile à lire et à maintenir.
Si un module commence à contenir des fonctions qui n'ont plus vraiment de lien entre elles, c'est souvent un signal qu'il devrait être découpé en plusieurs modules plus spécialisés.
Éviter aussi une découpe excessive
À l'inverse, découper le code en trop de petits modules peut également poser problème. Si chaque module ne contient qu'une ou deux lignes de code, le projet devient difficile à suivre.
L'objectif est de trouver un équilibre. Un module doit être suffisamment petit pour rester lisible, mais suffisamment complet pour avoir un sens.
Chaîne d'importation
Le point d'entrée, souvent nommé app.js, main.js ou index.js, ne contient généralement pas toute la logique du programme.
Son rôle principal est de démarrer l'application et d'importer les premiers modules nécessaires. Il ne sert pas à centraliser tout le code.
Chaque module reste responsable de son propre fonctionnement et peut importer lui-même les autres modules dont il a besoin. Le point d'entrée se contente de lancer l'ensemble.
On peut comparer ce rôle à celui d'un chef d'orchestre. Il ne joue pas chaque instrument, mais il donne le signal de départ et coordonne l'ensemble.
Cette organisation rend le projet plus lisible et plus souple. Chaque module fait son travail à son niveau, et le point d'entrée assemble l'application sans entrer dans les détails internes.
Exemple
Dans un projet bien organisé, chaque module peut importer directement les autres modules dont il a besoin pour fonctionner. Le point d'entrée se contente alors de coordonner les modules et de lancer l'application.
L'exemple suivant illustre une petite chaîne d'importation entre plusieurs modules, ainsi qu'un point d'entrée qui fait interagir plusieurs parties du programme.
Structure de l'application :
projet/
|-- index.html
└── js/
|-- app.js
└── modules/
|-- afficherMessage.js
|-- monterMessageBonjour.js
└── formaterMessage.js
- formaterMessage.js : contient une fonction formaterMessage() chargée de recevoir un texte en paramètre et de retourner ce texte sous une forme formatée. Ce module est autonome et ne dépend d'aucun autre fichier.
- monterMessageBonjour.js : contient une fonction monterMessageBonjour() chargée de recevoir un prénom en paramètre et de construire un message de salutation. Pour cela, elle importe la fonction formaterMessage() depuis formaterMessage.js. Ce module dépend donc d'un autre module pour remplir son rôle.
- afficherMessage.js : contient une fonction afficherMessage() chargée de recevoir un message en paramètre et de l'afficher dans la console. Ce module ne s'occupe pas de la construction du message, uniquement de son affichage.
- app.js : est le point d'entrée de l'application. Il importe les modules monterMessageBonjour() et afficherMessage(), orchestre leur utilisation, puis déclenche l'exécution du programme.
Cette organisation montre que le point d'entrée ne fait pas tout. Chaque module reste responsable de sa propre logique et importe uniquement ce dont il a besoin.
Fichier (./js/modules/formaterMessage.js) :
function formaterMessage(texte)
{
return `>>> ${texte} <<<`;
}
export { formaterMessage };
Ce module est totalement autonome. Il fournit une fonction utilitaire réutilisable.
Fichier (./js/modules/monterMessageBonjour.js) :
import { formaterMessage } from "./formaterMessage.js";
function monterMessageBonjour(prenom)
{
return formaterMessage(`Bonjour ${prenom} !`);
}
export { monterMessageBonjour };
Ce module dépend de formaterMessage.js. Il ne s'occupe pas de l'affichage, uniquement de la construction du message.
Fichier (./js/modules/afficherMessage.js) :
function afficherMessage(message)
{
console.log(message);
}
export { afficherMessage };
Ce module est responsable de l'affichage. Il ne connaît ni la logique de salutation ni le formatage du message.
Fichier (./js/app.js) :
import { afficherMessage } from "./modules/afficherMessage.js";
import { monterMessageBonjour } from "./modules/monterMessageBonjour.js";
const messageBonjour = monterMessageBonjour("Marie");
afficherMessage(messageBonjour);
Le point d'entrée importe deux modules distincts. Il utilise l'un pour construire un message, puis l'autre pour l'afficher.
app.js reste lisible et joue pleinement son rôle de chef d'orchestre. Chaque module gère ses propres dépendances, et l'ensemble du programme se construit à partir de ces interactions.
Remarque importante
Pour un projet aussi petit que celui-ci, il serait tout à fait possible d'écrire l'ensemble du code dans un seul fichier JavaScript. L'application fonctionnerait correctement et l'utilisation de modules ne serait pas strictement nécessaire.
Ici, le découpage en plusieurs modules est volontaire. Il ne répond pas à une contrainte technique, mais à un objectif pédagogique.
Cet exemple permet de mettre en évidence deux points importants :
- La séparation des responsabilités : chaque module s'occupe d'un rôle précis. L'un construit un message, un autre le formate, un autre encore se charge de l'affichage. Aucun fichier ne fait tout.
- La dépendance entre modules : certains modules s'appuient sur d'autres pour fonctionner. Ils importent uniquement ce dont ils ont besoin, sans connaître l'ensemble du projet.
Cette organisation est volontairement simple, mais elle illustre exactement la logique utilisée dans des projets plus complexes, où ce découpage devient indispensable pour garder un code lisible et maîtrisable.
Encapsulation d'un module
Un module ne sert pas uniquement à découper le code en plusieurs fichiers. Il permet aussi de contrôler précisément ce qui est accessible depuis l'extérieur.
Par défaut, tout le contenu d'un module est isolé. Les autres fichiers ne peuvent pas accéder librement à ses variables ou à ses fonctions. Seuls les éléments exportés explicitement deviennent utilisables ailleurs.
Ce principe s'appelle l'encapsulation. Le module protège son fonctionnement interne et expose uniquement ce qu'il accepte de partager.
Exemple
Fichier (./js/modules/salutations.js) :
const personnageParDefaut = "Claudy";
function direBonjour(prenom = personnageParDefaut)
{
console.log(`Bonjour ${prenom} !`);
}
export { direBonjour };
Dans ce module, la constante personnageParDefaut n'est pas exportée. Elle reste donc interne au module. En revanche, la fonction direBonjour est exportée et peut être utilisée ailleurs.
Fichier (./js/app.js) :
import { direBonjour } from "./modules/salutations.js";
direBonjour(); // Affiche : Bonjour Claudy !
console.log(personnageParDefaut); // ReferenceError: personnageParDefaut is not defined
La fonction direBonjour fonctionne correctement, car elle provient du module et a accès à son contenu interne.
En revanche, la constante personnageParDefaut n'est pas connue dans ce fichier. Elle n'a pas été exportée, elle ne peut donc pas être utilisée directement dans app.js.
Ce comportement est volontaire. Il empêche les autres fichiers de dépendre de détails internes du module et limite les erreurs liées à des accès non contrôlés.
L'encapsulation permet ainsi d'utiliser un module sans avoir besoin de connaître son fonctionnement interne, ce qui rend le code plus clair, plus robuste et plus facile à maintenir.
Encapsulation et état interne
L'encapsulation ne sert pas seulement à "cacher" des variables. Elle permet aussi de garder un état interne dans le module, sans l'exposer directement.
Un point important pour comprendre la suite. Dans le navigateur, un module est chargé une seule fois. Cela signifie que ses variables internes restent en mémoire tant que la page est ouverte.
Quand plusieurs fichiers importent le même module, ils ne créent pas chacun une copie séparée. Ils utilisent tous le même module déjà chargé. L'état interne est donc partagé via ce module, sans passer par des variables globales.
L'exemple suivant illustre ce principe avec un compteur interne. La variable compteur n'est pas exportée, donc elle reste inaccessible depuis l'extérieur. Par contre, deux fonctions exportées permettent de la modifier et de lire sa valeur.
Fichier (./js/modules/compteur.js) :
let compteur = 0;
function incrementer()
{
compteur++;
}
function afficherCompteur()
{
console.log(`Compteur = ${compteur}`);
}
export { incrementer, afficherCompteur };
Ici, compteur reste interne au module. Aucun autre fichier ne peut écrire console.log(compteur) ou modifier directement sa valeur, car elle n'est pas exportée.
Par contre, les fonctions incrementer et afficherCompteur sont exportées. Elles ont donc le droit d'être utilisées ailleurs, et elles gardent accès à la variable interne du module.
Fichier (./js/app.js) :
import { incrementer, afficherCompteur } from "./modules/compteur.js";
afficherCompteur(); // Affiche : Compteur = 0
incrementer();
incrementer();
afficherCompteur(); // Affiche : Compteur = 2
Ce qui est intéressant ici, c'est que la valeur du compteur reste à 2 après les deux appels. Elle ne revient pas à 0, car le module n'est pas recréé à chaque import ou à chaque appel de fonction.
Autrement dit, l'état interne du module persiste et évolue au fil de l'exécution. C'est utile pour stocker des informations internes à un module, tout en empêchant les autres fichiers de les modifier n'importe comment.
Ce mécanisme est puissant, mais il demande aussi de la rigueur. Si un module garde un état interne, il faut savoir que cet état peut influencer la suite du programme. En pratique, on réserve souvent ce type de logique à des modules qui ont réellement besoin de mémoriser quelque chose entre plusieurs appels.
L'idée importante reste la suivante. Un module peut exposer des fonctions publiques et conserver des données privées. L'extérieur ne manipule pas directement ces données, mais il peut passer par des fonctions prévues pour cela. C'est exactement le but de l'encapsulation.
Autres façons d'exporter et d'importer
Jusqu'ici, les exemples ont déjà utilisé la forme la plus courante pour débuter avec les modules.
Un module exporte explicitement certains éléments, puis un autre fichier importe uniquement ce dont il a besoin grâce à un import nommé, par exemple :
import { direBonjour } from "./modules/salutations.js";
Cette logique reste la base. La suite élargit simplement les possibilités, car JavaScript propose plusieurs syntaxes selon le type de module écrit et selon les conventions du projet.
Rappel sur les exports nommés
Un export nommé conserve le nom exact de ce qui est exporté. Cette forme est souvent privilégiée pour apprendre, car elle impose une correspondance stricte entre ce qui est exporté et ce qui est importé.
Fichier (./js/modules/salutations.js) :
function direBonjour(prenom)
{
console.log(`Bonjour ${prenom} !`);
}
function direAurevoir(prenom)
{
console.log(`Au revoir ${prenom} !`);
}
export { direBonjour, direAurevoir };
Fichier (./js/app.js) :
import { direBonjour, direAurevoir } from "./modules/salutations.js";
direBonjour("Marie"); // Affiche : Bonjour Marie !
direAurevoir("Marie"); // Affiche : Au revoir Marie !
Notez que les noms entre accolades doivent correspondre exactement aux noms exportés par le module. Une différence de casse, une faute de frappe, ou un nom qui n'existe pas provoque une erreur d'import.
Renommer lors de l'import (alias)
Lorsqu'un module est importé, le nom utilisé dans le fichier courant doit être unique. Or, dans un projet réel, plusieurs modules peuvent parfaitement exposer des fonctions portant le même nom, parce qu'elles remplissent une responsabilité similaire dans des contextes différents.
Pour gérer ce type de situation, JavaScript permet de renommer un export au moment de l'import. Ce renommage crée ce que l'on appelle un alias.
Un alias est simplement un autre nom donné localement à un élément importé. Il ne modifie pas le module d'origine et n'a d'effet que dans le fichier qui réalise l'import.
Cette mécanique est particulièrement utile dans deux cas fréquents :
- éviter un conflit de noms lorsque plusieurs modules exportent un élément portant le même nom,
- rendre le rôle de la fonction plus explicite dans le contexte métier du fichier courant.
Imaginons une application qui gère des utilisateurs. Selon le contexte, la validation des données ne suit pas les mêmes règles.
Un module s'occupe de la validation lors de l'inscription d'un utilisateur, un autre de la validation côté administration. Les deux modules exportent logiquement une fonction nommée validerDonnees.
Fichier (./js/modules/validationUtilisateur.js) :
function validerDonnees(donnees)
{
// Logique de validations des données utilisateur.
}
export { validerDonnees };
Fichier (./js/modules/validationAdmin.js) :
function validerDonnees(donnees)
{
// Logique de validations des données admin.
}
export { validerDonnees };
Dans un fichier qui doit utiliser les deux logiques, importer ces fonctions sans précaution provoquerait un conflit de noms.
La solution consiste à créer des alias à l'import à l'aide du mot-clé as.
Fichier (./js/app.js) :
import { validerDonnees as validerUtilisateur } from "./modules/validationUtilisateur.js";
import { validerDonnees as validerAdmin } from "./modules/validationAdmin.js";
validerUtilisateur({ email: "marie@test.be", motDePasse: "secret" });
validerAdmin({ email: "admin@test.be", role: "SUPER_ADMIN" });
Dans cet exemple :
- les deux modules continuent d'exporter une fonction nommée validerDonnees,
- chaque import crée un alias qui explicite le contexte d'utilisation,
- le fichier reste lisible et sans ambiguïté.
Le module d'origine reste inchangé. Seul le nom utilisé localement dans le fichier courant est modifié. C'est précisément le rôle d'un alias.
Importer tous les exports nommés dans un objet (namespace import)
Il existe une autre forme d'import, basée sur l'astérisque *. Elle permet de regrouper tous les exports nommés d'un module dans un seul objet. On parle souvent d'import namespace.
L'intérêt est double. D'abord, cela évite d'introduire beaucoup de noms différents dans le fichier courant. Ensuite, l'origine des fonctions et constantes reste visible en permanence grâce à un préfixe.
Exemple :
Fichier (./js/modules/salutations.js) :
function direBonjour(prenom)
{
console.log(`Bonjour ${prenom} !`);
}
function direAurevoir(prenom)
{
console.log(`Au revoir ${prenom} !`);
}
export { direBonjour, direAurevoir };
Fichier (./js/app.js) :
import * as salutations from "./modules/salutations.js";
salutations.direBonjour("Marie");
salutations.direAurevoir("Marie");
Ici, salutations est un objet namespace. Il contient des propriétés correspondant aux exports nommés du module, par exemple salutations.direBonjour.
Ce style est utile quand un module expose beaucoup d'outils, typiquement un module "utils", ou lorsque l'on veut rendre l'origine des appels très explicite. C'est aussi une solution simple pour éviter des conflits de noms sans multiplier les alias.
L'objet namespace n'est pas un objet "normal" que l'on modifie. Ses propriétés représentent les exports du module et sont traitées comme en lecture seule côté import. L'objectif est d'utiliser ce que le module expose, pas de le reconfigurer depuis l'extérieur.
Enfin, si un module possède aussi un export par défaut, celui-ci apparaît généralement sous la propriété default de l'objet namespace, ce qui peut rendre le code moins lisible. Dans ce cas, on préfère souvent un import par défaut classique, ou un import nommé.
Export par défaut
Un module peut définir un export par défaut. Il s'agit d'un export spécial, limité à un seul par fichier.
L'intérêt principal est simple. Certains modules ne servent qu'à fournir un élément central, par exemple une fonction principale, une classe, ou un composant. Dans ce cas, l'export par défaut permet d'exprimer clairement que ce fichier existe surtout pour "fournir cet élément-là".
Cette pratique est courante, mais elle dépend fortement des conventions d'équipe et du type de projet. Dans beaucoup de codebases modernes, on voit les deux styles. Certaines équipes privilégient les exports nommés pour leur cohérence, d'autres utilisent l'export par défaut pour les modules à responsabilité unique.
Exemples où l'export par défaut est souvent pertinent :
- un module qui expose une seule fonction principale,
- un module qui expose une seule classe principale,
- un module qui expose un composant principal dans un framework.
Exemple :
Fichier (./js/modules/afficherMessage.js) :
export default function afficherMessage(message)
{
console.log(message);
}
Fichier (./js/app.js) :
import afficherMessage from "./modules/afficherMessage.js";
afficherMessage("Hello");
Différence avec un export nommé
- l'import ne contient pas d'accolades,
- le nom utilisé à l'import est libre par défaut, car il n'est pas lié à un identifiant exporté.
Cette liberté explique à la fois l'intérêt et la limite du mécanisme.
Pourquoi choisir un export par défaut
Quand un module n'expose qu'une seule chose importante, la lecture devient fluide.
On importe directement "la chose principale" du fichier, sans se demander quel nom exact a été exporté.
Pourquoi rester prudent
Comme le nom est libre, un même module peut être importé sous des noms différents selon les fichiers.
Le programme fonctionne toujours, mais la lecture devient moins cohérente.
Deux développeurs peuvent parler du même élément avec deux noms différents, ce qui complique la compréhension.
Exemple : ces deux imports sont valides, mais ils introduisent deux noms différents pour désigner exactement la même chose.
import afficherMessage from "./modules/afficherMessage.js";
import logMessage from "./modules/afficherMessage.js";
En pratique, ce point se gère par convention. Deux approches fréquentes existent :
- Utiliser l'export par défaut, mais imposer une règle de nommage cohérente à l'import (par exemple, reprendre le nom du fichier).
-
Privilégier les exports nommés pour vos propres modules.
Cela force mécaniquement un nom identique partout et facilite la navigation dans le code.
De plus, les outils de refactoring (renommage automatique dans VS Code) fonctionnent beaucoup mieux avec cette approche. Si l'élément exporté change de nom dans le fichier source, VsCode ne mettra à jour le nom dans les fichiers qui l'importent qu'avec les exportations nommées.
Le choix dépend du contexte, mais la règle la plus importante reste la même. Une équipe gagne surtout en lisibilité en étant cohérente et en appliquant la même convention partout.
Mélanger export par défaut et exports nommés
Un module peut combiner un export par défaut et des exports nommés.
Cas typique. Le module expose un élément principal, mais fournit aussi quelques outils secondaires.
Fichier (./js/modules/exemple.js) :
export default function principal()
{
console.log("Fonction principale");
}
function secondaire()
{
console.log("Fonction secondaire");
}
export { secondaire };
Fichier (./js/app.js) :
import principal, { secondaire } from "./modules/exemple.js";
principal();
secondaire();
Ici, la lecture est assez naturelle.
- principal : correspond à l'élément central du module, importé sans accolades.
- secondaire : correspond à un outil supplémentaire, importé explicitement entre accolades.
Exercices
Exo-modules-01
Objectif : Mettre en place un projet minimal qui charge un script en mode module et vérifier que le JavaScript s'exécute correctement dans le navigateur via un serveur local.
Instructions :
-
Créer un dossier nommé exo-modules-01, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-01/ |-- index.html └── js/ └── app.js - Dans index.html, générer le code HTML minimal (par exemple via le snippet html:5 dans VS Code), puis compléter le title et le h1 avec le texte "JS: Exo Modules 01".
-
Dans index.html, charger uniquement ./js/app.js avec type="module" :
- app.js correspond au point d'entrée du script. Ce fichier centralise le démarrage du code et les imports des modules placés dans ./js/modules/.
- Le passer en module permet d'utiliser import et export. Cela garantit aussi que le fichier est traité comme un module JavaScript, avec une portée locale, un chargement différé, et le mode strict activé par défaut.
- Dans ./js/app.js, ajouter une instruction qui affiche "hello module !" dans la console, afin de vérifier que le fichier est bien chargé et exécuté.
- Ouvrir le projet en double-cliquant sur index.html afin de l'exécuter directement dans le navigateur par défaut (l'adresse dans la barre d'URL devrait commencer par file:///...).
-
Ouvrir ensuite la console et observez le message d'erreur lié au chargement des modules :
- Ce message signifie que le navigateur ne considère pas index.html et ./js/app.js comme étant sur la même origine (le même site). Pour vérifier une "même origine", le navigateur compare normalement protocole + domaine + port. Or, en file:///, il n'y a pas de domaine ni de port HTTP, donc la comparaison complète n'est pas possible et le chargement du module est bloqué par la politique Same Origin.
- Cette politique existe pour isoler les sites entre eux et éviter qu'une page puisse accéder aux ressources ou aux données d'un autre site sans autorisation (ex.: réponses d'API protégées par une session).
-
Lancer ensuite un serveur local dans le dossier du projet, comme vu dans le cours
que ce :
- soit via un outil type Laragon, XAMPP, ou équivalent,
- soit via la commande php lancée dans un terminal à la racine du projet, comme vu dans le cours php -S localhost:8000,
- soit via l'extension Live Server de VS Code (bouton Go Live).
-
Rouvrir ensuite la page via une adresse http://... correspondant au serveur démarré :
- soit via Laragon ou XAMPP, en utilisant http://localhost/exo-modules-01,
- soit via Laragon avec les virtual hosts activés, en utilisant le domaine local généré automatiquement, en utilisant http://exo-modules-01.test/,
- soit via la commande PHP, en utilisant http://localhost:8000/,
- soit via l'extension Live Server de VS Code, en utilisant l'URL ouverte automatiquement.
- Vérifier que l'erreur précédente disparaît et que le message "Hello module !" s'affiche bien dans la console.
Exo-modules-02
Objectif : Créer une petite règle réutilisable qui vérifie si une personne est majeure, puis l'utiliser depuis le point d'entrée pour afficher un message adapté dans la console.
Instructions :
-
Créer un dossier nommé exo-modules-02, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-02/ |-- index.html └── js/ |-- app.js └── modules/ └── vérifierMajorite.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 02".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
- Ouvrir la page via le serveur local, puis ouvrir la console.
-
Dans ./js/modules/vérifierMajorite.js, créer une fonction fléchée nommée
estMajeure.
- Cette fonction reçoit un seul paramètre nommé age. Ce paramètre est un nombre entier.
-
Cette fonction retourne un booléen :
- si la personne est majeure, la fonction retourne true
- sinon, la fonction retourne false
- La fonction doit être exportée avec un export nommé.
- Dans ./js/app.js, importer la fonction estMajeure à partir du module vérifierMajorite.js.
-
Avant d'aller plus loin, retourner dans le navigateur et rafraîchir la page du projet.
Vérifier dans la console que l'import ne provoque aucune erreur :
- Si un message du type suivant apparaît : Le chargement du module ... a été bloqué en raison d'un type MIME interdit ("text/html"), c'est probablement que le chemin d'import est incorrect. Le cas le plus fréquent est l'oubli de l'extension .js du chemin vers le module à importer, notamment après une autocomplétion de VS Code.
- Dans ./js/app.js, Déclarer une constante nommée age et l'initialiser à 18.
-
Utiliser ensuite la fonction estMajeure() en lui passant la valeur de la constante age pour décider du message à afficher.
- Si le résultat est true, afficher le message suivant : Vous avez [age] an(s), vous êtes donc majeur !
- Si le résultat est false, afficher le message suivant : Vous avez [age] an(s), vous êtes donc mineur !
- Dans les deux cas, remplacer [age] par la valeur de la constante age.
- Tester avec différentes valeurs pour la constante age afin de vérifier que le message affiché s'adapte correctement selon le cas.
Exo-modules-03
Objectif : Journaliser des événements de deux sources différentes. Un module gère les logs utilisateur, un autre les logs système. Les deux modules exportent une fonction qui porte le même nom, donc l'import doit utiliser un alias.
Instructions :
-
Créer un dossier nommé exo-modules-03, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-03/ |-- index.html └── js/ |-- app.js └── modules/ |-- journalUtilisateur.js └── journalSysteme.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 03".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
-
Dans ./js/modules/journalUtilisateur.js, créer une fonction fléchée nommée
enregistrerEvenement(message).
- message est une chaîne.
- La fonction retourne une chaîne préfixée par [USER].
- Exemple de retour attendu : [USER] Connexion réussie.
- Exporter cette fonction en export nommé.
-
Dans ./js/modules/journalSysteme.js, créer une fonction fléchée qui porte
exactement le même nom :
enregistrerEvenement(message).
- message est une chaîne.
- La fonction retourne une chaîne préfixée par [SYSTEM].
- Exemple de retour attendu : [SYSTEM] Cache vidé.
- Exporter cette fonction en export nommé.
-
Dans ./js/app.js, importer la fonction
enregistrerEvenement depuis les modules
journalUtilisateur.js et
journalSysteme.js.
Les deux fonctions portant le même nom, il faut éviter une collision lors de l'import dans
app.js
(erreur du type "Uncaught SyntaxError: redeclaration of import...").
Pour cela, renommer chaque import à l'aide d'un alias avec le mot-clé
as.
- Utiliser l'alias enregistrerEvenementUtilisateur pour l'import depuis journalUtilisateur.js.
- Utiliser l'alias enregistrerEvenementSysteme pour l'import depuis journalSysteme.js.
-
Toujours dans app.js, appeler les deux fonctions avec ces messages :
- Connexion réussie pour l'utilisateur (enregistrerEvenementUtilisateur)
- Cache vidé pour le système (enregistrerEvenementSysteme)
-
Vérifier que la console affiche bien deux lignes différentes :
[USER] Connexion réussie [SYSTEM] Cache vidé
Exo-modules-04
Objectif : Afficher un message de connexion en console en utilisant une petite chaîne d'importation. Le point d'entrée déclenche l'action, un module construit le message, et un autre module applique un format.
Instructions :
-
Créer un dossier nommé exo-modules-04, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-04/ |-- index.html └── js/ |-- app.js └── modules/ |-- demarrerConnexion.js └── formaterMessage.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 04".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
-
Dans ./js/modules/formaterMessage.js, créer une fonction fléchée nommée
formaterMessage.
- Cette fonction reçoit un texte (une chaîne).
- Elle retourne une chaîne formatée.
- Format demandé : le texte entouré de ===.
- Ex.: si le texte reçu est "Connexion réussie", le retour peut être "=== Connexion réussie ===".
- Exporter cette fonction en export nommé.
-
Dans ./js/modules/demarrerConnexion.js, créer une fonction fléchée nommée
demarrerConnexion.
- Cette fonction ne reçoit rien.
- Elle transmet le texte "Connexion réussie" à la fonction formaterMessage().
- Elle retourne ensuite la valeur renvoyée par formaterMessage(), c'est à dire le résultat formaté du texte "Connexion réussie".
- Pour pouvoir utiliser formaterMessage(), demarrerConnexion.js doit l'importer depuis formaterMessage.js.
- Exporter demarrerConnexion en export nommé.
-
Dans ./js/app.js :
- Importer demarrerConnexion.
- Appeler demarrerConnexion() pour obtenir le message final.
- Afficher ce message dans la console.
-
Vérifier que le programme suit bien cette logique :
- app.js déclenche
- demarrerConnexion.js construit le message et dépend d'un autre module
- formaterMessage.js reste un petit module utilitaire autonome
Exo-modules-05
Objectif : Sélectionner au hasard un Pokémon parmi une liste définie dans un module, puis afficher le Pokémon sélectionné dans la console. L'exercice sert aussi à constater qu'une variable non exportée reste utilisable dans son module, mais devient inaccessible en dehors.
Instructions :
-
Créer un dossier nommé exo-modules-05, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-05/ |-- index.html └── js/ |-- app.js └── modules/ └── selectionnerPokemonAuHasard.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 04".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
-
Dans ./js/modules/selectionnerPokemonAuHasard.js, déclarer un tableau
nommé pokemonPopulaires en copiant-collant exactement le contenu suivant :
const pokemonPopulaires = [ "Pikachu", "Greninja", "Lucario", "Mimikyu", "Charizard", "Umbreon", "Sylveon", "Garchomp", "Rayquaza", "Gengar", ]; -
Toujours dans selectionnerPokemonAuHasard.js, créer une fonction fléchée nommée
selectionnerPokemonAuHasard.
- Cette fonction ne reçoit aucun paramètre.
- Elle retourne une chaîne, correspondant à un Pokémon choisi au hasard dans pokemonPopulaires.
-
Dans cette fonction, mettre en place une sélection aléatoire basée sur un index.
- Générer un nombre décimal aléatoire avec Math.random().
- Transformer ce nombre en un index valide en utilisant la longueur du tableau pokemonPopulaires.length.
- Arrondir vers le bas pour obtenir un index entier avec Math.floor().
- Retourner l'élément du tableau correspondant à cet index.
-
À la fin du fichier selectionnerPokemonAuHasard.js,
exporter uniquement la fonction selectionnerPokemonAuHasard
en export nommé.
- Le tableau pokemonPopulaires ne doit pas être exporté. Il reste interne au module.
-
Dans ./js/app.js :
- Importer la fonction selectionnerPokemonAuHasard depuis le module.
- Appeler la fonction, puis stocker la valeur retournée dans une constante.
- Afficher cette constante dans la console.
- Ouvrir le projet via le serveur local, puis rafraîchir la page plusieurs fois. Vérifier que vous obtenez un nouveau Pokémon affiché dans la console à chaque rafraîchissement.
-
Dans app.js, tenter d'afficher le tableau pokemonPopulaires dans la console.
- Vous devriez obtenir le message suivant dans la console : "Uncaught ReferenceError: pokemonPopulaires is not defined".
- Cela signifie que pokemonPopulaires n'existe pas dans app.js. Le tableau est déclaré dans un autre module, et il n'a pas été exporté puis importé ici.
- Pourtant, selectionnerPokemonAuHasard() y a accès. La raison est que la fonction et le tableau sont définis dans le même fichier module. Le module "garde" ses variables internes, et seules les fonctions ou constantes exportées sont accessibles depuis l'extérieur.
- C'est le principe d'encapsulation. Un module peut garder certaines données privées et n'exposer que ce qu'il exporte, ce qui évite des manipulations directes depuis les autres fichiers qui l'importent.
Exo-modules-06
Objectif : Simuler une petite "liste de présence" utilisée pendant une session de travail. Les noms ajoutés restent mémorisés tant que la page reste ouverte, car ils sont stockés dans une variable interne au module. La liste doit pouvoir être affichée, complétée et modifiée via des fonctions exportées.
Instructions :
-
Créer un dossier nommé exo-modules-06, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-06/ |-- index.html └── js/ |-- app.js └── modules/ └── gererListePresence.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 06".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
-
Dans ./js/modules/gererListePresence.js, déclarer un tableau nommé
noms initialisé avec une liste vide.
- Ce tableau doit rester interne au module, il ne faut donc pas l'exporter.
- Il sert de "mémoire de session" pour la liste de présence.
-
Toujours dans gererListePresence.js, créer une fonction fléchée nommée
ajouterNom(nom).
- nom est une chaîne de caractères.
- La fonction vérifie si le nom est déjà présent dans le tableau interne noms à l'aide de includes(), afin d'éviter les doublons.
- Si le nom n'est pas trouvé, l'ajouter au tableau interne noms.
- Exporter cette fonction en export nommé.
-
Dans le même fichier, créer une fonction fléchée nommée
supprimerNom(nom).
- nom est une chaîne de caractères.
- La fonction recherche le nom dans le tableau interne noms. Comme la position du nom est nécessaire pour pouvoir le supprimer, utiliser indexOf() plutôt que includes().
- Si le nom est trouvé, le supprimer du tableau.
- Exporter cette fonction en export nommé.
-
Dans le même fichier, créer une fonction fléchée nommée
afficherNoms().
- Cette fonction ne reçoit aucun paramètre.
- La fonction retourne une chaîne prête à afficher dans la console. Cette chaîne doit contenir tous les noms de la liste, séparés par une virgule.
-
Pour construire cette chaîne, convertir le tableau en texte avec join().
- join() reçoit un séparateur (par exemple ", ") et retourne une chaîne contenant tous les éléments du tableau.
- Ce choix évite un piège fréquent dans la console. Si un tableau est affiché directement, l'outil de dev peut parfois montrer son contenu après une modification, car l'affichage garde une référence vers le même tableau. En retournant une chaîne construite au moment de l'appel, le résultat affiché correspond toujours à l'état exact à cet instant.
- Si la liste est vide, la fonction retourne exactement : Aucun nom dans la liste.
- Exporter cette fonction en export nommé.
- Dans ./js/app.js, importer les trois fonctions ajouterNom, supprimerNom et afficherNoms provenant du module gererListePresence.js.
-
Dans app.js, exécuter ce scénario en affichant des messages dans la console :
- Afficher la liste au départ via afficherNoms().
- Ajouter successivement trois noms dans cet ordre : Lambert, Dupont, El Amrani.
- Afficher la liste.
- Supprimer le nom Dupont.
- Afficher la liste.
Exo-modules-07
Objectif : Générer un super-héros aléatoire en combinant un adjectif, un animal et un pouvoir. Les listes de mots proviennent d'un module de données. Une fonction utilitaire sélectionne un élément au hasard dans un tableau via un export par défaut. Un module assemble ensuite la phrase finale en utilisant un import * et un import de type default.
Instructions :
-
Créer un dossier nommé exo-modules-07, puis organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-07/ |-- index.html └── js/ |-- app.js └── modules/ |-- donnees.js |-- selectionAleatoireDansTableau.js └── genererSuperHeros.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 07".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
- Ouvrir la page via le serveur local, puis ouvrir la console.
-
Dans ./js/modules/donnees.js, créer trois fonctions nommées
obtenirAdjectifs(), obtenirAnimaux() et
obtenirPouvoirs() en copiant-collant le contenu suivant :
function obtenirAdjectifs() { return [ 'astucieux', 'audacieux', 'brillant', 'désuet', 'élégant', 'formidable', 'fringant', 'gracieux', 'insolite', 'intrépide', 'loyal', 'magnanime', 'mystique', 'preux', 'resplendissant', 'sympathique', 'vivace', 'vaillant', 'vigoureux', 'zélé' ]; } function obtenirAnimaux() { return [ 'albatros', 'axolotl', 'baleine', 'caméléon', 'canard', 'chat', 'chien', 'cochon', 'dauphin', 'escargot', 'hibou', 'lama', 'manchot', 'mouflon', 'ornithorynque', 'paon', 'panda', 'phoque', 'singe', 'tortue' ]; } function obtenirPouvoirs() { return [ 'des ailes géantes', 'des bras élastiques', 'des rayons laser', 'des sauts interstellaires', 'la capacité de devenir invisible', 'la maîtrise de la foudre', 'la possibilité de respirer sous l'eau', 'le contrôle absolu du feu', 'le contrôle de la gravité', 'le don d'hypnotiser ses ennemis', 'le pouvoir de camoufler tout son corps', 'le pouvoir de clonage', 'le pouvoir de créer des tornades', 'le pouvoir de lancer des cookies explosifs', 'le pouvoir de produire des paillettes infinies', 'le pouvoir de réparer instantanément les objets cassés', 'un souffle glacial', 'une vitesse fulgurante', 'une vue laser', 'une force surhumaine' ]; } export { obtenirAdjectifs, obtenirAnimaux, obtenirPouvoirs } -
Dans ./js/modules/selectionAleatoireDansTableau.js, créer une fonction nommée
selectionnerElementAleatoireDansTableau(tableau).
- Le paramètre tableau correspond à un tableau où chaque élément est une chaîne de caractères.
- La fonction doit retourner un seul élément, choisi au hasard.
- Cette fonction doit être exportée en export par défaut.
Pour construire un hasard basé sur un index, suivre cette logique :
- Générer un nombre décimal aléatoire avec Math.random().
- Le transformer en un index potentiel en le multipliant par tableau.length.
- Arrondir vers le bas avec Math.floor() afin d'obtenir un entier.
- Retourner l'élément du tableau à cet index.
-
Dans ./js/modules/genererSuperHeros.js, importer tous les exports de
./donnees.js avec un import *.
- Utiliser un alias nommé mots.
- Les fonctions exportées devront ensuite être appelées via l'objet mots, (ex.: mots.obtenirAdjectifs()).
- Ce type d'import permet de regrouper plusieurs exports nommés sous un seul objet. Cela évite de lister trois imports séparés et rend l'intention plus lisible.
-
Toujours dans genererSuperHeros.js, importer la fonction exportée par défaut depuis
./selectionAleatoireDansTableau.js.
Elle sera utilisée pour sélectionner aléatoirement un élément dans un tableau.
- Comme il s'agit d'un export par défaut, le nom utilisé lors de l'import est libre et n'est pas lié au nom de la fonction dans le module d'origine. Utiliser le nom local choisirMotAleatoire.
- Attention, un export par défaut ne s'importe pas entre accolades. Les accolades sont réservées aux exports nommés.
-
Dans genererSuperHeros.js, créer une fonction nommée
genererSuperHeros(), puis l'exporter en export nommé.
- La fonction ne reçoit aucun paramètre.
- La fonction se contente d'appeler les fonctions permettant de sélectionner 3 mots alétoires (un adjectif, un animal et un pouvoir) à partir de fonctions importées et elle construit une chaîne formatée à partir des 3 résultats.
- Elle retourne une chaîne finale prête à être affichée.
-
Dans genererSuperHeros(), appliquer la construction suivante :
- Obtenir les adjectifs via la fonction obtenirAdjectifs() du module donnees.js.
- Obtenir les animaux via la fonction obtenirAnimaux() du module donnees.js.
- Obtenir les pouvoirs via la fonction obtenirPouvoirs() du module donnees.js.
- Pour chacun de ces trois tableaux, sélectionner un élément au hasard via la fonction choisirMotAleatoire(...) du module selectionAleatoireDansTableau.js.
-
Assembler ensuite la phrase au format :
[adjectif] [animal] avec [pouvoir] - Retourner cette phrase.
-
Dans ./js/app.js, importer la fonction
genererSuperHeros depuis
./modules/genererSuperHeros.js et exécuter le scénario suivant :
- Appeler genererSuperHeros() et stocker le résultat dans une constante superHeros.
- Afficher ensuite dans la console : Vous êtes un [superHeros]!
- Remplacer [superHeros] par la valeur stockée dans la constante superHeros.
-
Rafraîchir la page plusieurs fois et vérifier que la phrase affichée change.
Un exemple possible est :
Vous êtes un gracieux panda avec une force surhumaine!
Exercices bonus
Exo-modules-08
Objectif : Calculer le montant total d'une petite liste de commandes, puis afficher ce total dans la console. Les commandes sont stockées dans un module de données, et le calcul est réalisé dans un autre module importé par le point d'entrée.
Instructions :
-
Créer un dossier nommé exo-modules-08, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-08/ |-- index.html └── js/ |-- app.js └── modules/ |-- donneesCommandes.js └── gestionCommande.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 07".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
- Ouvrir la page via le serveur local, puis ouvrir la console.
-
Dans ./js/modules/donneesCommandes.js, créer une constante exportée nommée
commandes contenant les objets suivants :
export const commandes = [ { article: "pomme", prix: 3, quantite: 4 }, { article: "citron", prix: 5, quantite: 5 }, { article: "poire", prix: 4, quantite: 1 } ]; - Dans ./js/modules/gestionCommande.js, importer la constante commandes depuis donneesCommandes.js.
-
Dans gestionCommande.js, créer une fonction fléchée nommée
calculerMontantTotal().
- Cette fonction ne reçoit aucun paramètre.
- Elle parcourt le tableau commandes avec une boucle.
- Pour chaque commande, elle additionne prix * quantite dans une variable de total.
- Elle retourne le total final.
- Exporter cette fonction en export nommé.
- Dans ./js/app.js, importer la fonction calculerMontantTotal depuis ./modules/gestionCommande.js.
- Dans app.js, appeler calculerMontantTotal(), stocker le résultat dans une constante, puis afficher ce message dans la console : "Montant total: [valeur]€" (remplacer [valeur] par la valeur calculée).
- Tester en vous assurant que le message dans la console affiche bien : "Montant total: 41€"
Exo-modules-09
Objectif : Générer un petit rapport de moyennes pour une liste d'étudiants. Les données de notes se trouvent dans un module dédié, le calcul de moyenne dans un module utilitaire, et un troisième module assemble le rapport final qui est affiché dans la console depuis le point d'entrée.
Instructions :
-
Créer un dossier nommé exo-modules-09, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-09/ |-- index.html └── js/ |-- app.js └── modules/ |-- donneesNotes.js |-- calculerMoyenne.js └── genererMoyenneNotesParNom.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 08".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
- Ouvrir la page via le serveur local, puis ouvrir la console.
-
Dans ./js/modules/donneesNotes.js, créer une constante exportée nommée
notesParNom en copiant-collant le contenu suivant :
export const notesParNom = [ { nom: "Lambert", notes: [12, 15, 14] }, { nom: "Dupont", notes: [9, 10, 11] }, { nom: "El Amrani", notes: [16, 14, 18] }, ]; -
Dans ./js/modules/calculerMoyenne.js, créer une fonction fléchée nommée
calculerMoyenne(notes).
- notes est un tableau de nombres entiers.
- La fonction retourne un nombre correspondant à la moyenne.
- Pour calculer la somme du tableau, utiliser reduce() avec un accumulateur et une valeur de départ à 0.
- La moyenne correspond à la somme divisée par notes.length.
- Exporter la fonction en export nommé.
-
Dans ./js/modules/genererMoyenneNotesParNom.js, importer :
- la constante notesParNom depuis ./donneesNotes.js
- la fonction calculerMoyenne depuis ./calculerMoyenne.js
-
Toujours dans genererMoyenneNotesParNom.js, créer une fonction fléchée nommée
genererMoyenneNotesParNom().
- Cette fonction ne reçoit aucun paramètre.
- Elle construit un tableau nommé moyennesParNom (vide au départ).
-
Elle parcourt notesParNom avec une boucle.
Pour chaque étudiant, elle ajoute un objet contenant :
- nom avec le nom de l'étudiant
- moyenne avec le résultat de calculerMoyenne() appliquée au tableau notes
- La fonction retourne le tableau moyennesParNom.
- Exporter la fonction en export nommé.
- Dans ./js/app.js, importer la fonction genererMoyenneNotesParNom depuis ./modules/genererMoyenneNotesParNom.js.
-
Dans app.js, appeler genererMoyenneNotesParNom()
et stocker le résultat dans une constante.
Parcourir ensuite ce résultat avec une boucle et afficher, pour chaque étudiant, un message au format suivant :
- Moyenne de [nom] : [moyenne]
-
Vérifier que les valeurs affichées correspondent aux calculs.
Avec les données fournies, les moyennes attendues sont :
- Moyenne de Lambert : 13.666666666666666
- Moyenne de Dupont : 10
- Moyenne de El Amrani : 16
Exo-modules-10
Objectif : Générer un petit plan de salle exploitable. Les noms des personnes sont stockés dans un tableau à deux dimensions, où chaque case correspond à une place. Un module transforme ce plan en une liste d'objets contenant le nom et ses coordonnées, puis le point d'entrée affiche un résumé et les placements dans la console.
Instructions :
-
Créer un dossier nommé exo-modules-10, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-10/ |-- index.html └── js/ |-- app.js └── modules/ |-- planSalle.js └── genererPlacesParNom.js - Dans index.html, générer le code HTML minimal, puis compléter le title et le h1 avec le texte "JS: Exo Modules 09".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
- Ouvrir la page via le serveur local, puis ouvrir la console.
-
Dans ./js/modules/planSalle.js, créer une constante exportée nommée
planSalle en copiant-collant le contenu suivant :
export const planSalle = [ ["Lambert", "Dupont", ""], ["El Amrani", "", "Nguyen"], ["", "Martin", "Diallo"] ];- Chaque sous-tableau représente une rangée.
- Chaque élément d'une rangée représente une colonne.
- Une chaîne vide "" signifie qu'il n'y a personne à cette place.
- Dans ./js/modules/genererPlacesParNom.js, importer la constante planSalle depuis ./planSalle.js.
-
Toujours dans genererPlacesParNom.js, créer une fonction fléchée nommée
genererPlacesParNom(), puis l'exporter en export nommé.
- La fonction ne reçoit aucun paramètre.
- Elle retourne un tableau d'objets, où chaque objet représente une personne placée dans la salle.
-
Dans genererPlacesParNom(), construire le tableau résultat en respectant la logique suivante :
- Créer un tableau vide nommé placesParNom.
-
Parcourir planSalle avec deux boucles imbriquées :
- une boucle sur les rangées, avec un index r
- dans cette boucle, une boucle sur les colonnes, avec un index c
- À chaque itération de la boucle des colonnes, récupérer le contenu de la case courante du plan, en utilisant les indices r et c. Cette valeur correspond au nom stocké à cette place.
-
Si ce nom n'est pas une chaîne vide, ajouter un objet dans placesParNom
avec cette structure :
- une propriété nom contenant le nom récupéré
-
une propriété coordonnees contenant un tableau [r, c]
- r correspond au numéro de la rangée où la personne se trouve
- c correspond au numéro de la colonne où la personne se trouve
- Afficher temporairement le résultat de placesParNom dans la console pour vous assurez d'obtenir la structure attendue :
[ { "nom": "Lambert", "coordonnees": [0, 0] }, { "nom": "Dupont", "coordonnees": [0, 1] }, { "nom": "El Amrani", "coordonnees": [1, 0] }, { "nom": "Nguyen", "coordonnees": [1, 2] }, { "nom": "Martin", "coordonnees": [2, 1] }, { "nom": "Diallo", "coordonnees": [2, 2] } ] - Supprimez l'affichage temporaire de placesParNom dans la console.
- À la fin, retourner placesParNom.
- Dans ./js/app.js, importer la fonction genererPlacesParNom depuis ./modules/genererPlacesParNom.js.
- Dans app.js, appeler genererPlacesParNom() et stocker le résultat dans une constante nommée placesParNom. Calculer ensuite le nombre de personnes en utilisant placesParNom.length, puis stocker ce nombre dans une constante nommée nbrPersonnes.
- Afficher ensuite ce message dans la console : "Nombre de personnes dans la salle : [nbrPersonnes] !". Remplacer [nbrPersonnes] par la valeur de la constante nbrPersonnes.
-
Parcourir ensuite le tableau placesParNom avec une boucle for
classique basée sur un index.
- L'index commence à 0.
- La condition de la boucle utilise nbrPersonnes.
- L'incrémentation augmente l'index de 1.
-
À chaque tour de boucle :
-
Récupérer l'objet de placement correspondant à l'élément courant de placesParNom,
puis le stocker dans une constante nommée place.
Cet objet contient le nom de la personne et ses coordonnées. -
Déduire le numéro de place à afficher en ajoutant 1 à l'index courant de la boucle,
puis stocker ce résultat dans une constante nommée numeroPlace.
Cela permet d'afficher une numérotation qui commence à 1, même si l'index de boucle commence à 0. -
Afficher un message au format suivant :
[numeroPlace] - [nom] est placé(e) dans la rangée [r] et la colonne [c] - Remplacer [nom] par la valeur stockée dans la propriété nom de l'objet place.
-
Remplacer [r] et [c] à partir de la propriété coordonnees
de l'objet place.
- [r] correspond au premier élément du tableau coordonnees et représente le numéro de la rangée.
- [c] correspond au deuxième élément du tableau coordonnees et représente le numéro de la colonne.
-
Récupérer l'objet de placement correspondant à l'élément courant de placesParNom,
puis le stocker dans une constante nommée place.
-
Rafraichir la page et vérifier que la console affiche une ligne de résumé avec le nombre de personnes et ne ligne par personne avec le numéro de sa place, son nom et ses coordonnées.
Vérifier que le resultat affiché dans la console correspond a ceci :
Nombre de personnes dans la salle : 6 ! 1 - Lambert est placé(e) dans la rangée 0 et la colonne 0 2 - Dupont est placé(e) dans la rangée 0 et la colonne 1 3 - El Amrani est placé(e) dans la rangée 1 et la colonne 0 4 - Nguyen est placé(e) dans la rangée 1 et la colonne 2 5 - Martin est placé(e) dans la rangée 2 et la colonne 1 6 - Diallo est placé(e) dans la rangée 2 et la colonne 2
Exo-modules-11
Objectif : Afficher un planning lisible a partir d'une grille de codes. Le planning est un tableau a deux dimensions, et un objet sert de dictionnaire pour traduire chaque code en libellé complet. L'objectif est de manipuler un tableau multi et un objet litteral dans des boucles imbriquees, avec des exports imports nommés.
Instructions :
-
Creer un dossier nomme exo-modules-11, puis organiser les fichiers et dossiers en respectant l'arborescence suivante :
exo-modules-11/ |-- index.html └── js/ |-- app.js └── modules/ |-- donneesPlanning.js └── construirePlanning.js - Dans index.html, generer le code HTML minimal, puis completer le title et le h1 avec le texte "JS: Exo Modules 10".
- Dans index.html, charger uniquement ./js/app.js avec type="module".
- Ouvrir la page via le serveur local, puis ouvrir la console.
-
Dans ./js/modules/donneesPlanning.js, creer le contenu du module en copiant-collant ceci :
// Données du planning sous forme de tableau à deux dimensions (tableau de tableaux). // // - Le premier index correspond au jour (ligne). // - Le second index correspond au créneau horaire (colonne). // - Une chaîne vide "" signifie qu'aucun cours n'est prévu à ce moment. // - Une valeur comme "JS", "HTML", "PHP", etc. est un code de cours. // // Exemple de lecture : // planning[0][1] → jour 0 (lundi), créneau 1 (midi) → "HTML" /* créneau 0 créneau 1 créneau 2 (matin) (midi) (soir) jour 0 (lundi) ["JS", "HTML", "NET" ], jour 1 (mardi) ["PHP", "JS", "" ], jour 2 (mercredi) ["", "UX", "PHP" ] */ const planning = [ ["JS", "HTML", "NET"], ["PHP", "JS", ""], ["", "UX", "PHP"], ]; // Dictionnaire de correspondance entre un code de cours et son libellé complet. // Permet de transformer une valeur du tableau planning comme "JS" en "JavaScript". const libellesParCode = { JS: "JavaScript", HTML: "HTML", PHP: "PHP", UX: "UX design", NET: "Reseaux", }; // Correspondance entre l'index de rangée du planning et le nom du jour. // Exemple : // planning[0] → "lundi" const joursParIndex = [ "lundi", "mardi", "mercredi", "jeudi", "vendredi" ]; // Correspondance entre l'index de colonne du planning et le créneau horaire. // Exemple : // planning[r][2] → "soir" const creneauxParIndex = [ "matin", "midi", "soir" ]; export { planning, libellesParCode, joursParIndex, creneauxParIndex };- Le tableau planning :
- Chaque index de premier niveau représente la rangée des jour (ex.: planning[0] = lundi, planning[1] = mercredi, etc.)
- Chaque élément de premier niveau contient tous les cours d'un même jour (ex.: liste des codes de cours en planning[0] = ["JS", "HTML", "NET"]).
- Chaque index de second niveau niveau représente la colonne des créneaux (ex.: planning[j][0] = matin, planning[j][1] = midi, etc.)
- Chaque élément de second niveau contient le cours à un jour j, créneaux c (ex.: planning[0][3] correspond à lundi soir, le code du cours est donc NET).
- Une chaine vide "" signifie qu'il n'y a aucun cours a ce moment.
- Une chaîne non vide correspond au code du cours planifié à ce moment (ex.: JS, NET, etc.).
- Le tableau libellesParCode sert a traduire un code de cours (ex.: Le code "JS" devient le libellé "JavaScript").
- Le tableau joursParIndex sert a convertir un index de jour en nom de jour (ex.: index 0 = lundi).
- Le tableau creneauxParIndex sert a convertir un index de creneau en libelle de creneau (ex.: index 1 = midi).
- Le tableau planning :
- Dans ./js/modules/construirePlanning.js, importer en import nomme les donnees exportees depuis ./donneesPlanning.js planning, libellesParCode, joursParIndex et creneauxParIndex.
-
Toujours dans construirePlanning.js, creer une fonction fléchée nommee
construirePlanning(), puis l'exporter en export nomme.
- La fonction ne reçoit aucun paramètre.
- Elle retourne un objet organisé par cours.
- Dans l'objet retourne, chaque cle correspond au libelle complet d'un cours (ex.: JavaScript).
- Pour chaque cours, la valeur associée est un autre objet. Les clés de cet objet correspondent aux jours (ex.: lundi, mardi ou mercredi). La valeur associée à chaque jour est un tableau qui contient les créneaux durant lesquels ce cours a lieu (ex.: matin, midi ou soir).
-
Dans construirePlanning(), construire l'objet resultat a partir de la grille planning.
- Commencer par creer un objet vide nomme planningParCours.
-
Parcourir planning avec deux boucles imbriquees :
- une boucle sur les jours avec un index j
- a chaque jour, une boucle sur les creneaux avec un index c
- A chaque couple (jour, creneau), recuperer le code present dans la grille planning. Si ce code est une chaine vide, ignorer cette case.
-
Si le code n'est pas vide :
- traduire le code en libelle complet via libellesParCode
- si le code n'existe pas dans le dictionnaire, utiliser le libellé "Cours inconnu" au lieu de laisser une valeur manquante
- convertir l'index du jour en nom de jour via joursParIndex
- convertir l'index du creneau en nom de creneau via creneauxParIndex
-
Ajouter ensuite le creneau dans la structure planningParCours :
- si le cours n'existe pas encore dans planningParCours, creer la structure correspondante
- si le jour n'existe pas encore pour ce cours, initialiser une liste vide
- ajouter le creneau a cette liste
- Afficher temporairement le résultat de planningParCours dans la console pour vous assurez d'obtenir la structure attendue :
{ "HTML": { "lundi": ["midi"] }, "Javascript": { "lundi": ["matin"], "mardi": ["midi"] }, "PHP": { "mardi": ["matin"], "mercredi": ["soir"] }, "Reseaux": { "lundi": ["soir"] }, "UX design": { "mercredi": ["midi"] } } - Supprimez l'affichage temporaire de planningParCours dans la console.
- A la fin, retourner planningParCours.
- Dans ./js/app.js, importer la fonction construirePlanning depuis ./modules/construirePlanning.js.
- Dans app.js, appeler construirePlanning() et stocker le résultat dans une constante nommée planningParCours.
-
Parcourir ses cours, utiliser une boucle for...in afin de récuperer chaque clé de l'objet.
Pour chaque cours :
- récuperer l'objet associé à ce cours, qui contient les jours et leurs creneaux
- construire un message multi-lignes qui commence par Cours [nom du cours]
-
parcourir ensuite les jours de ce cours (à nouveau via une boucle for...in)
et ajouter une ligne par jour au format suivant :
- [jour] - [liste des creneaux] - pour afficher la liste des creneaux proprement, convertir le tableau de creneaux en texte avec join()
- afficher le message final dans la console
-
Rafraichir la page et vérifier que la console affiche un bloc par cours, avec les jours et les creneaux correspondants.
Vérifier que le resultat affiché dans la console correspond a ceci :
Cours : JavaScript - lundi : matin - mardi : midi Cours : HTML - lundi : midi Cours : Reseaux - lundi : soir Cours : PHP - mardi : matin - mercredi : soir Cours : UX design - mercredi : midi