Módulos ES em service workers

Uma alternativa moderna para importScripts().

Contexto

Os módulos ES são os favoritos dos desenvolvedores há algum tempo. Além de vários outros benefícios, eles oferecem a promessa de um formato de módulo universal em que o código compartilhado pode ser lançado uma vez e executado em navegadores e em ambientes de execução alternativos, como Node.js. Embora todos os navegadores modernos ofereçam algum suporte ao módulo ES, nem todos oferecem suporte em todos os lugares em que o código pode ser executado. Especificamente, o suporte para importar módulos ES dentro de um service worker do navegador está começando a ficar mais disponível.

Este artigo detalha o estado atual do suporte a módulos ES em service workers em navegadores comuns, além de algumas armadilhas a serem evitadas e práticas recomendadas para enviar códigos de service worker compatíveis com versões anteriores.

Casos de uso

O caso de uso ideal para módulos ES em service workers é carregar uma biblioteca moderna ou um código de configuração compartilhado com outros ambientes de execução que ofereçam suporte a módulos ES.

Tentar compartilhar o código dessa maneira antes dos módulos ES envolvia o uso de formatos de módulo "universais" mais antigos, como o UMD, que incluía boilerplate desnecessário, e escrever código que fazia mudanças em variáveis expostas globalmente.

Os scripts importados por módulos ES podem acionar o fluxo de atualização do service worker se o conteúdo deles mudar, correspondendo ao comportamento do importScripts().

Limitações atuais

Somente importações estáticas

Os módulos ES podem ser importados de duas maneiras: estaticamente, usando a sintaxe import ... from '...', ou dinamicamente, usando o método import(). Dentro de um service worker, apenas a sintaxe estática é aceita no momento.

Essa limitação é análoga a uma restrição semelhante colocada no uso de importScripts(). As chamadas dinâmicas para importScripts() não funcionam em um worker de serviço, e todas as chamadas importScripts(), que são simultâneas por natureza, precisam ser concluídas antes que o worker de serviço conclua a fase install. Essa restrição garante que o navegador saiba e possa armazenar em cache implicitamente todo o código JavaScript necessário para a implementação de um worker de serviço durante a instalação.

Eventualmente, essa restrição poderá ser suspensa, e as importações de módulo ES dinâmico poderão ser permitidas. Por enquanto, use apenas a sintaxe estática dentro de um service worker.

E quanto a outros trabalhadores?

O suporte a módulos ES em workers "dedicados" (aqueles criados com new Worker('...', {type: 'module'})) é mais difundido e tem suporte no Chrome e no Edge desde a versão 80, bem como nas versões recentes do Safari. As importações estáticas e dinâmicas de módulos ES são compatíveis com workers dedicados.

O Chrome e o Edge oferecem suporte a módulos ES em shared workers desde a versão 83, mas nenhum outro navegador oferece suporte no momento.

Não há suporte para mapas de importação

Os mapas de importação permitem que ambientes de execução reescrevam especificadores de módulo para, por exemplo, adicionar o URL de uma CDN preferencial na qual os módulos do ES podem ser carregados.

Embora o Chrome e o Edge versão 89 e mais recentes ofereçam suporte a mapas de importação, eles não podem ser usados com workers de serviço.

Suporte ao navegador

Os módulos ES em service workers têm suporte no Chrome e no Edge a partir da versão 91.

O Safari adicionou suporte na versão 122 da prévia de tecnologia, e os desenvolvedores podem esperar que essa funcionalidade seja lançada na versão estável do Safari no futuro.

Exemplo de código

Este é um exemplo básico de uso de um módulo ES compartilhado no contexto window de um app da Web, além de registrar um service worker que usa o mesmo módulo ES:

// Inside config.js:
export const cacheName = 'my-cache';
// Inside your web app:
<script type="module">
  import {cacheName} from './config.js';
  // Do something with cacheName.

  await navigator.serviceWorker.register('es-module-sw.js', {
    type: 'module',
  });
</script>
// Inside es-module-sw.js:
import {cacheName} from './config.js';

self.addEventListener('install', (event) => {
  event.waitUntil((async () => {
    const cache = await caches.open(cacheName);
    // ...
  })());
});

Compatibilidade com versões anteriores

O exemplo acima funcionaria bem se todos os navegadores oferecessem suporte a módulos ES em service workers, mas, no momento em que este artigo foi escrito, não era o caso.

Para acomodar navegadores que não têm suporte integrado, é possível executar o script do service worker em um bundler compatível com o módulo ES para criar um service worker que inclui todo o código do módulo inline e funciona em navegadores mais antigos. Como alternativa, se os módulos que você está tentando importar já estiverem disponíveis em pacotes nos formatos IIFE ou UMD, eles podem ser importados usando importScripts().

Depois que você tiver duas versões do service worker disponíveis, uma que usa módulos ES e a outra que não usa, será necessário detectar o que o navegador atual oferece suporte e registrar o script do service worker correspondente. As práticas melhores para detectar suporte estão em constante mudança, mas você pode acompanhar a discussão neste problema do GitHub para receber recomendações.

_Foto de Vlado Paunovic no Unsplash_