Service Worker 中的 ES 模块

是 importScripts() 的现代替代方案。

背景

ES 模块长期以来一直是开发者的最爱。除了许多其他优势之外,它们还承诺提供一种通用模块格式。在这种格式下,共享代码只需发布一次,即可在浏览器和 Node.js 等替代运行时中运行。虽然所有现代浏览器都提供某种 ES 模块支持,但并非在所有可以运行代码的任何位置都支持。具体而言,对在浏览器的 Service Worker 中导入 ES 模块的支持刚刚开始变得越来越广泛。

本文详细介绍了各常见浏览器的 Service Worker 中 ES 模块支持的当前状态,以及需要避免的一些问题,以及发送向后兼容的 Service Worker 代码的最佳做法。

用例

对于 Service Worker 内 ES 模块的理想用例,理想的用例是加载与支持 ES 模块的其他运行时共享的现代库或配置代码。

如果尝试在 ES 模块之前以这种方式共享代码,需要使用旧的“通用”模块格式,例如包含不需要样板的 UMD,以及编写对全局公开的变量进行更改的代码。

通过 ES 模块导入的脚本可以在内容发生更改时触发 Service Worker 更新流程,这与 importScripts()行为相符。

当前限制

仅限静态导入

ES 模块可以通过以下两种方式之一导入:静态(使用 import ... from '...' 语法)或动态(使用 import() 方法)。目前,在 Service Worker 内,仅支持静态语法。

此限制类似于针对 importScripts() 使用施加的类似限制。对 importScripts() 的动态调用在 Service Worker 内部不行,并且所有本质上是同步的 importScripts() 调用都必须在 Service Worker 完成其 install 阶段之前完成。此限制可确保浏览器了解并能够隐式缓存安装期间实现 Service Worker 所需的所有 JavaScript 代码。

最终,此限制可能会被解除,可以允许动态 ES 模块导入。 目前,请确保仅在 Service Worker 内使用静态语法。

其他工作器呢?

“专用”工作器中的 ES 模块(即使用 new Worker('...', {type: 'module'}) 构建的模块)的支持范围更广,并且从 80 版以及 Safari 的最新版本开始,Chrome 和 Edge 均支持该模块。专用 worker 同时支持静态和动态 ES 模块导入。

83 版起,Chrome 和 Edge 就一直支持共享工作器中的 ES 模块,但目前没有其他浏览器提供支持。

不支持导入地图

借助导入映射,运行时环境可以重写模块说明符,例如在可以从中加载 ES 模块的首选 CDN 的网址前面。

虽然 Chrome 和 Edge 版本 89 及更高版本支持导入地图,但它们目前无法与 Service Worker 一起使用

浏览器支持

91 版开始,Chrome 和 Edge 支持 Service Worker 中的 ES 模块。

Safari 在技术预览版 122 版本中增加了对相关功能的支持,开发者将来应该会在 Safari 稳定版中看到此功能。

示例代码

下面是一个基本示例,展示了如何在 Web 应用的 window 上下文中使用共享 ES 模块,同时注册使用同一 ES 模块的 Service Worker:

// 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);
    // ...
  })());
});

向后兼容性

如果所有浏览器都支持 Service Worker 中的 ES 模块,则上面的示例可以正常运行,但截至撰写本文时,还无法做到。

为了适应未内置支持的浏览器,您可以通过 ES 模块兼容的捆绑器运行 Service Worker 脚本,以创建包含所有内嵌模块代码的 Service Worker,该 Service Worker 可在旧版浏览器中运行。或者,如果您尝试导入的模块已经以 IIFEUMD 格式捆绑了,则可以使用 importScripts() 导入它们。

一旦您有两个版本的 Service Worker 可用(一个使用 ES 模块,另一个不使用),您需要检测当前浏览器支持什么,并注册相应的 Service Worker 脚本。检测支持的最佳做法目前不断变化,但您可以关注此 GitHub 问题中的讨论以获取建议。

_照片由 Vlado PaunovicUnsplash 用户提交_