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 内部使用静态语法。

其他 Worker 呢?

“专用” Worker(使用 new Worker('...', {type: 'module'}) 构建的 Worker)中对 ES 模块的支持 更为广泛,自 80 版本 起 Chrome 和 Edge 就已支持,最新版本 的 Safari 也支持。专用 Worker 中同时支持静态和动态 ES 模块导入。

83 版本 起,Chrome 和 Edge 在 共享 Worker 中支持 ES 模块,但目前其他浏览器均不支持。

不支持导入映射

导入映射 允许运行时环境重写模块说明符,例如,在模块说明符前添加首选 CDN 的 URL,以便从中加载 ES 模块。

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

浏览器支持

Service Worker 中的 ES 模块在 Chrome 和 Edge 中从 91 版本 开始受支持。

Safari 在 Technology Preview 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 模块,则以上示例可以正常工作,但截至撰写本文时,情况并非如此。

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

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

_照片由 Vlado PaunovicUnsplash 上拍摄_