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

Depuis près de dix ans, les fournisseurs de navigateurs et les experts en performances Web estiment que localStorage est lent et que les développeurs Web doivent cesser de l'utiliser.

Pour être juste, les personnes qui disent cela ne sont pas fausses. localStorage est une API synchrone qui bloque le thread principal. Chaque fois que vous y accédez, vous risquez d'empêcher votre page d'être interactive.

Le problème est que l'API localStorage est d'une simplicité tentante et que la seule alternative asynchrone à localStorage est IndexedDB, qui n'est pas connu pour sa facilité d'utilisation ni son API accueillante.

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

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

Eh bien, il se peut qu'il y en ait bientôt. Chrome teste une nouvelle fonctionnalité appelée modules intégrés. Le premier que nous prévoyons de proposer est un module de stockage clé/valeur asynchrone appelé KV Storage.

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

Que sont les modules intégrés ?

Les modules intégrés sont tout comme les modules JavaScript standards, sauf qu'ils n'ont pas besoin d'être téléchargés, car ils sont fournis avec le navigateur.

Comme les API Web traditionnelles, les modules intégrés doivent suivre un processus de standardisation. Chacun d'entre eux aura sa propre spécification qui nécessite un examen de conception et des signes positifs de compatibilité de la part des développeurs Web et d'autres fournisseurs de navigateurs avant de pouvoir être expédiés. (Dans Chrome, les modules intégrés suivront le même processus de lancement que pour implémenter et diffuser toutes les nouvelles API.)

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

Ne pas exposer les modules intégrés à l'échelle mondiale présente de nombreux avantages: ils n'entraînent pas de surcharge lors du démarrage d'un nouveau contexte d'exécution JavaScript (par exemple, un nouvel onglet, nœud de calcul ou service worker), et ne consomment ni mémoire, ni processeur, sauf s'ils sont effectivement importés. De plus, elles ne présentent pas de risque de conflit de noms avec d'autres variables définies dans votre code.

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

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

Module de stockage de KV

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

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

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

storage est une instance de la classe StorageArea nommée 'default'. C'est l'instance que les développeurs utiliseront le plus souvent dans leur code d'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 celles stockées via l'instance storage par défaut). Les données StorageArea sont stockées dans une base de données IndexedDB nommée kv-storage:${name}, où "name" correspond au nom de l'instance StorageArea.

Voici un exemple d'utilisation du module de stockage de 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 ne prend pas en charge un module intégré ?

Si vous avez l'habitude d'utiliser des modules JavaScript natifs dans les navigateurs, vous savez probablement (du moins jusqu'à présent) que toute importation autre qu'une URL génère une erreur. De plus, std:kv-storage n'est pas une URL valide.

Cela soulève donc 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 les modules intégrés dès qu'un même navigateur les accepte grâce à une autre fonctionnalité que nous testons, les cartes d'importation.

Importer des cartes

Les mappages d'importation sont essentiellement un mécanisme qui permet aux développeurs d'alias d'identifiants d'importation pour un ou plusieurs identifiants alternatifs.

Cette fonctionnalité est très efficace, car elle vous permet de modifier (au moment de l'exécution) la manière dont un navigateur résout un identifiant d'importation particulier pour 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 un mappage d'importation pour que cela fonctionne avec le module de stockage de 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>

Le point essentiel 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 à nouveau l'URL d'origine, /path/to/kv-storage-polyfill.mjs.

Ainsi, lorsque le navigateur rencontre une instruction d'importation faisant référence à cette URL (/path/to/kv-storage-polyfill.mjs), il tente d'abord de charger std:kv-storage. Si ce n'est pas le cas, il se rabat sur le chargement de /path/to/kv-storage-polyfill.mjs.

Là encore, la magie opère : le navigateur n'a pas besoin de prendre en charge l'importation de cartes ni de modules intégrés pour que cette technique fonctionne, car l'URL transmise à l'instruction d'importation est celle du polyfill. Le polyfill n'est pas réellement une solution de secours, il s'agit du polyfill par défaut. Le module intégré est une amélioration progressive !

Qu'en est-il des navigateurs qui ne sont pas du tout compatibles avec les modules ?

Pour utiliser des mappages d'importation pour charger de manière conditionnelle des modules intégrés, vous devez utiliser des instructions import, ce qui signifie également que vous devez utiliser des scripts de module, par exemple <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 que les navigateurs qui ne prennent pas en charge les modules ne prendront certainement pas en charge les modules intégrés.

Démonstration de KV Storage

Pour illustrer qu'il est possible d'utiliser les modules intégrés tout en étant compatible avec d'anciens navigateurs, j'ai réalisé une démonstration qui intègre toutes les techniques décrites ci-dessus et fonctionne dans tous les navigateurs à l'heure actuelle:

  • Les navigateurs qui acceptent les modules, importent des cartes et le module intégré ne chargent pas de code inutile.
  • Les navigateurs qui acceptent les modules et importent des cartes, mais qui ne sont pas compatibles avec le module intégré, chargent le polyfill de stockage KV (via le chargeur de module du navigateur).
  • Les navigateurs qui acceptent les modules, mais ne prennent pas en charge l'importation de cartes chargent également le polyfill de stockage KV (via le chargeur de module du navigateur).
  • Les navigateurs qui ne prennent pas du tout en charge 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 sa source. Une explication détaillée de l'implémentation est également disponible dans le fichier README. N'hésitez pas à jeter un œil à cette fonctionnalité si vous voulez savoir comment elle se présente.

Pour voir le module natif intégré en action, vous devez charger la démo dans Chrome 74 ou version ultérieure en activant l'indicateur des fonctionnalités expérimentales de la plate-forme Web (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 des outils de développement. À la place, vous verrez la version du module intégré (car vous pouvez en fait inspecter le code source du module ou même y insérer des points d'arrêt) :

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

Donnez-nous votre avis

Cette présentation devrait vous avoir donné un aperçu des possibilités offertes par les modules intégrés. Et j'espère que vous êtes impatient ! Nous aimerions que les développeurs testent le module de stockage de KV (ainsi que toutes les nouvelles fonctionnalités décrites ici) et nous font part de leurs commentaires.

Voici les liens GitHub permettant de nous faire part de vos commentaires sur chacune des fonctionnalités mentionnées dans cet article:

Si votre site utilise actuellement localStorage, vous devriez essayer de passer à l'API KV Storage pour voir si elle répond à tous vos besoins. Si vous vous inscrivez à la phase d'évaluation de 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 et versions ultérieures n'auront pas à payer de frais de téléchargement supplémentaires.