Модули ES в сервисных воркерах

Современная альтернатива importScripts().

Фон

Модули ES уже давно пользуются популярностью у разработчиков. Помимо множества других преимуществ, они предлагают универсальный формат модулей, в котором общий код может быть выпущен один раз и запущен в браузерах и в альтернативных средах выполнения, таких как Node.js. Хотя все современные браузеры в той или иной степени поддерживают модули ES, они не поддерживают их везде, где может выполняться код. В частности, поддержка импорта модулей ES в сервис-воркер браузера только начинает становиться более доступной.

В этой статье подробно описывается текущее состояние поддержки модулей ES в сервисных работниках в распространенных браузерах, а также некоторые подводные камни, которых следует избегать, и лучшие практики по поставке обратно совместимого кода сервисных работников.

Примеры использования

Идеальный вариант использования модулей ES внутри сервисных работников — загрузка современной библиотеки или кода конфигурации, который используется совместно с другими средами выполнения, поддерживающими модули ES.

До появления модулей ES для этого приходилось использовать старые "универсальные" форматы модулей, такие как UMD, которые содержат ненужный шаблонный код, и писать код, который вносил изменения в глобально доступные переменные.

Скрипты, импортированные через модули ES, могут запускать процесс обновления сервисного работника, если их содержимое меняется, что соответствует поведению importScripts().

Текущие ограничения

Только статический импорт

Модули ES можно импортировать двумя способами: статически с помощью синтаксиса import ... from '...' или динамически с помощью метода import(). Внутри сервис-воркера в настоящее время поддерживается только статический синтаксис.

Это ограничение аналогично ограничению на использование importScripts(). Динамические вызовы importScripts() не работают внутри сервисного работника, и все вызовы importScripts(), которые по своей природе являются синхронными, должны завершиться до того, как сервисный работник завершит свою фазу install. Это ограничение гарантирует, что браузер знает обо всем коде JavaScript, необходимом для реализации сервисного работника во время установки, и может неявно кэшировать его.

В будущем это ограничение может быть снято, и динамический импорт модулей ES может быть разрешен. Пока что используйте в сервисных скриптах только статический синтаксис.

А как насчет других работников?

Поддержка модулей ES в выделенных воркерах, созданных с помощью new Worker('...', {type: 'module'}), более распространена. Она реализована в Chrome и Edge начиная с версии 80, а также в последних версиях Safari. В выделенных воркерах поддерживается как статический, так и динамический импорт модулей ES.

Chrome и Edge поддерживают модули ES в общих рабочих процессах начиная с версии 83, но другие браузеры пока не предлагают такую поддержку.

Нет поддержки карт импорта

Карты импорта позволяют средам выполнения перезаписывать спецификаторы модулей, например добавлять в начало URL предпочитаемой сети доставки контента, из которой можно загружать модули ES.

Хотя Chrome и Edge версии 89 и выше поддерживают карты импорта, в настоящее время их нельзя использовать с сервис-воркерами.

Поддержка браузеров

Модули ES в сервисных рабочих процессах поддерживаются в Chrome и Edge, начиная с версии 91.

Поддержка в Safari была добавлена в Technology Preview 122 Release. В будущем эта функция должна появиться в стабильной версии Safari.

Пример кода

Ниже приведен простой пример использования общего модуля ES в контексте window веб-приложения с одновременной регистрацией сервисного работника, использующего тот же модуль 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);
    // ...
  })());
});

Обратная совместимость

Приведенный выше пример работал бы нормально, если бы все браузеры поддерживали модули ES в сервисных работниках, но на момент написания этой статьи это не так.

Чтобы обеспечить совместимость с браузерами, в которых нет встроенной поддержки, вы можете запустить скрипт сервисного работника через сборщик, совместимый с модулями ES. Это позволит создать сервисного работника, который включает весь код модуля и будет работать в старых браузерах. Если модули, которые вы пытаетесь импортировать, уже доступны в формате IIFE или UMD, вы можете импортировать их с помощью importScripts().

Когда у вас будут две версии сервисного работника (одна с модулями ES, а другая без), вам нужно будет определить, что поддерживает текущий браузер, и зарегистрировать соответствующий скрипт сервисного работника. Рекомендации по определению поддержки в настоящее время меняются, но вы можете следить за обсуждением в этой проблеме GitHub.