Service Worker 中的 ES 模块

importScripts() 的新式替代方案。

背景

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

本文详细介绍了各大常用浏览器中服务工件对 ES 模块的支持情况,以及一些需要避免的注意事项,以及发布向后兼容的服务工件代码的最佳实践。

使用场景

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

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

如果通过 ES 模块导入的脚本内容发生更改,则可以触发服务工作器更新流程,与 importScripts()行为一致。

当前限制

仅限静态导入

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

此限制类似于对 importScripts() 使用施加的类似限制。对 importScripts() 的动态调用无法在服务工件内运行,并且所有 importScripts() 调用(本质上是同步调用)都必须在服务工件完成其 install 阶段之前完成。此限制可确保浏览器知道服务工件实现期间所需的所有 JavaScript 代码,并能够隐式缓存这些代码。

最终,此限制可能会解除,并且可能会允许导入动态 ES 模块。目前,请确保您仅在服务工件中使用静态语法。

其他工人呢?

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

版本 83开始,Chrome 和 Edge 支持共享工作器中的 ES 模块,但目前没有其他浏览器提供此支持。

不支持导入映射

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

虽然 Chrome 和 Edge 89 版及更高版本支持导入映射,但目前无法与服务工作器搭配使用

浏览器支持

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

向后兼容性

如果所有浏览器都支持服务工作器中的 ES 模块,上述示例将正常运行,但在撰写本文时,情况并非如此。

为了适应不提供内置支持的浏览器,您可以通过与 ES 模块兼容的捆绑工具运行服务工件脚本,以创建包含所有模块代码的内嵌服务工件,并在旧版浏览器中运行。或者,如果您尝试导入的模块已以 IIFEUMD 格式捆绑提供,您可以使用 importScripts() 导入它们。

当您有两个版本的服务工件可用(一个使用 ES 模块,另一个不使用)后,您需要检测当前浏览器支持的版本,并注册相应的服务工件脚本。检测支持的最佳实践目前尚未确定,但您可以关注此 GitHub 问题中的讨论,获取相关建议。

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