Service Workers
Service Workers 充当代理服务器,处理应用程序内部的网络请求。这使得你的应用程序能够离线工作成为可能,但即使你不需要离线支持(或者由于你正在构建的应用程序类型而无法实际实现),通常也值得使用 Service Workers 通过预缓存构建的 JS 和 CSS 来加快导航速度。
在 SvelteKit 中,如果你有一个 src/service-worker.js
文件(或 src/service-worker/index.js
),它将被捆绑并自动注册。如果你需要,可以更改 Service Worker 的位置。
如果你需要使用自己的逻辑注册 Service Worker 或使用其他解决方案,可以 禁用自动注册。默认注册看起来像这样
if ('serviceWorker' in var navigator: Navigator
navigator) {
function addEventListener<"load">(type: "load", listener: (this: Window, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)
addEventListener('load', function () {
var navigator: Navigator
navigator.Navigator.serviceWorker: ServiceWorkerContainer
Available only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>
register('./path/to/service-worker.js');
});
}
在 Service Worker 内部
在 Service Worker 内部,你可以访问 $service-worker
模块,它为你提供了所有静态资产、构建文件和预渲染页面的路径。你还会获得一个应用程序版本字符串,可用于创建唯一的缓存名称,以及部署的 base
路径。如果你的 Vite 配置指定了 define
(用于全局变量替换),这将应用于 Service Workers 以及你的服务器/客户端构建。
以下示例会急切地缓存构建的应用程序和 static
中的任何文件,并根据需要缓存所有其他请求。这将使每个页面在访问后都能离线工作。
/// <reference types="@sveltejs/kit" />
import { const build: string[]
An array of URL strings representing the files generated by Vite, suitable for caching with cache.addAll(build)
.
During development, this is an empty array.
build, const files: string[]
An array of URL strings representing the files in your static directory, or whatever directory is specified by config.kit.files.assets
. You can customize which files are included from static
directory using config.kit.serviceWorker.files
files, const version: string
See config.kit.version
. It’s useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches.
version } from '$service-worker';
// Create a unique cache name for this deployment
const const CACHE: string
CACHE = `cache-${const version: string
See config.kit.version
. It’s useful for generating unique cache names inside your service worker, so that a later deployment of your app can invalidate old caches.
version}`;
const const ASSETS: string[]
ASSETS = [
...const build: string[]
An array of URL strings representing the files generated by Vite, suitable for caching with cache.addAll(build)
.
During development, this is an empty array.
build, // the app itself
...const files: string[]
An array of URL strings representing the files in your static directory, or whatever directory is specified by config.kit.files.assets
. You can customize which files are included from static
directory using config.kit.serviceWorker.files
files // everything in `static`
];
var self: Window & typeof globalThis
self.function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)
Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options’s capture.
When set to true, options’s capture prevents callback from being invoked when the event’s eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event’s eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event’s eventPhase attribute value is AT_TARGET.
When set to true, options’s passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
When set to true, options’s once indicates that the callback will only be invoked once after which the event listener will be removed.
If an AbortSignal is passed for options’s signal, then the event listener will be removed when signal is aborted.
The event listener is appended to target’s event listener list and is not appended if it has the same type, callback, and capture.
addEventListener('install', (event: Event
event) => {
// Create a new cache and add all files to it
async function function (local function) addFilesToCache(): Promise<void>
addFilesToCache() {
const const cache: Cache
cache = await var caches: CacheStorage
Available only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>
open(const CACHE: string
CACHE);
await const cache: Cache
cache.Cache.addAll(requests: Iterable<RequestInfo>): Promise<void> (+1 overload)
addAll(const ASSETS: string[]
ASSETS);
}
event: Event
event.waitUntil(function (local function) addFilesToCache(): Promise<void>
addFilesToCache());
});
var self: Window & typeof globalThis
self.function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)
Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options’s capture.
When set to true, options’s capture prevents callback from being invoked when the event’s eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event’s eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event’s eventPhase attribute value is AT_TARGET.
When set to true, options’s passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
When set to true, options’s once indicates that the callback will only be invoked once after which the event listener will be removed.
If an AbortSignal is passed for options’s signal, then the event listener will be removed when signal is aborted.
The event listener is appended to target’s event listener list and is not appended if it has the same type, callback, and capture.
addEventListener('activate', (event: Event
event) => {
// Remove previous cached data from disk
async function function (local function) deleteOldCaches(): Promise<void>
deleteOldCaches() {
for (const const key: string
key of await var caches: CacheStorage
Available only in secure contexts.
caches.CacheStorage.keys(): Promise<string[]>
keys()) {
if (const key: string
key !== const CACHE: string
CACHE) await var caches: CacheStorage
Available only in secure contexts.
caches.CacheStorage.delete(cacheName: string): Promise<boolean>
delete(const key: string
key);
}
}
event: Event
event.waitUntil(function (local function) deleteOldCaches(): Promise<void>
deleteOldCaches());
});
var self: Window & typeof globalThis
self.function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void (+1 overload)
Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options’s capture.
When set to true, options’s capture prevents callback from being invoked when the event’s eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event’s eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event’s eventPhase attribute value is AT_TARGET.
When set to true, options’s passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
When set to true, options’s once indicates that the callback will only be invoked once after which the event listener will be removed.
If an AbortSignal is passed for options’s signal, then the event listener will be removed when signal is aborted.
The event listener is appended to target’s event listener list and is not appended if it has the same type, callback, and capture.
addEventListener('fetch', (event: Event
event) => {
// ignore POST requests etc
if (event: Event
event.request.method !== 'GET') return;
async function function (local function) respond(): Promise<Response>
respond() {
const const url: URL
url = new var URL: new (url: string | URL, base?: string | URL) => URL
The URL interface represents an object providing static methods used for creating object URLs.
URL
class is a global reference for require('url').URL
https://node.org.cn/api/url.html#the-whatwg-url-api
URL(event: Event
event.request.url);
const const cache: Cache
cache = await var caches: CacheStorage
Available only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>
open(const CACHE: string
CACHE);
// `build`/`files` can always be served from the cache
if (const ASSETS: string[]
ASSETS.Array<string>.includes(searchElement: string, fromIndex?: number): boolean
Determines whether an array includes a certain element, returning true or false as appropriate.
includes(const url: URL
url.URL.pathname: string
pathname)) {
const const response: Response | undefined
response = await const cache: Cache
cache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>
match(const url: URL
url.URL.pathname: string
pathname);
if (const response: Response | undefined
response) {
return const response: Response
response;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const const response: Response
response = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch(event: Event
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 (!(const response: Response
response instanceof var Response: {
new (body?: BodyInit | null, init?: ResponseInit): Response;
prototype: Response;
error(): Response;
json(data: any, init?: ResponseInit): Response;
redirect(url: string | URL, status?: number): Response;
}
This Fetch API interface represents the response to a request.
Response)) {
throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error('invalid response from fetch');
}
if (const response: Response
response.Response.status: number
status === 200) {
const cache: Cache
cache.Cache.put(request: RequestInfo | URL, response: Response): Promise<void>
put(event: Event
event.request, const response: Response
response.Response.clone(): Response
clone());
}
return const response: Response
response;
} catch (function (local var) err: unknown
err) {
const const response: Response | undefined
response = await const cache: Cache
cache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>
match(event: Event
event.request);
if (const response: Response | undefined
response) {
return const response: Response
response;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw function (local var) err: unknown
err;
}
}
event: Event
event.respondWith(function (local function) respond(): Promise<Response>
respond());
});
缓存时要小心!在某些情况下,陈旧的数据可能比离线时不可用的数据更糟糕。由于浏览器如果缓存变得太满,就会清空缓存,因此在缓存大型资产(如视频文件)时也应小心。
在开发过程中
Service Worker 会针对生产环境进行捆绑,但在开发过程中不会。因此,只有支持 Service Worker 中的模块 的浏览器才能在开发时使用它们。如果你正在手动注册你的 Service Worker,则需要在开发环境中传递 { type: 'module' }
选项
import { const dev: boolean
Whether the dev server is running. This is not guaranteed to correspond to NODE_ENV
or MODE
.
dev } from '$app/environment';
var navigator: Navigator
navigator.Navigator.serviceWorker: ServiceWorkerContainer
Available only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>
register('/service-worker.js', {
RegistrationOptions.type?: WorkerType | undefined
type: const dev: boolean
Whether the dev server is running. This is not guaranteed to correspond to NODE_ENV
or MODE
.
dev ? 'module' : 'classic'
});
build
和prerendered
在开发过程中为空数组
类型安全
为 Service Workers 设置正确的类型需要一些手动设置。在你的 service-worker.js
中,将以下内容添加到文件顶部
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
const sw = self as unknown as ServiceWorkerGlobalScope;
这将禁用对像 HTMLElement
这样的 DOM 类型(在 Service Worker 中不可用)的访问,并实例化正确的全局变量。将 self
重新分配给 sw
允许你在过程中对其进行类型转换(有几种方法可以做到这一点,但这最简单,不需要任何其他文件)。在文件的其余部分使用 sw
而不是 self
。对 SvelteKit 类型的引用确保 $service-worker
导入具有正确的类型定义。如果你导入 $env/static/public
,则必须 // @ts-ignore
导入或将 /// <reference types="../.svelte-kit/ambient.d.ts" />
添加到引用类型中。
其他解决方案
SvelteKit 的 Service Worker 实现故意保持低级。如果你需要更完善但也更固执己见的解决方案,我们建议查看 Vite PWA 插件 等解决方案,该插件使用 Workbox。有关 Service Workers 的更多一般信息,我们建议参阅 MDN Web 文档。