19 mai 2023

Exporter et importer

Les directives d’exportation et d’importation ont plusieurs variantes de syntaxe.

Dans l’article prĂ©cĂ©dent, nous avons vu une utilisation simple, explorons maintenant plus d’exemples.

Exporter avant les déclarations

Nous pouvons Ă©tiqueter n’importe quelle dĂ©claration comme exportĂ©e en plaçant export devant elle, que ce soit une variable, une fonction ou une classe.

Par exemple, ici toutes les exportations sont valides:

// exporter un tableau
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// exporter une constante
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// exporter une classe
export class User {
  constructor(name) {
    this.name = name;
  }
}
Pas de point-virgule aprùs la classe/fonction d’exportation

Veuillez noter que l’export avant une classe ou une fonction n’en fait pas une function expression. C’est toujours une fonction dĂ©claration, bien qu’elle soit exportĂ©e.

La plupart des guides de bonnes pratiques JavaScript ne recommandent pas les points-virgules aprÚs les déclarations de fonctions et de classes.

C’est pourquoi il n’est pas nĂ©cessaire d’utiliser un point-virgule Ă  la fin de export class et de export function:

export function sayHi(user) {
  alert(`Hello, ${user}!`);
}  // pas de ; Ă  la fin

Exporter en dehors des déclarations

En outre, nous pouvons mettre l’export sĂ©parĂ©ment.

Ici, nous dĂ©clarons d’abord, puis exportons:

// 📁 say.js
function sayHi(user) {
  alert(`Hello, ${user}!`);
}

function sayBye(user) {
  alert(`Bye, ${user}!`);
}

export {sayHi, sayBye}; // une liste de variables exportées


 Ou, techniquement, nous pourrions Ă©galement dĂ©finir les fonctions d’export au-dessus des fonctions.

Import *

Habituellement, nous mettons une liste de ce qu’il faut importer entre accolades import {...}, comme ceci:

// 📁 main.js
import {sayHi, sayBye} from './say.js';

sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!

Mais s’il y a beaucoup à importer, nous pouvons tout importer en tant qu’objet en utilisant import * as <obj>, par exemple:

// 📁 main.js
import * as say from './say.js';

say.sayHi('John');
say.sayBye('John');

À premiĂšre vue, “importer tout” semble ĂȘtre une chose tellement cool, simple a Ă©crire, pourquoi devrions-nous explicitement Ă©numĂ©rer ce que nous devons importer?

Eh bien, il y a quelques raisons.

  1. Lister explicitement ce qu’il faut importer donne des noms plus courts : sayHi() au lieu de say.sayHi().
  2. La liste explicite des importations donne un meilleur aperçu de la structure du code : ce qui est utilisĂ© et oĂč. Cela facilite la prise en charge du code et la refactorisation.
N’ayez pas peur d’importer trop

Les outils de construction modernes, tels que webpack et d’autres, regroupent les modules et les optimisent pour accĂ©lĂ©rer le chargement. Ils ont Ă©galement supprimĂ© les importations inutilisĂ©es.

Par exemple, si vous importer import * as library Ă  partir d’une Ă©norme bibliothĂšque de codes, puis n’utilisez que quelques mĂ©thodes, celles qui ne sont pas utilisĂ©es [ne seront pas incluses] (https://github.com/webpack/webpack/tree/main/ examples/harmony-unused#examplejs) dans le bundle optimisĂ©.

Import “as”

Nous pouvons également utiliser as pour importer sous différents noms.

Par exemple, importons sayHi dans la variable locale hi par souci de concision, et importons sayBye en bye:

// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';

hi('John'); // Hello, John!
bye('John'); // Bye, John!

Export “as”

La syntaxe similaire existe pour l’export.

Exportons les fonctions en tant que hi et bye:

// 📁 say.js
...
export {sayHi as hi, sayBye as bye};

Maintenant, hi et bye sont les noms Ă  utiliser dans les importations:

// 📁 main.js
import * as say from './say.js';

say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!

Export default

En pratique, il existe principalement deux types de modules.

  1. Les modules qui contiennent une bibliothĂšque, un pack de fonctions, comme say.js ci-dessus.
  2. Les modules qui déclarent une seule entité, par exemple un module user.js qui exporte uniquement la class User.

La deuxiĂšme approche est gĂ©nĂ©ralement privilĂ©giĂ©e, de sorte que chaque “chose” rĂ©side dans son propre module.

Naturellement, cela nĂ©cessite beaucoup de fichiers, car toute chose veut son propre module, mais ce n’est pas un problĂšme du tout. En fait, la navigation dans le code devient plus facile si les fichiers sont bien nommĂ©s et structurĂ©s en dossiers.

Les modules fournissent une syntaxe spĂ©ciale export default (“l’exportation par dĂ©faut”) afin d’amĂ©liorer l’aspect “une chose par module”.

Placez export default avant l’entitĂ© Ă  exporter:

// 📁 user.js
export default class User { // ajouter juste "default"
  constructor(name) {
    this.name = name;
  }
}

Il ne peut y avoir qu’un seul export default par fichier.


 Et ensuite importez-le sans accolades:

// 📁 main.js
import User from './user.js'; // pas {User}, juste User

new User('John');

Les importations sans accolades sont plus agrĂ©ables. Une erreur courante lorsque vous commencez Ă  utiliser des modules est d’oublier les accolades. Par consĂ©quent, rappelez-vous que l’import nĂ©cessite des accolades pour les exportations nommĂ©es et ne les utilise pas pour celle par dĂ©faut.

Export nommé Export par défaut
export class User {...} export default class User {...}
import {User} from ... import User from ...

Techniquement, nous pouvons avoir à la fois des exportations par défaut et des exportations nommées dans un seul module, mais dans la pratique, les gens ne les mélangent généralement pas. Un module a soit, des exports nommés, soit celui par défaut.

Comme il peut y avoir au plus une exportation par dĂ©faut par fichier, l’entitĂ© exportĂ©e peut ne pas avoir de nom.

Par exemple, ce sont toutes des exportations par défaut parfaitement valides:

export default class { // pas de nom de classe
  constructor() { ... }
}
export default function(user) { // pas de nom de fonction
  alert(`Hello, ${user}!`);
}
// exporter une seule valeur sans créer de variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

Ne pas donner de nom, c’est bien, car l’export default est unique par fichier. Par consĂ©quent, l’importation sans accolades sait ce qu’il faut importer.

Sans defaut, une telle exportation donnerait une erreur:

export class { // Erreur! (un export autre que par défaut nécessite un nom)
  constructor() {}
}

Le nom par “default”

Dans certaines situations, le mot clĂ© default est utilisĂ© pour rĂ©fĂ©rencer l’exportation par dĂ©faut.

Par exemple, pour exporter une fonction séparément de sa définition:

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

// comme si nous avions ajouté "export default" avant la fonction
export {sayHi as default};

Ou, dans un autre cas, supposons qu’un module user.js exporte un Ă©lĂ©ment principal par “dĂ©faut” et quelques Ă©lĂ©ments nommĂ©s (rarement le cas, mais ça arrive):

// 📁 user.js
export default class User {
  constructor(name) {
    this.name = name;
  }
}

export function sayHi(user) {
  alert(`Hello, ${user}!`);
}

Voici comment importer l’exportation par dĂ©faut avec celle nommĂ©e:

// 📁 main.js
import {default as User, sayHi} from './user.js';

new User('John');

Et, enfin, si vous importez tout * comme objet, la propriĂ©tĂ© default est exactement l’exportation par dĂ©faut:

// 📁 main.js
import * as user from './user.js';

let User = user.default; // l'exportation par défaut
new User('John');

Un mot contre les exportations par défaut

Les exportations nommĂ©es sont explicites. Ils nomment exactement ce qu’ils importent, nous avons donc ces informations, c’est une bonne chose.

Les exportations nommées nous obligent à utiliser exactement le bon nom pour importer :

import {User} from './user.js';
// importer {MyUser} ne fonctionnera pas, le nom devrait ĂȘtre {User}


Alors que pour une exportation par dĂ©faut, nous choisissons toujours le nom lors de l’importation:

import User from './user.js'; // fonctionne
import MyUser from './user.js'; // fonctionne aussi
// n'importe quoi pourrait ĂȘtre importĂ© ..., cela continuera de fonctionner

Les membres de l’équipe peuvent donc utiliser des noms diffĂ©rents pour importer la mĂȘme chose, et ce n’est pas bien.

Habituellement, pour éviter cela et garder le code cohérent, il existe une rÚgle voulant que les variables importées correspondent aux noms de fichier, par exemple:

import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...

NĂ©anmoins, certaines Ă©quipes considĂšrent qu’il s’agit d’un grave inconvĂ©nient des exportations par dĂ©faut. Ils prĂ©fĂšrent donc toujours utiliser des exportations nommĂ©es. MĂȘme si une seule chose est exportĂ©e, elle est toujours exportĂ©e sous un nom, sans default.

Cela facilite également la réexportation (voir ci-dessous).

Réexportation

La syntaxe “re-export” export ... from ... permet d’importer et d’exporter immĂ©diatement des Ă©lĂ©ments (Ă©ventuellement sous un autre nom), comme ceci:

export {sayHi} from './say.js'; // réexportez sayHi

export {default as User} from './user.js'; // réexportez default

Pourquoi cela peut ĂȘtre nĂ©cessaire ? Voyons un cas d’utilisation pratique.

Imaginez, nous Ă©crivons un “package”: un dossier avec beaucoup de modules, avec une partie des fonctionnalitĂ©s exportĂ©es Ă  l’extĂ©rieur (des outils comme NPM nous permettent de publier et de distribuer de tels packages, mais nous n’avons pas Ă  les utiliser), et de nombreux modules ne sont que des “helpers”, destinĂ©s Ă  une utilisation interne dans d’autres modules de package.

La structure de fichier pourrait ĂȘtre comme ceci :

auth/
    index.js
    user.js
    helpers.js
    tests/
        login.js
    providers/
        github.js
        facebook.js
        ...

Nous aimerions exposer les fonctionnalitĂ©s du paquet via un seul point d’entrĂ©e.

En d’autres termes, une personne souhaitant utiliser notre package ne doit importer que depuis le “fichier principal” auth/index.js.

Comme ceci :

import {login, logout} from 'auth/index.js'

Le “fichier principal”, auth / index.js exporte toutes les fonctionnalitĂ©s que nous aimerions fournir dans notre package.

L’idĂ©e est que les tiers, les dĂ©veloppeurs qui utilisent notre package, ne doivent pas se mĂȘler de sa structure interne, rechercher des fichiers dans notre dossier de packages. Nous n’exportons que ce qui est nĂ©cessaire dans auth / index.js et gardons le reste cachĂ© des regards indiscrets.

La fonctionnalitĂ© exportĂ©e Ă©tant dispersĂ©e dans le package, nous pouvons l’importer dans auth / index.js et l’exporter:

// 📁 auth/index.js

// importer les login / logout et les exporter immédiatement
import {login, logout} from './helpers.js';
export {login, logout};

// importer par défaut en tant qu'utilisateur et l'exporter
import User from './user.js';
export {User};
...

Maintenant, les utilisateurs de notre paquet peuvent import {login} from "auth/index.js".

La syntaxe export ... from ... est juste une notation plus courte pour importer et exporter directement:

// 📁 auth/index.js
// re-export login/logout
export {login, logout} from './helpers.js';

// re-export l'exportation par défaut en tant qu'User
export {default as User} from './user.js';
...

La diffĂ©rence notable entre export ... from et import/export est que les modules rĂ©exportĂ©s ne sont pas disponibles dans le fichier actuel. Donc, dans l’exemple ci-dessus de auth/index.js, nous ne pouvons pas utiliser les fonctions login/logout rĂ©exportĂ©es.

RĂ©-exportation de l’exportation par dĂ©faut

L’exportation par dĂ©faut nĂ©cessite un traitement sĂ©parĂ© lors de la rĂ©exportation.

Supposons que nous ayons user.js avec le export default class User et que nous souhaitons le réexporter :

// 📁 user.js
export default class User {
  // ...
}

On peut y rencontrer deux problĂšmes :

  1. export User from './user.js' çe ne fonctionnera pas
 Cela conduirait à une erreur de syntaxe.

    Pour rĂ©exporter l’exportation par dĂ©faut, nous devrions Ă©crire export {default as User}, comme dans l’exemple ci-dessus.

  2. export * from './user.js' ne réexporte que les exportations nommées, et ignore celle par défaut.

    Si nous souhaitons rĂ©exporter l’export nommĂ© et l’export par dĂ©faut, deux instructions sont nĂ©cessaires:

    export * from './user.js'; // réexporter les exportations nommées
    export {default} from './user.js'; // réexporter l'exportation par défaut

Ces bizarreries de rĂ©exporter une exportation par dĂ©faut sont l’une des raisons pour lesquelles certains dĂ©veloppeurs n’aiment pas les exportations par dĂ©faut et prĂ©fĂšrent les exportations nommĂ©es.

Résumé

Voici tous les types d’export que nous avons abordĂ©s dans ce chapitre et dans les chapitres prĂ©cĂ©dents.

Vous pouvez vĂ©rifier vous-mĂȘme en les lisant et en vous rappelant leur signification:

  • Avant la dĂ©claration d’une classe / fonction / 
:
    • export [default] class/function/variable ...
  • Exportation autonome:
    • export {x [as y], ...}.
  • RĂ©exportation:
    • export {x [as y], ...} from "module"
    • export * from "module" (ne rĂ©-exporte pas par dĂ©faut).
    • export {default [as y]} from "module" (rĂ©-export par dĂ©faut).

Import:

  • Importations d’exports nommĂ©s :
    • import {x [as y], ...} from "module"
  • Importation de l’export par dĂ©faut :
    • import x from "module"
    • import {default as x} from "module"
  • Tout importer :
    • import * as obj from "module"
  • Importer le module (son code s’exĂ©cute), mais ne l’affecte pas Ă  une variable :
    • import "module"

Nous pouvons mettre des dĂ©clarations import/export en haut ou en bas d’un script, cela n’a pas d’importance.

Donc, techniquement, ce code est correct:

sayHi();

// ...

import {sayHi} from './say.js'; // importer Ă  la fin du fichier

En pratique, les importations se font gĂ©nĂ©ralement au dĂ©but du fichier, mais ce n’est que pour des raisons de commoditĂ©.

Veuillez noter que les instructions import/export ne fonctionnent pas si elles sont Ă  l’intĂ©rieur {...}.

Une importation conditionnelle, comme celle-ci, ne fonctionnera pas:

if (something) {
  import {sayHi} from "./say.js"; // Erreur: l'importation doit ĂȘtre au plus haut niveau
}


Mais que se passe-t-il si nous devons vraiment importer quelque chose de maniĂšre conditionnelle? Ou au bon moment? Aimez-vous, charger un module sur demande, quand c’est vraiment nĂ©cessaire?

Nous verrons les importations dynamiques dans le chapitre suivant.

Carte du tutoriel

Commentaires

lire ceci avant de commenter

  • Si vous avez des amĂ©liorations Ă  suggĂ©rer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de prĂ©ciser.
  • Pour insĂ©rer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes – enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen
)