Stockage KV : le premier module intégré sur le Web

Depuis la majeure partie de la dernière décennie, les fournisseurs de navigateurs et les experts en performances Web affirment que localStorage est lent et que les développeurs Web devraient cesser de l'utiliser.

Pour être honnête, ces personnes ne se trompent pas. localStorage est une API synchrone qui bloque le thread principal. Chaque fois que vous y accédez, vous empêchez potentiellement votre page d'être interactive.

Le problème est que l'API localStorage est tellement simple qu'il est tentant de l'utiliser. La seule alternative asynchrone à localStorage est IndexedDB, qui (avouons-le) n'est pas réputée pour sa facilité d'utilisation ni pour son API conviviale.

Les développeurs doivent donc choisir entre une solution difficile à utiliser et une solution qui nuit aux performances. Bien qu'il existe des bibliothèques qui offrent la simplicité de l'API localStorage tout en utilisant en réalité des API de stockage asynchrones en arrière-plan, l'inclusion de l'une de ces bibliothèques dans votre application a un coût en termes de taille de fichier et peut réduire votre budget de performances.

Mais que se passerait-il si vous pouviez obtenir les performances d'une API de stockage asynchrone avec la simplicité de l'API localStorage, sans avoir à payer le coût de la taille de fichier ?

Eh bien, peut-être bientôt. Chrome expérimente une nouvelle fonctionnalité appelée modules intégrés. Le premier que nous prévoyons de déployer est un module de stockage de clés/valeurs asynchrone appelé KV Storage.

Mais avant d'entrer dans les détails du module de stockage KV, laissez-moi vous expliquer ce que j'entends par modules intégrés.

Que sont les modules intégrés ?

Les modules intégrés sont comme les modules JavaScript standards, à la différence qu'ils ne doivent pas être téléchargés, car ils sont fournis avec le navigateur.

Comme les API Web traditionnelles, les modules intégrés doivent passer par un processus de standardisation. Chacun d'eux dispose de ses propres spécifications qui nécessitent un examen de la conception et des signes positifs de la part des développeurs Web et d'autres fournisseurs de navigateurs avant de pouvoir être distribués. (Dans Chrome, les modules intégrés suivent le même processus de lancement que celui que nous utilisons pour implémenter et distribuer toutes les nouvelles API.)

Contrairement aux API Web traditionnelles, les modules intégrés ne sont pas exposés au niveau de la portée globale. Ils ne sont disponibles que via des importations.

Ne pas exposer les modules intégrés de manière globale présente de nombreux avantages: ils n'ajoutent aucune surcharge au démarrage d'un nouveau contexte d'exécution JavaScript (par exemple, un nouvel onglet, un worker ou un service worker), et ils ne consomment aucune mémoire ni aucun processeur, sauf s'ils sont réellement importés. De plus, elles ne risquent pas de provoquer des collisions de noms avec d'autres variables définies dans votre code.

Pour importer un module intégré, vous devez utiliser le préfixe std: suivi de l'identifiant du module intégré. Par exemple, dans les navigateurs compatibles, vous pouvez importer le module KV Storage avec le code suivant (voir ci-dessous pour apprendre à utiliser un polyfill KV Storage dans les navigateurs non compatibles):

import storage, {StorageArea} from 'std:kv-storage';

Module de stockage KV

La simplicité du module de stockage KV est semblable à celle de l'API localStorage, mais sa forme d'API est en fait plus proche d'un Map JavaScript. Au lieu de getItem(), setItem() et removeItem(), utilisez get(), set() et delete(). Il dispose également d'autres méthodes de mappage qui ne sont pas disponibles pour localStorage, comme keys(), values() et entries(). Comme pour Map, ses clés ne doivent pas nécessairement être des chaînes. Il peut s'agir de n'importe quel type sérialisable structuré.

Contrairement à Map, toutes les méthodes KV Storage renvoient des promesses ou des itérateurs asynchrones (car l'objectif principal de ce module est qu'il n'est pas synchrone, contrairement à localStorage). Pour consulter l'API complète en détail, vous pouvez consulter la spécification.

Comme vous avez pu le constater dans l'exemple de code ci-dessus, le module de stockage KV comporte une exportation par défaut storage et une exportation nommée StorageArea.

storage est une instance de la classe StorageArea portant le nom 'default'. C'est ce que les développeurs utiliseront le plus souvent dans le code de leur application. La classe StorageArea est fournie dans les cas où une isolation supplémentaire est nécessaire (par exemple, une bibliothèque tierce qui stocke des données et souhaite éviter les conflits avec les données stockées via l'instance storage par défaut). Les données StorageArea sont stockées dans une base de données IndexedDB avec le nom kv-storage:${name}, où le nom est le nom de l'instance StorageArea.

Voici un exemple d'utilisation du module de stockage KV dans votre code:

import storage from 'std:kv-storage';

const main = async () => {
  const oldPreferences = await storage.get('preferences');

  document.querySelector('form').addEventListener('submit', async () => {
    const newPreferences = Object.assign({}, oldPreferences, {
      // Updated preferences go here...
    });

    await storage.set('preferences', newPreferences);
  });
};

main();

Que se passe-t-il si un navigateur n'est pas compatible avec un module intégré ?

Si vous savez utiliser des modules JavaScript natifs dans les navigateurs, vous savez probablement qu'(au moins jusqu'à présent) importer autre chose qu'une URL génère une erreur. std:kv-storage n'est pas une URL valide.

Cela soulève la question suivante: devons-nous attendre que tous les navigateurs acceptent les modules intégrés avant de pouvoir les utiliser dans notre code ? Heureusement, la réponse est non.

Vous pouvez utiliser des modules intégrés dès qu'un seul navigateur les prend en charge grâce à une autre fonctionnalité que nous testons, appelée importation de cartes.

Importer des cartes

Les mappages d'importation sont essentiellement un mécanisme permettant aux développeurs d'associer des alias d'identifiants d'importation à un ou plusieurs identifiants alternatifs.

Cette fonctionnalité est puissante, car elle vous permet de modifier (au moment de l'exécution) la façon dont un navigateur résout un identifiant d'importation particulier dans l'ensemble de votre application.

Dans le cas des modules intégrés, cela vous permet de référencer un polyfill du module dans le code de votre application, mais un navigateur compatible avec le module intégré peut charger cette version à la place.

Voici comment déclarer une carte d'importation pour que cela fonctionne avec le module de stockage KV:

<!-- The import map is inlined into your page -->
<script type="importmap">
{
  "imports": {
    "/path/to/kv-storage-polyfill.mjs": [
      "std:kv-storage",
      "/path/to/kv-storage-polyfill.mjs"
    ]
  }
}
</script>

<!-- Then any module scripts with import statements use the above map -->
<script type="module">
  import storage from '/path/to/kv-storage-polyfill.mjs';

  // Use `storage` ...
</script>

L'élément clé du code ci-dessus est que l'URL /path/to/kv-storage-polyfill.mjs est mappée sur deux ressources différentes: std:kv-storage, puis l'URL d'origine, /path/to/kv-storage-polyfill.mjs.

Ainsi, lorsque le navigateur rencontre une instruction d'importation référençant cette URL (/path/to/kv-storage-polyfill.mjs), il tente d'abord de charger std:kv-storage, et si ce n'est pas possible, il revient au chargement de /path/to/kv-storage-polyfill.mjs.

Encore une fois, la magie réside dans le fait que le navigateur n'a pas besoin de prendre en charge les mappages d'importation ni les modules intégrés pour que cette technique fonctionne, car l'URL transmise à l'instruction d'importation est l'URL du polyfill. Le polyfill n'est pas vraiment un remplacement, il s'agit de la valeur par défaut. Le module intégré est une amélioration progressive.

Que faire des navigateurs qui ne sont pas du tout compatibles avec les modules ?

Pour utiliser des mappages d'importation afin de charger des modules intégrés de manière conditionnelle, vous devez utiliser des instructions import, ce qui signifie également que vous devez utiliser des scripts de module, c'est-à-dire <script type="module">.

Actuellement, plus de 80% des navigateurs sont compatibles avec les modules. Pour les navigateurs qui ne le sont pas, vous pouvez utiliser la technique module/nomodule pour diffuser un ancien bundle. Notez que lorsque vous générez votre build nomodule, vous devez inclure tous les polyfills, car vous savez avec certitude que les navigateurs qui ne sont pas compatibles avec les modules ne seront certainement pas compatibles avec les modules intégrés.

Démonstration de KV Storage

Pour illustrer qu'il est possible d'utiliser des modules intégrés tout en prenant en charge les anciens navigateurs, j'ai créé une démo qui intègre toutes les techniques décrites ci-dessus et s'exécute dans tous les navigateurs actuels:

  • Les navigateurs compatibles avec les modules, les cartes d'importation et le module intégré ne chargent aucun code inutile.
  • Les navigateurs compatibles avec les modules et les cartes d'importation, mais non avec le module intégré, chargent le polyfill de stockage KV (via le chargeur de module du navigateur).
  • Les navigateurs compatibles avec les modules, mais non avec les cartes d'importation, chargent également le polyfill de stockage KV (via le chargeur de modules du navigateur).
  • Les navigateurs qui n'acceptent pas du tout les modules reçoivent le polyfill de stockage KV dans leur ancien bundle (chargé via <script nomodule>).

La démonstration est hébergée sur Glitch. Vous pouvez donc consulter son code source. Je dispose également d'une explication détaillée de l'implémentation dans le fichier README. N'hésitez pas à y jeter un œil si vous voulez voir comment il est conçu.

Pour voir le module natif intégré en action, vous devez charger la démonstration dans Chrome 74 ou version ultérieure avec l'indicateur expérimental des fonctionnalités de la plate-forme Web activé (chrome://flags/#enable-experimental-web-platform-features).

Vous pouvez vérifier que le module intégré est en cours de chargement, car vous ne verrez pas le script de polyfill dans le panneau Source de DevTools. À la place, vous verrez la version du module intégré (fait amusant: vous pouvez réellement inspecter le code source du module ou même y placer des points d'arrêt):

Source du module de stockage KV dans les outils pour les développeurs Chrome

Envoyez-nous vos commentaires

Cette introduction vous a donné un aperçu des possibilités offertes par les modules intégrés. J'espère que vous êtes impatient ! Nous aimerions vraiment que les développeurs testent le module KV Storage (ainsi que toutes les nouvelles fonctionnalités abordées ici) et nous fassent part de leurs commentaires.

Voici les liens GitHub où vous pouvez nous envoyer vos commentaires sur chacune des fonctionnalités mentionnées dans cet article:

Si votre site utilise actuellement localStorage, nous vous conseillons de passer à l'API KV Storage pour voir si elle répond à tous vos besoins. Si vous vous inscrivez à l'essai de l'origine KV Storage, vous pouvez déployer ces fonctionnalités dès aujourd'hui. Tous vos utilisateurs devraient bénéficier de meilleures performances de stockage, et les utilisateurs de Chrome 74 ou version ultérieure ne paieront aucun coût de téléchargement supplémentaire.