Dépendances asynchrones conditionnelles

Fonctionnalités Web : Top-level await

await de premier niveau permet aux modules d'agir comme des fonctions asynchrones, ce qui signifie qu'ils peuvent suspendre l'exécution du module pour attendre les promesses. Cette fonctionnalité est extrêmement utile pour charger de manière conditionnelle des dépendances asynchrones (comme des polyfills ou des bibliothèques secondaires volumineuses) uniquement lorsque le navigateur en a besoin.

En utilisant l'instruction "await" de premier niveau, vous pouvez encapsuler la logique de chargement conditionnel dans un seul module, ce qui empêche l'exécution des modules consommateurs en aval tant que la dépendance n'est pas entièrement chargée et prête.

Modèle de polyfill conditionnel

Bien que l'instruction await de premier niveau puisse être utilisée pour charger de manière conditionnelle n'importe quelle dépendance asynchrone qui est un module, une bonne utilisation du modèle de chargement de dépendance conditionnelle consiste à charger de manière conditionnelle des polyfills pour les navigateurs qui ne sont pas compatibles avec une fonctionnalité spécifique. Cette approche encapsule la détection des fonctionnalités et l'importation dynamique dans un seul module de dépendance.

Dans le cas suivant, le polyfill de l'attribut popover est chargé de manière conditionnelle s'il n'est pas présent sur HTMLElement.prototype :

// conditionally-load-polyfill.js

// Check if the feature is missing before doing work.
// MANDATORY: Prefer checking HTMLElement.prototype over window or document
// when checking for a global DOM attribute or property like popover.
if (!('popover' in HTMLElement.prototype)) {
  // Use top-level await to pause the execution of any module that imports this file 
  // until the polyfill finishes downloading and executing.
  await import('/path/to/popover-polyfill.js');
}

// Export a marker if needed by your application
export const polyfillLoaded = true;
// main.js

// MANDATORY: Because conditionally-load-polyfill.js uses top-level await, 
// this import will block execution of main.js until the polyfill is ready.
import './conditionally-load-polyfill.js';

// Now it is safe to use the feature (e.g., showing a popover)
const myPopover = document.getElementById('my-popover');
if (myPopover) {
  myPopover.showPopover();
}

Séquencer les importations de modules et éviter le bug de Safari 27

OBLIGATOIRE : Vous devez structurer votre graphique de modules de sorte que le module await de premier niveau soit importé à partir d'un seul point d'entrée et se termine avant l'évaluation des modules en aval :

  • NE PLACEZ PAS d'importations statiques sœurs dans un fichier d'entrée (par exemple, import './conditionally-load-polyfill.js'; import './app.js';). Conformément à la spécification d'évaluation des modules ECMAScript, l'instruction await de premier niveau ne suspend que le module parent qui l'importe, et non les importations statiques sœurs. Par conséquent, ./app.js sera évalué simultanément dans tous les navigateurs avant la fin du chargement du polyfill.
  • N'importez PAS le module await de premier niveau à partir de plusieurs modules frères simultanément (par exemple, en important de manière statique ./conditionally-load-polyfill.js à la fois dans a.js et b.js). Bien que Safari 27 ait corrigé le chargeur de module de WebKit pour prendre en charge les importations simultanées de modules await de premier niveau conformément aux spécifications, Safari 15 à 26.6 échoue lorsque plusieurs modules importent simultanément un module contenant un await de premier niveau (voir la compatibilité des navigateurs MDN).

Choisissez plutôt l'un des deux modèles suivants en fonction de la structure de votre module :

  1. Module d'entrée unique (illustré dans main.js ci-dessus) : si votre module d'entrée exécute directement la logique d'initialisation dépendante, importez de manière statique ./conditionally-load-polyfill.js en haut de ce module et exécutez votre code dans son corps.
  2. Amorçage de modules d'application distincts : si votre module d'entrée délègue à des modules d'application en aval (comme ./app.js), enchaînez les appels import() dynamiques avec await de niveau supérieur afin que ./app.js ne soit récupéré et évalué qu'après la résolution du module polyfill :
// main.js

// MANDATORY: Chain dynamic imports with top-level await when bootstrapping separate
// application modules so './app.js' waits for the polyfill to finish loading.
// DO NOT use sibling static imports (`import './conditionally-load-polyfill.js'; import './app.js';`),
// which evaluate concurrently and execute './app.js' before the polyfill is ready.
await import('./conditionally-load-polyfill.js');
await import('./app.js');

Stratégies de remplacement

Bien que la fonctionnalité await de base soit compatible avec les principaux navigateurs depuis 2021 (Chrome 89, Edge 89, Firefox 89 et Safari 15), elle n'est devenue entièrement conforme aux spécifications que dans Safari 27 (voir Compatibilité des navigateurs MDN).

  • Safari 15–26.6 : tant que vous suivez le modèle de point d'entrée unique de la section précédente afin que plusieurs modules frères n'importent pas simultanément le module await de premier niveau, vous pouvez vous fier à await de premier niveau sans solution de secours supplémentaire.
  • Anciens navigateurs (avant 2021) : si votre application doit être compatible avec les navigateurs sans compatibilité await de premier niveau, évitez l'utilisation de await de premier niveau et encapsulez votre orchestration import() conditionnelle dans une fonction async standard ou une chaîne de promesses .then().