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,它将在旧版浏览器中工作。或者,如果您尝试导入的模块已经以 IIFE 或 UMD 格式捆绑提供,则可以使用 importScripts()
导入它们。
一旦您拥有两个版本的 Service Worker(一个使用 ES 模块,另一个不使用),您将需要检测当前浏览器支持什么,并注册相应的 Service Worker 脚本。检测支持的最佳实践目前正在变化,但您可以关注此 GitHub issue 中的讨论以获取建议。
_照片由 Vlado Paunovic 在 Unsplash 上拍摄_