服务工作者充当代理服务器,处理您应用内的网络请求。这使得您的应用能够在离线状态下运行,即使您不需要离线支持(或者由于您正在构建的应用类型而无法实际实现),通常也值得使用服务工作者通过预缓存您的 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)。