Diviser les programmes JavaScript en plusieurs modules qu'on pourrait importer les uns dans les autres est un mécanisme qui n'existe que depuis 2015 (ES6).
Un module est un fichier JS ...
-
qui peut importer un autre module, via l'instruction
import
-
ou qui peut exporter ses variables et fonctions, via l'instruction
export
... et donc, tout fichier JS est un module, lorsqu'on écrit de façon modulaire.
Un module ne peut être appelé depuis le code HTML, via la balise <script>, que si
l'attribut type
prend pour valeur "module" :
<script type="module" src="m_xxx.js">
Pour éviter la confusion, l'extension des fichiers JS de type module peut-être .mjs. Toutefois, ... la plupart des serveurs (même Apache) ne sont pas (encore) configurés pour supporter cette extension, par défaut.
Configuration : Les fichiers ayant l'extension .mjs doivent être "servis"
avec un en-tête Content-Type
qui contient un type MIME JavaScript comme
text/javascript
Un fichier HTML peut continuer à appeler des fichiers JS "classiques". Et, donc pour
éviter la confusion, il est donc recommandé de préfixer les modules JS. Par exemple,
par m_
.
Pour exécuter des modules en local, il faut utiliser un serveur web local.
Exemple
Exemple d'utilisation d'une fonction importée.
Contenu du fichier JS, appelé exportAlert.js
, qui contient une fonction
importable
et qui est situé dans le dossier-fils (appelé "modules") du dossier de la présente page
web :
export function alerte() { alert("Bonjour './'") }
Contenu du fichier JS, appelé m_jour5.js
, qui importe (la fonction
alerte
) :
/* l'appel du présent fichier se fait via : type="module" */ import { alerte } from "./modules/exportAlert.js"; /* NB : Relative references must start with either "/", "./", or "../" => import { alerte } from "modules/exportAlert.js"; == erreur => Un fichier JS qui utilise le mot-clé "import" doit être appelé via "/", "./", or "../" */ function appelAlert() { alerte() } /* appelAlert(); /* = appel lors du chargement de la page */ /* NB : La fonction appelAlert() ne peut pas être appelée via un onclick="..." (dans le code HTML) => créer un écouteur => le bouton doit disposer de l'attribut id */ document.getElementById("btnAppelAlert").onclick = function(evt){ appelAlert() }
Un fichier JS qui utilise le mot-clé import
doit être appelé
via "/", "./", ou "../"
.
Extrait du code HTML de la présente page web qui appelle un fichier JS qui importe :
... <button id="btnAppelAlert">Afficher : <i>Bonjour ./</i></button> <!-- Code HTML du bouton ci-dessous --> ... <script src="jour5.js"></script> <script src="m_jour5.js" type="module"></script> </body> </html>
Si on fait appel à des importations, ...
... il est préférable de scinder le code JS en deux fichiers JS.
Le premier est celui qui n'est pas lié aux importations.
Le second est lié aux importations.
Par commodité, le nom est préfixé par m_
(comme dans le mot
module ...)
Cette séparation permet à la page web de rester partiellement opérationnelle, en cas d'erreur dans le second fichier.
En effet, si tout le code JS se trouve dans un seul fichier, comme les importations ont lieu au début du fichier, une erreur dans un chemin relatif arrêtera l'exécution du code JS.
Exemple. Code JS du fichier jour5.js
:
if (location.protocol.substr(0,4)!="http") document.getElementById("noServer").innerHTML ="<span class='red'>Pas connecté à un serveur !</span>"
Si la page web n'est pas sur un serveur, s'affiche en rouge et en haut de page un message.
Mais, ... si cette instruction était placée après un import
et que le fichier visé soit manquant, l'exécution s'arrêtera à cette importation ratée
et donc les instructions qui suivent ne seront pas exécutées. L'utilisateur ne sera
donc pas prévenu que la page web ne provient pas d'un serveur.
Et, donc, qu'elle ne fonctionnera pas correctement même s'il n'existe pas d'erreur au niveau des chemins d'importations.
Le bouton ci-dessous (Afficher : Bonjour ./) ne fonctionnera pas.
Le bouton ci-dessus ne fonctionne que :
- Si la présente page web est sur un serveur,
- Et, si
m_jour5.js
est appelé viatype="module",
- Et, si un écouteur a été créé (dédié à l'événement click de ce bouton).
Disposer d'un serveur web local léger
Un serveur (local) tel qu'Apache peut être utilisé pour tester l'exécution de modules JS. Mais, il est lourd ...
Pour disposer d'un serveur web local léger, il n'est pas recommandé d'installer un programme développé par un éditeur peu connu. Python est très connu et fiable.
Si Python est installé sur l'ordinateur, on dispose d'un serveur web local léger et
suffisant pour l'exécution de fichiers JS.
Sinon, il est recommandé d'installer Python.
Il suffit alors de se placer dans le dossier contenant les fichiers (via l'explorateur de fichiers), d'ouvrir un terminal (via le menu contextuel) et d'y taper la commande :
py -m http.server
Il est préférable que le fichier index.htm
(ou index.html
)
soit le fichier d'accueil d'un site "autonome" (= dont toutes les ressources, telles
que les fichiers CSS, images, ... soient dans ce dossier ou ses sous-dossiers)
Tester un site sur un serveur local est un bon moyen de voir s'il est autonome.
Un site autonome peut facilement être déplacé sur un ou plusieurs serveurs
connectés à Internet.
Dans le navigateur, ouvrir un nouvel onglet et taper dans la barre des URLs :
http://localhost:8000/
Pour éteindre ce serveur, dans le terminal, il suffit de taper : CTRL + C
Et, exit
pour fermer le terminal.
export
Pour que des variables (souvent des constantes) et des fonctions (et classes) puissent
être exportées, il suffit d'indiquer, à la fin du fichier, export
suivi de
leurs noms séparés par une virgule entre accolades. Tel que
const name = ...; function draw() { ... } function reportArea() { ... } function reportPerimeter() { ... } export { name, draw, reportArea, reportPerimeter };
Le nom de la fonction ne doit pas être suivi de parenthèses.
Les valeurs exportées doivent être présentes au plus haut niveau du script, il n'est pas
possible d'utiliser
export
dans une fonction.
Ou,
Le nom de chaque variable et fonctions exportable est précédé du mot export
export const name = "square"; export function draw(ctx, length, x, y, color) { ctx.fillStyle = color; ctx.fillRect(x, y, length, length); return { length: length, x: x, y: y, color: color, }; }
Remarquons qu'un objet peut être retourné par une fonction.
Le nom d'une variable ne peut pas correspondre à celui d'une fonction.
Chemin relatif
Maintenant que nous disposons de fichiers au contenu exportable, il faut connaître le chemin vers ces fichiers pour les importer.
Un fichier JS qui utilise le mot-clé import
doit être appelé
via "/", "./", ou "../"
.
Le chemin donné peut être absolu (en commençant par /
) ...
mais, dans ce cas, l'application web ne peut être déplacée !
En pratique, pour pouvoir déplacer l'application web, le chemin donné est toujours relatif :
-
./
: signifie que le fichier est cherché à partir du répertoire courant du fichier appelant.Lorsque le fichier appelant est un fichier HTML, c'est plus simple.
Le dossier courant (./) ne doit pas être précisé.Car le fichier HTML appelle tous les fichiers (image, CSS, JS, ...) dont il a besoin pour créer la page web.
Ici, le présent fichier (
jour5.htm
) appelle un fichier, situé dans le même dossier,jour4.htm
:<p>vers <a href="jour4.htm">jour4.htm</a></p>
vers jour4.htm
<p>vers <a href="./jour4.htm">./jour4.htm</a></p>
vers jour4.htm
Les deux écritures de chemin sont possibles.
On choisit donc la plus simplehref="jour4.htm"
(au lieu dehref="./jour4.htm"
) -
../
: signifie que la page est cherchée à partir du répertoire parent [du fichier qui appelle (la page cherchée)].
Souvent ce dossier est celui qui contient le fichier m_main.js
.
Un fichier au contenu exportable peut aussi (préalablement) importer ...
import
Lorsque des modules destinés à l'exportation existent, il ne reste plus qu'à les importer ...
import { name, draw, reportArea, reportPerimeter } from "./modules/square.js"; let myCanvas = create("myCanvas", document.body, 480, 320); let reportList = createReportList(myCanvas.id); let square1 = draw(myCanvas.ctx, 50, 50, 100, "blue"); reportArea(square1.length, reportList); reportPerimeter(square1.length, reportList);
as
Une fonction d'un module peut avoir le même nom que celle d'un autre module ... Pour
éviter lors de l'importation, une SyntaxError: redeclaration of import name
, il
faut renommer ces fonctions via as
.
import * as SQUARE from "./modules/square.js"; /* dispose d'une fonction exportable 'draw' */ import * as CIRCLE from "./modules/circle.js"; /* dispose d'une fonction exportable 'draw' */ let square1 = SQUARE.draw(...); let circle1 = CIRCLE.draw(...);
import *
permet d'importer tout ce qui est exportable.
Il est toutefois possible de n'importer que partiellement.
import { draw, reportArea } as SQUARE from "./modules/square.js"; let square1 = SQUARE.draw(...);
Par convention, le namespace est écrit en majuscules.
Ou,
import { draw as drawSquare, reportArea as reportSquareArea } from "./modules/square.js"; let square1 = drawSquare(...);
DOM
Qui dit module, dit écouteur créé en JS.
<script type="importmap">
La valeur importmap
de l'attribut type
de la
balise script
n'est reconnue des principaux navigateurs web que depuis mars
2021.
Dans l'instruction (contenue dans un fichier JS) :
import { alerte } from "./modules/exportAlert.js";
ce
qui suit le mot-clé from
est appelé un module specifier.
Jusqu'ici, ce module specifier était une URL soit relative soit absolue.
Grâce au contenu de cette balise HTML, il est possible de remplacer cette URL par une clé quelconque.
Il est même possible de lier plusieurs clés à plusieurs fichiers JS. Bref, de créer une carte (en anglais, map) de correspondances.
Le contenu de cette balise HTML est un objet au format JSON.
Cette balise est unique dans le fichier HTML. Logique, vu qu'on ne peut fait
qu'une carte de correspondances. De plus, les autres attributs de cette balise
(src, async, nomodule, defer, crossorigin, integrity, and referrerpolicy
) ne
doivent pas être présents.
Exemple
<script type="importmap"> { "imports": { "square": "./module/shapes/square.js", "circle": "https://example.com/shapes/circle.js" } } </script>
Même s'il n'existe qu'une seule correspondance, la clé de cet objet est toujours
imports
(avec un s)
De plus, lorsqu'il existe plusieurs couples de key:value, ils sont séparés par une
virgule.
Cette balise doit être placée avant toute balise <script>
important
des modules utilisant des spécificateurs présents dans la carte.
. Elle peut être
placée dans le head du fichier HTML