W3docs

JavaScript Dynamic import()

Apprenez le dynamic import() en JavaScript — chargez des modules à la demande pour le découpage de code et le chargement différé, avec await, .then() et la gestion des erreurs.

Les imports dynamiques en JavaScript sont une fonctionnalité introduite dans ECMAScript 2020 (ES2020) qui vous permet de charger un module au moment de l'exécution, à la demande, au lieu de tout charger d'un coup lors du premier parsing du script. Contrairement à l'instruction statique import, la forme import() est une expression semblable à une fonction qui retourne une promesse, ce qui vous permet de décider quand et si charger un module en fonction de conditions, d'actions utilisateur ou du routage. Ce guide couvre la syntaxe, les cas d'utilisation les plus courants (découpage de code, chargement différé, chargement conditionnel), la gestion des erreurs, et un exemple complet exécutable.

Ce chapitre suppose que vous êtes à l'aise avec les modules ES et async/await.

Import statique vs. import() dynamique

Une instruction import statique doit apparaître au niveau supérieur d'un module et est entièrement résolue avant l'exécution du code du module. Cela la rend prévisible et compatible avec les outils, mais cela signifie aussi que chaque module importé statiquement est chargé d'emblée — même le code que l'utilisateur n'atteindra peut-être jamais.

import() diffère sur trois points importants :

  • C'est une expression, pas une instruction, donc elle peut apparaître n'importe où — dans un if, une fonction ou un gestionnaire d'événements.
  • Elle accepte un spécificateur dynamique : le chemin du module peut être une variable ou une chaîne calculée, pas seulement un littéral de chaîne.
  • Elle retourne une promesse qui se résout en l'objet espace de noms du module (un object dont les propriétés sont les exports nommés du module, plus default).
// Static import — runs at parse time, must be top-level
import { formatDate } from './utils.js';

// Dynamic import — runs when this line executes, can be anywhere
const utils = await import('./utils.js');
utils.formatDate(new Date());

Puisque import() retourne une promesse, vous gérez le résultat avec await (dans une fonction async) ou avec .then()/.catch() :

// With await
const mod = await import('./utils.js');

// With .then() / .catch()
import('./utils.js')
  .then(mod => mod.formatDate(new Date()))
  .catch(err => console.error('Failed to load module:', err));

Lire les exports depuis l'objet module

La valeur résolue est l'objet espace de noms du module. Les exports nommés sont des propriétés ; l'export par défaut se trouve sous la clé default. La déstructuration rend cela plus lisible :

// math.js exports: export function add(a,b){...}, export default function greet(){...}
const { add, default: greet } = await import('./math.js');

console.log(add(2, 3)); // 5
console.log(greet());   // "hello"
Info

await import(...) ne fonctionne qu'à l'intérieur d'une fonction async ou au niveau supérieur d'un module ES (top-level await). Dans un <script> ordinaire ou une fonction non-async, utilisez plutôt la forme .then().

Cas d'utilisation courants

Les imports dynamiques excellent lorsqu'une partie de votre application est utilisée conditionnellement ou n'est pas immédiatement nécessaire. Voici les patterns les plus courants.

Découpage de code

Le cas d'utilisation le plus courant des imports dynamiques est le découpage de code : diviser votre bundle en morceaux plus petits qui ne se chargent que lorsque c'est nécessaire — généralement lors de la visite d'une route ou de l'utilisation d'une fonctionnalité. Ci-dessous, un script volumineux n'est récupéré qu'après que l'utilisateur clique, au lieu d'alourdir le chargement initial de la page.

button.addEventListener('click', function () {
    import('./heavyScript.js').then(mod => {
        mod.runHeavyTask();
    });
});

Puisque le gestionnaire de clic est synchrone, la forme .then() est utilisée ici plutôt qu'await. Le navigateur (ou le bundler) demande heavyScript.js uniquement au premier clic ; les clics suivants réutilisent le module mis en cache.

Avertissement

Mesurez avant de découper. Ajouter trop de petits morceaux dynamiques peut nuire aux performances — chacun représente un aller-retour réseau supplémentaire. Réservez les imports dynamiques au code réellement volumineux ou rarement utilisé.

Chargement différé de composants

Des frameworks comme React, Angular et Vue utilisent les imports dynamiques en coulisses pour charger les composants en différé — un composant n'est récupéré que lors de son premier rendu.

// Lazy loading a component in React
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
    return (
        <React.Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
        </React.Suspense>
    );
}

React.lazy enveloppe l'import dynamique, et React.Suspense affiche le fallback jusqu'à l'arrivée du morceau. L'utilisateur voit Loading... seulement pendant le bref instant où le composant est récupéré.

Utilisation avancée des imports dynamiques

Chargement conditionnel

Puisque import() est une expression, vous pouvez la protéger avec n'importe quelle condition — un indicateur de fonctionnalité, un paramètre utilisateur, l'environnement, ou même la locale du navigateur.

if (user.prefersAdvancedMode) {
    const advanced = await import('./advancedEditor.js');
    advanced.init();
}

Les utilisateurs qui n'activent jamais le mode avancé ne téléchargeront jamais advancedEditor.js. Vous pouvez aller plus loin avec un spécificateur dynamique — charger un module différent par locale, par exemple :

const locale = navigator.language.startsWith('fr') ? 'fr' : 'en';
const messages = await import(`./locales/${locale}.js`);
console.log(messages.default.greeting);
Avertissement

Les bundlers comme Webpack et Vite doivent savoir quels fichiers pourraient être chargés. Un spécificateur entièrement arbitraire (par exemple un chemin construit à partir d'une entrée utilisateur) ne peut pas être bundlé. Gardez la partie variable du chemin dans un répertoire et une extension connus, comme dans l'exemple de locale ci-dessus.

Support des outils de build et de Node.js

Lorsque vous écrivez import('./module.js'), des bundlers comme Webpack, Rollup et Vite émettent automatiquement un morceau séparé et le chargent à la demande — aucune configuration supplémentaire n'est généralement nécessaire. Dans le navigateur, import() natif est pris en charge par tous les navigateurs modernes.

import() fonctionne également dans Node.js (v12+), y compris dans les fichiers CommonJS, ce qui est la façon standard de charger un module ES depuis du code CommonJS :

// Loading an ESM module from a CommonJS file
async function run() {
    const { default: chalk } = await import('chalk');
    console.log(chalk.green('Loaded an ESM package from CommonJS'));
}
run();

Métadonnées de module avec import.meta

À l'intérieur d'un module, vous pouvez lire import.meta pour obtenir des informations contextuelles. Le champ le plus largement pris en charge est import.meta.url, qui contient l'URL du module courant — pratique pour résoudre des ressources voisines :

// Resolve a JSON file relative to the current module
const dataUrl = new URL('./data.json', import.meta.url);
const data = await import(dataUrl, { with: { type: 'json' } });

Exemple complet : widget météo dynamique

Le widget météo chargera dynamiquement le module de récupération des données météo uniquement lorsque l'utilisateur le demande. C'est un scénario idéal pour les imports dynamiques, car il diffère le chargement d'un code d'interaction API potentiellement lourd jusqu'à ce qu'il soit réellement nécessaire.

L'exemple utilise trois fichiers : une page HTML, un script d'entrée, et le module chargé en différé.

index.html :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dynamic Weather Widget</title>
</head>
<body>
    <h1>Weather Widget</h1>
    <button id="loadWeather">Load Weather</button>
    <div id="weatherOutput">Click the button to load the weather.</div>

    <script src="index.js"></script>
</body>
</html>

index.js :

document.getElementById('loadWeather').addEventListener('click', async () => {
    const output = document.getElementById('weatherOutput');
    try {
        const weatherModule = await import('./weatherModule.js');
        const data = await weatherModule.loadWeather();
        output.textContent = `Weather: ${data.weather}`;
    } catch (err) {
        output.textContent = 'Failed to load weather data.';
    }
});

Ce code déclenche un import dynamique lors d'une interaction utilisateur :

  1. Écouteur d'événement : Attache un gestionnaire de clic au bouton.
  2. Import dynamique : Utilise await import() pour récupérer le module uniquement au clic, gardant le bundle initial petit.
  3. Gestion des erreurs : Le bloc try...catch enveloppe à la fois l'import() et l'appel de données, de sorte qu'un téléchargement échoué ou une requête rejetée affiche le message de repli.

Cette approche aide à rendre les pages web efficaces et réactives en ne chargeant les ressources que lorsque c'est nécessaire et en fournissant un retour immédiat aux interactions utilisateur.

weatherModule.js :

export async function loadWeather() {
    // Simulated API call
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ weather: 'Sunny, 76°F' });  // Simulating weather data
        }, 1000);
    });
}

La fonction simule la récupération de données depuis une source distante sans avoir besoin d'une vraie API : elle se résout après un délai d'une seconde pour que vous puissiez voir le chargement différé en action.

Explication de l'exemple

  • Configuration HTML : Fournit un bouton et un conteneur pour le résultat.
  • Import dynamique en action : Un clic sur le bouton déclenche le chargement de weatherModule.js par index.js à la demande.
  • Module météo : Simule un délai API, montrant comment les imports dynamiques diffèrent la logique lourde ou conditionnelle jusqu'à ce qu'elle soit réellement nécessaire.

Pièges courants

  • await hors d'un module ou d'une fonction async. Le await import() au niveau supérieur ne fonctionne que dans les modules ES ; dans les scripts ordinaires ou les callbacks non-async, utilisez .then().
  • Oublier .default. L'export par défaut d'un module est accessible via la propriété default de l'objet résolu, pas l'objet lui-même.
  • Chemins entièrement dynamiques. Les bundlers ne peuvent pas découper un chemin qu'ils ne peuvent pas analyser. Gardez la partie littérale du spécificateur (répertoire et extension) statique.
  • Découpage excessif. Chaque morceau dynamique est une requête séparée. Découpez le code volumineux ou rarement utilisé, pas chaque petit utilitaire.

Conclusion

Le import() dynamique vous permet de charger des modules à la demande, retournant une promesse qui se résout en l'objet espace de noms du module. Il alimente le découpage de code, les composants chargés en différé, le chargement conditionnel, et les imports adaptés à la locale — améliorant les performances au démarrage lorsqu'il est utilisé de manière délibérée. Combinez-le avec async/await et une solide gestion des erreurs, et appuyez-vous sur votre bundler pour transformer chaque import() en un morceau optimisé.

Pour approfondir, consultez les modules ES : export et import, l'introduction aux modules, et les promesses.

Entraînement

Pratique
Quelles affirmations sur le dynamic import() en JavaScript sont correctes ?
Quelles affirmations sur le dynamic import() en JavaScript sont correctes ?
Was this page helpful?