KV Storage: el primer módulo integrado de la Web

Los proveedores de navegadores y los expertos en rendimiento web dijeron durante la mayor parte de la última década que localStorage es lento, y los desarrolladores web deberían dejar de usarlo.

Para ser justos, las personas que dicen esto no están equivocados. localStorage es una API síncrona que bloquea el subproceso principal y, cada vez que accedes a ella, es posible que impidas que la página sea interactiva.

El problema es que la API de localStorage es tan tentadoramente simple, y la única alternativa asíncrona a localStorage es IndexedDB, que (seamos sinceros) no es conocida por su facilidad de uso o su API agradable.

Por lo tanto, los desarrolladores tienen la opción de elegir entre algo difícil de usar y algo malo para el rendimiento. Aunque existen bibliotecas que ofrecen la simplicidad de la API de localStorage y, al mismo tiempo, usan APIs de almacenamiento asíncrono de forma interna, incluir una de esas bibliotecas en tu app tiene un costo por tamaño de archivo y puede consumir tu presupuesto de rendimiento.

Pero ¿qué pasaría si fuera posible obtener el rendimiento de una API de almacenamiento asíncrona con la simplicidad de la API de localStorage, sin tener que pagar el costo del tamaño del archivo?

Bueno, pronto podría haberlo. Chrome está experimentando con una nueva función conocida como módulos integrados, y el primero que planeamos lanzar es un módulo de almacenamiento de par clave-valor asíncrono denominado Almacenamiento KV.

Pero antes de entrar en los detalles del módulo de almacenamiento de KV, permíteme explicarte a qué me refiero con módulos integrados.

¿Qué son los módulos integrados?

Los módulos integrados son como los módulos normales de JavaScript, excepto que no es necesario que los descargues porque se envían con el navegador.

Al igual que las APIs web tradicionales, los módulos integrados deben pasar por un proceso de estandarización: cada uno tendrá su propia especificación que requiere una revisión del diseño y signos positivos de compatibilidad por parte de los desarrolladores web y otros proveedores de navegadores antes de su envío. (En Chrome, los módulos integrados seguirán el mismo proceso de lanzamiento que usamos para implementar y enviar todas las APIs nuevas).

A diferencia de las APIs web tradicionales, los módulos integrados no están expuestos en el alcance global, sino que solo están disponibles a través de importaciones.

No exponer los módulos integrados a nivel global tiene muchas ventajas: no agregarán sobrecarga cuando se inicie un nuevo contexto de entorno de ejecución de JavaScript (p.ej., una pestaña, un trabajador o un service worker nuevos) y no consumirán memoria ni CPU, a menos que se importen realmente. Además, no corren el riesgo de colisiones de nombres con otras variables definidas en tu código.

Para importar un módulo integrado, usa el prefijo std: seguido del identificador del módulo integrado. Por ejemplo, en los navegadores compatibles, puedes importar el módulo de KV Storage con el siguiente código (consulta a continuación cómo usar un polyfill de KV Storage en navegadores no compatibles):

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

El módulo de almacenamiento KV

El módulo de KV Storage es similar en su simplicidad a la API de localStorage, pero la forma de la API es más cercana a una Map de JavaScript. En lugar de getItem(), setItem() y removeItem(), tiene get(), set() y delete(). También tiene otros métodos similares a mapas que no están disponibles para localStorage, como keys(), values() y entries(), y, al igual que Map, sus claves no tienen que ser strings. Pueden ser cualquier tipo estructurado-serializable.

A diferencia de Map, todos los métodos de KV Storage muestran promesas o iteradores asíncronos (ya que el objetivo principal de este módulo es que no es síncrono, a diferencia de localStorage). Para ver la API completa en detalle, puedes consultar la especificación.

Como puedes haber notado en el ejemplo de código anterior, el módulo de almacenamiento de KV tiene una storage de exportación predeterminada y una de exportación llamada StorageArea.

storage es una instancia de la clase StorageArea con el nombre 'default', y es lo que los desarrolladores usarán con mayor frecuencia en el código de su aplicación. La clase StorageArea se proporciona para los casos en los que se necesita aislamiento adicional (p.ej., una biblioteca de terceros que almacena datos y desea evitar conflictos con los datos almacenados a través de la instancia storage predeterminada). Los datos de StorageArea se almacenan en una base de datos IndexedDB con el nombre kv-storage:${name}, en el que el nombre es el nombre de la instancia StorageArea.

A continuación, se muestra un ejemplo de cómo usar el módulo de almacenamiento KV en tu código:

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();

¿Qué sucede si un navegador no admite un módulo integrado?

Si estás familiarizado con el uso de módulos nativos de JavaScript en navegadores, es probable que sepas que (al menos hasta ahora) importar cualquier otro elemento que no sea una URL generará un error. std:kv-storage no es una URL válida.

Eso genera la siguiente pregunta: ¿tenemos que esperar hasta que todos los navegadores admitan módulos integrados para poder usarlos en nuestro código? Por suerte, la respuesta es no.

De hecho, puedes usar módulos integrados tan pronto como un navegador los admita, gracias a la ayuda de otra función con la que estamos experimentando llamada import Maps.

Importar mapas

Las importaciones de mapas son, en esencia, un mecanismo mediante el cual los desarrolladores pueden asignar un alias de importación a uno o más identificadores alternativos.

Esto es muy útil porque te brinda una manera de cambiar (en el tiempo de ejecución) la manera en que un navegador resuelve un identificador de importación particular en toda tu aplicación.

En el caso de los módulos integrados, esto te permite hacer referencia a un polyfill del módulo en el código de la aplicación, pero un navegador compatible con el módulo integrado puede cargar esa versión.

A continuación, te mostramos cómo declarar un mapa de importación para que funcione con el módulo de KV Storage:

<!-- 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>

El punto clave del código anterior es que la URL /path/to/kv-storage-polyfill.mjs se asigna a dos recursos diferentes: std:kv-storage y, luego, a la URL original de nuevo, /path/to/kv-storage-polyfill.mjs.

Por lo tanto, cuando el navegador encuentra una declaración de importación que hace referencia a esa URL (/path/to/kv-storage-polyfill.mjs), primero intenta cargar std:kv-storage y, si no puede hacerlo, vuelve a cargar /path/to/kv-storage-polyfill.mjs.

Una vez más, la magia es que el navegador no necesita admitir la importación de mapas ni módulos integrados para que esta técnica funcione, ya que la URL que se pasa a la declaración de importación es la URL del polyfill. El polyfill no es un resguardo, es el predeterminado. El módulo integrado es una mejora progresiva.

¿Qué pasa con los navegadores que no admiten módulos en absoluto?

Para usar mapas de importación y cargar condicionalmente módulos integrados, debes usar sentencias import, lo que también significa que debes usar secuencias de comandos de módulos, es decir, <script type="module">.

Actualmente, más del 80% de los navegadores admiten módulos. En el caso de los navegadores que no los admiten, puedes usar la técnica de módulo/nomódulo para entregar un paquete heredado. Ten en cuenta que, cuando generes tu compilación de nomodule, deberás incluir todos los polyfills, ya que tienes la certeza de que los navegadores que no admiten módulos definitivamente no admitirán módulos integrados.

Demostración del almacenamiento de KV

Para ilustrar que es posible usar módulos integrados y, al mismo tiempo, admitir navegadores más antiguos, armé una demostración que incorpora todas las técnicas descritas anteriormente y se ejecuta en todos los navegadores en la actualidad:

  • Los navegadores que admiten módulos, que importan mapas y el módulo integrado no cargan ningún código innecesario.
  • Los navegadores que admiten módulos e importación de mapas, pero que no admiten el módulo integrado, cargan el polyfill de KV Storage (a través del cargador de módulos del navegador).
  • Los navegadores que admiten módulos, pero que no admiten la importación de mapas también cargan el polyfill de KV Storage (a través del cargador de módulos del navegador).
  • Los navegadores que no admiten módulos en absoluto obtienen el polyfill de KV Storage en su paquete heredado (cargado a través de <script nomodule>).

La demostración se aloja en Glitch, por lo que puedes ver su fuente. También puedes obtener una explicación detallada de la implementación en el archivo README. No dudes en consultarlo si tienes curiosidad por saber cómo se construye.

Para ver realmente el módulo integrado nativo en acción, debes cargar la demostración en Chrome 74 o versiones posteriores con la marca de funciones experimentales de la plataforma web activada (chrome://flags/#enable-experimental-web-platform-features).

Puedes verificar que el módulo integrado se cargue porque no verás la secuencia de comandos de polyfill en el panel de origen de Herramientas para desarrolladores. En su lugar, verás la versión del módulo integrado (dato curioso: puedes inspeccionar el código fuente del módulo o incluso ponerle puntos de interrupción):

La fuente del módulo de almacenamiento de KV en las Herramientas para desarrolladores de Chrome

Envíanos tus comentarios

Esta introducción debería haberte brindado una idea de lo que se puede lograr con los módulos integrados. Espero que estés emocionado. Nos encantaría que los desarrolladores prueben el módulo de almacenamiento de KV (así como todas las funciones nuevas que se mencionan aquí) y nos envíen sus comentarios.

Estos son los vínculos de GitHub en los que puedes enviarnos comentarios sobre cada una de las funciones que se mencionan en este artículo:

Si tu sitio actualmente usa localStorage, debes intentar cambiar a la API de KV Storage para ver si satisface todas tus necesidades. Si te registras en la prueba de origen de KV Storage, puedes implementar estas funciones hoy mismo. Todos los usuarios deberían beneficiarse con un mejor rendimiento de almacenamiento, y los usuarios de Chrome 74 y versiones posteriores no tendrán que pagar ningún costo de descarga adicional.