KV Storage: o primeiro módulo integrado da Web

Na última década, fornecedores de navegadores e especialistas em desempenho da Web têm dito que o localStorage é lento, e os desenvolvedores da Web precisam parar de usá-lo.

Para ser justo, as pessoas que dizem isso não estão erradas. A localStorage é uma API síncrona que bloqueia a linha de execução principal. Sempre que você a acessa, pode impedir que sua página seja interativa.

O problema é que a API localStorage é muito simples, e a única alternativa assíncrona ao localStorage é o IndexedDB, que não é conhecida por sua facilidade de uso ou por ser uma API acolhedora.

Assim, os desenvolvedores podem escolher entre algo difícil de usar e algo ruim para o desempenho. Embora existam bibliotecas que oferecem a simplicidade da API localStorage enquanto efetivamente usam APIs de armazenamento assíncrono em segundo plano, incluir uma dessas bibliotecas no app tem um custo de tamanho de arquivo e pode consumir seu orçamento de desempenho.

Mas e se fosse possível conseguir o desempenho de uma API de armazenamento assíncrona com a simplicidade da API localStorage, sem precisar pagar o custo do tamanho do arquivo?

Bem, em breve pode haver. O Chrome está testando um novo recurso conhecido como módulos integrados, e o primeiro que planejamos enviar é um módulo de armazenamento de chave-valor assíncrono chamado KV Storage.

Mas antes de entrar em detalhes sobre o módulo de armazenamento do KV, vou explicar o que quero dizer com módulos integrados.

O que são módulos integrados?

Os módulos integrados são semelhantes aos módulos JavaScript normais, exceto pelo fato de não precisarem ser transferidos por download porque vêm com o navegador.

Como as APIs da Web tradicionais, os módulos integrados precisam passar por um processo de padronização. Cada um tem a própria especificação que requer uma revisão do design e sinais positivos de suporte de desenvolvedores da Web e outros fornecedores de navegadores antes de serem enviados. No Chrome, os módulos integrados seguem o mesmo processo de inicialização que usamos para implementar e enviar todas as novas APIs.

Ao contrário das APIs da Web tradicionais, os módulos integrados não são expostos no escopo global. Eles estão disponíveis apenas por importações.

Não expor módulos integrados globalmente tem muitas vantagens: elas não adicionam nenhuma sobrecarga para iniciar um novo contexto de ambiente de execução do JavaScript (por exemplo, uma nova guia, worker ou service worker) e não consomem nenhuma memória ou CPU, a menos que sejam realmente importados. Além disso, elas não correm o risco de nomear colisões com outras variáveis definidas no código.

Para importar um módulo integrado, use o prefixo std: seguido pelo identificador do módulo integrado. Por exemplo, em navegadores compatíveis, é possível importar o módulo KV Storage com o código a seguir. Confira abaixo como usar um polyfill de KV Storage em navegadores incompatíveis:

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

O módulo KV Storage

O módulo de armazenamento KV é semelhante em sua simplicidade à API localStorage, mas o formato da API é mais próximo de um Map em JavaScript. Em vez de getItem(), setItem() e removeItem(), há get(), set() e delete(). Ele também tem outros métodos semelhantes a mapas não disponíveis para localStorage, como keys(), values() e entries(). Além disso, como Map, as chaves não precisam ser strings. Eles podem ser qualquer tipo de serialização estruturada.

Ao contrário de Map, todos os métodos de armazenamento do KV retornam promessas ou iteradores assíncronos, já que o ponto principal desse módulo é que ele não é síncrono, diferentemente de localStorage. Para acessar a API completa em detalhes, consulte a especificação.

Como você deve ter notado no exemplo de código acima, o módulo KV Storage tem uma exportação padrão storage e uma chamada exportação StorageArea.

storage é uma instância da classe StorageArea com o nome 'default', e é o que os desenvolvedores vão usar com mais frequência no código do aplicativo. A classe StorageArea é fornecida para casos em que é necessário isolamento extra, por exemplo, uma biblioteca de terceiros que armazena dados e quer evitar conflitos com dados armazenados pela instância storage padrão. Os dados de StorageArea são armazenados em um banco de dados IndexedDB com o nome kv-storage:${name}, em que nome é o nome da instância StorageArea.

Confira um exemplo de como usar o módulo KV Storage no seu 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();

E se um navegador não for compatível com um módulo integrado?

Se você está familiarizado com o uso de módulos JavaScript nativos em navegadores, provavelmente sabe que (pelo menos até agora) a importação de qualquer coisa diferente de um URL vai gerar um erro. E std:kv-storage não é um URL válido.

Isso levanta a questão: precisamos esperar até que todos os navegadores sejam compatíveis com módulos integrados antes de usá-los no nosso código? Felizmente, a resposta é não.

Você poderá usar os módulos integrados assim que um navegador oferecer suporte a eles, graças ao outro recurso que estamos testando, chamado de importar mapas.

Importar mapas

Os mapas de importação são essencialmente um mecanismo pelo qual os desenvolvedores podem atribuir alias aos identificadores de importação um ou mais identificadores alternativos.

Isso é eficiente porque oferece uma maneira de mudar (no tempo de execução) como um navegador resolve um identificador de importação específico em todo o aplicativo.

No caso de módulos integrados, isso permite fazer referência a um polyfill do módulo no código do aplicativo, mas um navegador que oferece suporte ao módulo integrado pode carregar essa versão.

Confira como declarar um mapa de importação para que esse recurso funcione com o módulo de armazenamento do 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>

O ponto principal no código acima é que o URL /path/to/kv-storage-polyfill.mjs está sendo mapeado para dois recursos diferentes: std:kv-storage e, em seguida, para o URL original novamente, /path/to/kv-storage-polyfill.mjs.

Assim, quando o navegador encontra uma instrução de importação referenciando esse URL (/path/to/kv-storage-polyfill.mjs), ele primeiro tenta carregar std:kv-storage e, se não conseguir, volta a carregar /path/to/kv-storage-polyfill.mjs.

Novamente, a mágica aqui é que o navegador não precisa oferecer suporte a mapas de importação ou módulos integrados para que essa técnica funcione, já que o URL transmitido para a instrução de importação é o URL do polyfill. Na verdade, o polyfill não é um substituto, é o padrão. O módulo integrado é um aprimoramento progressivo.

E quanto aos navegadores que não são compatíveis com módulos?

Para usar mapas de importação para carregar condicionalmente módulos integrados, é necessário usar instruções import, o que também significa que você precisa usar scripts de módulo, ou seja, <script type="module">.

Atualmente, mais de 80% dos navegadores são compatíveis com módulos. Para navegadores que não são, use a técnica de módulo/nomódulo para disponibilizar um pacote legado. Observe que, ao gerar o build nomodule, você vai precisar incluir todos os polyfills, porque você sabe com certeza que os navegadores que não oferecem suporte a módulos não vão oferecer suporte a módulos integrados.

Demonstração do KV Storage

Para ilustrar que é possível usar módulos integrados e, ao mesmo tempo, oferecer suporte a navegadores mais antigos, montei uma demonstração que incorpora todas as técnicas descritas acima e é executada em todos os navegadores atualmente:

  • Navegadores que oferecem suporte a módulos, mapas de importação e o módulo integrado não carregam códigos desnecessários.
  • Os navegadores que aceitam módulos e mapas de importação, mas não aceitam o módulo integrado, carregam o polyfill de armazenamento KV (em inglês) por meio do carregador de módulos do navegador.
  • Os navegadores que aceitam módulos, mas não mapas de importação, também carregam o polyfill de armazenamento KV (por meio do carregador de módulos do navegador).
  • Os navegadores que não são compatíveis com módulos recebem o polyfill de armazenamento KV no pacote legado (carregado via <script nomodule>).

A demonstração é hospedada no Glitch para que você possa acessar a fonte. Também temos uma explicação detalhada sobre a implementação no README (em inglês). Sinta-se à vontade para conferir se tiver curiosidade de ver como ele é construído.

Para conferir o módulo integrado nativo em ação, carregue a demonstração no Chrome 74 ou mais recente com a flag de recursos experimentais da plataforma da Web ativada (chrome://flags/#enable-experimental-web-platform-features).

Você pode verificar se o módulo integrado está sendo carregado porque não verá o script de polyfill no painel de origem do DevTools. Em vez disso, verá a versão do módulo integrado (fato curioso: você pode inspecionar o código-fonte do módulo ou até mesmo colocar pontos de interrupção nele):

A fonte do módulo KV Storage no Chrome DevTools

Envie seu feedback

Nesta introdução, você aprendeu o que é possível fazer com os módulos integrados. Esperamos que você esteja animado! Adoraríamos que os desenvolvedores testassem o módulo de armazenamento KV, assim como todos os novos recursos discutidos aqui, e nos dessem feedback.

Confira os links do GitHub em que você pode enviar feedback sobre cada um dos recursos mencionados neste artigo:

Se o site usa localStorage atualmente, tente alternar para a API KV Storage para ver se ela atende a todas as suas necessidades. Se você se inscrever para o teste de origem do armazenamento KV, poderá implantar esses recursos hoje mesmo. Todos os usuários vão se beneficiar de um melhor desempenho de armazenamento, e os usuários do Chrome 74 ou versões mais recentes não precisarão pagar nenhum custo de download extra.