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,并在旧版浏览器中运行。或者,如果您尝试导入的模块已经以 IIFEUMD 格式捆绑在一起,您可以使用 importScripts() 导入它们。

如果您有两个版本的 Service Worker(一个使用 ES 模块,另一个不使用),则您需要检测当前浏览器支持的内容,并注册相应的 Service Worker 脚本。检测支持的最佳做法目前尚未确定,但您可以按照此 GitHub 问题中的讨论来获取建议。

_摄影:Vlado Paunovic,来自 Unsplash 网站_