Módulos ES em service workers

Uma alternativa moderna a 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 oferecem suporte a módulos ES, nem todos oferecem suporte em todos os lugares em que o código pode ser executado. Especificamente, o suporte à importação de módulos ES dentro do service worker de um navegador está apenas começando a ser disponibilizado cada vez mais.

Neste artigo, detalhamos o estado atual do suporte ao módulo ES em service workers em navegadores comuns, junto com alguns problemas a serem evitados e práticas recomendadas para enviar código de service worker compatível com versões anteriores.

Casos de uso

O caso de uso ideal para módulos ES dentro de service workers é carregar uma biblioteca moderna ou um código de configuração compartilhado com outros ambientes de execução compatíveis com módulos ES.

A tentativa de compartilhar o código dessa maneira antes dos módulos ES implicava o uso de formatos de módulos "universais" mais antigos, como UMD, que incluem código boilerplate desnecessário, e a criação de códigos que fizessem 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 mudar, correspondendo ao comportamento de 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, somente a sintaxe estática é aceita no momento.

Essa limitação é análoga a uma restrição semelhante aplicada ao uso de importScripts(). Chamadas dinâmicas para importScripts() não funcionam dentro de um service worker, e todas as chamadas de importScripts(), que são inerentemente síncronas, precisam ser concluídas antes que o service worker conclua a fase install. Essa restrição garante que o navegador saiba sobre e possa armazenar em cache implicitamente todo o código JavaScript necessário para a implementação de um service worker durante a instalação.

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

E os outros workers?

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

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

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

A importação de mapas permite que os ambientes de execução reescrevam os especificadores de módulo para, por exemplo, incluir o URL de uma CDN preferencial que serve de base para o carregamento dos módulos ES.

Embora a versão 89 e mais recentes do Chrome e do Edge sejam compatíveis com mapas de importação, eles não podem ser usados com service workers.

Suporte ao navegador

Os módulos ES em service workers são compatíveis com o Chrome e o Edge a partir da versão 91.

O Safari adicionou suporte à versão 122 da Technology Preview 122, e os desenvolvedores esperarão essa funcionalidade 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 fossem compatíveis com módulos ES em service workers, mas, no momento, esse não é o caso.

Para acomodar navegadores que não têm suporte integrado, é possível executar o script do service worker por meio de um bundler compatível com módulo ES para criar um service worker que inclua todo o código do módulo inline e funcione 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, será possível importá-los usando importScripts().

Quando você tiver duas versões do service worker disponíveis, uma que usa módulos ES e outra que não, será necessário detectar o que é compatível com o navegador atual e registrar o script correspondente. As práticas recomendadas para detectar o suporte ainda estão em fluxo, mas você pode acompanhar a discussão neste problema do GitHub para recomendações.

_Foto de Vlado Paunovic no Unsplash_