服务工作者充当代理服务器,处理您应用内的网络请求。这使得您的应用能够在离线状态下运行,即使您不需要离线支持(或者由于您正在构建的应用类型而无法实际实现),通常也值得使用服务工作者通过预缓存您的 JS 和 CSS 来加速导航。
在 SvelteKit 中,如果您有一个`src/service-worker.js`文件(或`src/service-worker/index.js`),它将被打包并自动注册。如果您需要,可以更改[您的服务工作者位置](configuration#files)。
您可以根据需要禁用自动注册[,如果您需要使用自己的逻辑或另一个解决方案来注册服务工作者。默认注册看起来可能像这样:](configuration#serviceWorker)
```js
if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('./path/to/service-worker.js');
});
}
```
##
服务工作者内部
在服务工作者内部,您可以访问 [`$service-worker` 模块]($service-worker),该模块为您提供了所有静态资源、构建文件和预渲染页面的路径。您还提供了一个应用程序版本字符串,您可以使用它来创建唯一的缓存名称,以及部署的 `base` 路径。如果您的 Vite 配置指定了 `define`(用于全局变量替换),这将应用于服务工作者以及您的服务器/客户端构建。
以下示例会预先缓存构建的应用程序以及`static`中的任何文件,并且会按发生顺序缓存所有其他请求。这样,一旦访问过每个页面,就可以离线工作。
```js
// @errors: 2339
///
import { build, files, version } from '$service-worker';
// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;
const ASSETS = [
...build, // the app itself
...files // everything in `static`
];
self.addEventListener('install', (event) => {
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}
event.waitUntil(addFilesToCache());
});
self.addEventListener('activate', (event) => {
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
});
self.addEventListener('fetch', (event) => {
// ignore POST requests etc
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);
if (response) {
return response;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
// if we're offline, fetch can return a value that is not a Response
// instead of throwing - and we can't pass this non-Response to respondWith
if (!(response instanceof Response)) {
throw new Error('invalid response from fetch');
}
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch (err) {
const response = await cache.match(event.request);
if (response) {
return response;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw err;
}
}
event.respondWith(respond());
});
```
> \[注意\] 缓存时请小心!在某些情况下,过时的数据可能比离线时不可用的数据更糟。由于浏览器会在缓存太满时清空缓存,因此您还应注意缓存大型资产,如视频文件。
##
开发期间
服务工作者在生产环境中被打包,但在开发期间不会。因此,只有支持在服务工作者中使用[模块](https://web.dev/es-modules-in-sw)的浏览器才能在开发时间使用它们。如果您手动注册服务工作者,您需要在开发时传递`{ type: 'module' }`选项:
```js
import { dev } from '$app/environment';
navigator.serviceWorker.register('/service-worker.js', {
type: dev ? 'module' : 'classic'
});
```
> \[!注意\] `build` 和 `prerendered` 在开发期间为空数组
##
类型安全
设置服务工作者适当类型需要一些手动设置。在您的 `service-worker.js` 文件中,将以下内容添加到文件顶部:
```original-js
///
///
///
///
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
```
```generated-ts
///
///
///
///
const sw = self as unknown as ServiceWorkerGlobalScope;
```
此操作禁用了对 DOM 类型定义的访问,例如`HTMLElement`,这些在服务工作者内部不可用,并实例化了正确的全局变量。将`self`重新赋值为`sw`允许你在过程中进行类型转换(有几种方法可以做到这一点,但这是最简单的一种,不需要额外的文件)。在文件的其他部分使用`sw`代替`self`。对 SvelteKit 类型的引用确保了`$service-worker`导入有正确的类型定义。如果你导入`$env/static/public`,你必须使用`// @ts-ignore`忽略导入或添加 `/// ` 到引用类型。
##
其他解决方案
SvelteKit 的服务工作者实现旨在易于使用,可能是大多数用户的良好解决方案。然而,在 SvelteKit 之外,许多 PWA 应用程序利用了[Workbox](https://web.dev/learn/pwa/workbox)库。如果您习惯使用 Workbox,您可能更喜欢[Vite PWA 插件](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html)。
##
参考文献
关于服务工作者更一般的信息,我们推荐[MDN 网络文档](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers)。