服务工作者
服务工作者充当代理服务器,处理应用内的网络请求。这使得你的应用可以离线工作,但即使你不需要离线支持(或者由于你正在构建的应用类型而无法实际实现它),通常也值得使用服务工作者通过预先缓存你构建的 JS 和 CSS 来加快导航速度。
¥Service workers act as proxy servers that handle network requests inside your app. This makes it possible to make your app work offline, but even if you don’t need offline support (or can’t realistically implement it because of the type of app you’re building), it’s often worth using service workers to speed up navigation by precaching your built JS and CSS.
在 SvelteKit 中,如果你有 src/service-worker.js 文件(或 src/service-worker/index.js),它将被打包并自动注册。如果需要,你可以更改 服务工作者的位置。
¥In SvelteKit, if you have a src/service-worker.js file (or src/service-worker/index.js) it will be bundled and automatically registered. You can change the location of your service worker if you need to.
如果你需要使用自己的逻辑注册服务工作线程或使用其他解决方案,则可以 禁用自动注册。默认注册如下所示:
¥You can disable automatic registration if you need to register the service worker with your own logic or use another solution. The default registration looks something like this:
if ('serviceWorker' in var navigator: Navigatornavigator) {
function addEventListener<"load">(type: "load", listener: (this: Window, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)addEventListener('load', function () {
var navigator: Navigatornavigator.Navigator.serviceWorker: ServiceWorkerContainerAvailable only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>register('./path/to/service-worker.js');
});
}服务工作者内部(Inside the service worker)
¥Inside the service worker
在服务工作线程中,你可以访问 $service-worker module,它为你提供所有静态资源、构建文件和预渲染页面的路径。你还将获得一个应用版本字符串,你可以使用它来创建唯一的缓存名称和部署的 base 路径。如果你的 Vite 配置指定 define(用于全局变量替换),这将应用于服务工作者以及你的服务器/客户端构建。
¥Inside the service worker you have access to the $service-worker module, which provides you with the paths to all static assets, build files and prerendered pages. You’re also provided with an app version string, which you can use for creating a unique cache name, and the deployment’s base path. If your Vite config specifies define (used for global variable replacements), this will be applied to service workers as well as your server/client builds.
以下示例预缓存构建的应用和 static 中的任何文件,并在发生所有其他请求时缓存它们。这将使每个页面在访问后都可以离线工作。
¥The following example caches the built app and any files in static eagerly, and caches all other requests as they happen. This would make each page work offline once visited.
// Disables access to DOM typings like `HTMLElement` which are not available
// inside a service worker and instantiates the correct globals
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
// Ensures that the `$service-worker` import has proper type definitions
/// <reference types="@sveltejs/kit" />
// Only necessary if you have an import from `$env/static/public`
/// <reference types="../.svelte-kit/ambient.d.ts" />
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: stringSee 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';
// This gives `self` the correct types
const const self: Window & typeof globalThisself = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (module globalThisglobalThis.var self: Window & typeof globalThisself));
// Create a unique cache name for this deployment
const const CACHE: stringCACHE = `cache-${const version: stringSee 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`
];
const self: Window & typeof globalThisself.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: Eventevent) => {
// Create a new cache and add all files to it
async function function (local function) addFilesToCache(): Promise<void>addFilesToCache() {
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
await const cache: Cachecache.Cache.addAll(requests: Iterable<RequestInfo>): Promise<void> (+1 overload)addAll(const ASSETS: string[]ASSETS);
}
event: Eventevent.waitUntil(function (local function) addFilesToCache(): Promise<void>addFilesToCache());
});
const self: Window & typeof globalThisself.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: Eventevent) => {
// Remove previous cached data from disk
async function function (local function) deleteOldCaches(): Promise<void>deleteOldCaches() {
for (const const key: stringkey of await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.keys(): Promise<string[]>keys()) {
if (const key: stringkey !== const CACHE: stringCACHE) await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.delete(cacheName: string): Promise<boolean>delete(const key: stringkey);
}
}
event: Eventevent.waitUntil(function (local function) deleteOldCaches(): Promise<void>deleteOldCaches());
});
const self: Window & typeof globalThisself.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: Eventevent) => {
// ignore POST requests etc
if (event: Eventevent.request.method !== 'GET') return;
async function function (local function) respond(): Promise<Response>respond() {
const const url: URLurl = new var URL: new (url: string | URL, base?: string | URL) => URLThe URL interface represents an object providing static methods used for creating object URLs.
URL class is a global reference for import { URL } from 'node:url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(event: Eventevent.request.url);
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
// `build`/`files` can always be served from the cache
if (const ASSETS: string[]ASSETS.Array<string>.includes(searchElement: string, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.
includes(const url: URLurl.URL.pathname: stringpathname)) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(const url: URLurl.URL.pathname: stringpathname);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: Eventevent.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: Responseresponse 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: Responseresponse.Response.status: numberstatus === 200) {
const cache: Cachecache.Cache.put(request: RequestInfo | URL, response: Response): Promise<void>put(event: Eventevent.request, const response: Responseresponse.Response.clone(): Responseclone());
}
return const response: Responseresponse;
} catch (function (local var) err: unknownerr) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(event: Eventevent.request);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
// 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: unknownerr;
}
}
event: Eventevent.respondWith(function (local function) respond(): Promise<Response>respond());
});// Disables access to DOM typings like `HTMLElement` which are not available
// inside a service worker and instantiates the correct globals
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
// Ensures that the `$service-worker` import has proper type definitions
/// <reference types="@sveltejs/kit" />
// Only necessary if you have an import from `$env/static/public`
/// <reference types="../.svelte-kit/ambient.d.ts" />
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: stringSee 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';
// This gives `self` the correct types
const const self: ServiceWorkerGlobalScopeself = module globalThisglobalThis.var self: Window & typeof globalThisself as unknown as type ServiceWorkerGlobalScope = /*unresolved*/ anyServiceWorkerGlobalScope;
// Create a unique cache name for this deployment
const const CACHE: stringCACHE = `cache-${const version: stringSee 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`
];
const self: ServiceWorkerGlobalScopeself.addEventListener('install', (event: anyevent) => {
// Create a new cache and add all files to it
async function function (local function) addFilesToCache(): Promise<void>addFilesToCache() {
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
await const cache: Cachecache.Cache.addAll(requests: Iterable<RequestInfo>): Promise<void> (+1 overload)addAll(const ASSETS: string[]ASSETS);
}
event: anyevent.waitUntil(function (local function) addFilesToCache(): Promise<void>addFilesToCache());
});
const self: ServiceWorkerGlobalScopeself.addEventListener('activate', (event: anyevent) => {
// Remove previous cached data from disk
async function function (local function) deleteOldCaches(): Promise<void>deleteOldCaches() {
for (const const key: stringkey of await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.keys(): Promise<string[]>keys()) {
if (const key: stringkey !== const CACHE: stringCACHE) await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.delete(cacheName: string): Promise<boolean>delete(const key: stringkey);
}
}
event: anyevent.waitUntil(function (local function) deleteOldCaches(): Promise<void>deleteOldCaches());
});
const self: ServiceWorkerGlobalScopeself.addEventListener('fetch', (event: anyevent) => {
// ignore POST requests etc
if (event: anyevent.request.method !== 'GET') return;
async function function (local function) respond(): Promise<Response>respond() {
const const url: URLurl = new var URL: new (url: string | URL, base?: string | URL) => URLThe URL interface represents an object providing static methods used for creating object URLs.
URL class is a global reference for import { URL } from 'node:url'
https://nodejs.org/api/url.html#the-whatwg-url-api
URL(event: anyevent.request.url);
const const cache: Cachecache = await var caches: CacheStorageAvailable only in secure contexts.
caches.CacheStorage.open(cacheName: string): Promise<Cache>open(const CACHE: stringCACHE);
// `build`/`files` can always be served from the cache
if (const ASSETS: string[]ASSETS.Array<string>.includes(searchElement: string, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.
includes(const url: URLurl.URL.pathname: stringpathname)) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(const url: URLurl.URL.pathname: stringpathname);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(event: anyevent.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: Responseresponse 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: Responseresponse.Response.status: numberstatus === 200) {
const cache: Cachecache.Cache.put(request: RequestInfo | URL, response: Response): Promise<void>put(event: anyevent.request, const response: Responseresponse.Response.clone(): Responseclone());
}
return const response: Responseresponse;
} catch (function (local var) err: unknownerr) {
const const response: Response | undefinedresponse = await const cache: Cachecache.Cache.match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>match(event: anyevent.request);
if (const response: Response | undefinedresponse) {
return const response: Responseresponse;
}
// 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: unknownerr;
}
}
event: anyevent.respondWith(function (local function) respond(): Promise<Response>respond());
});缓存时请小心!在某些情况下,过时的数据可能会更糟比离线时不可用的数据更安全。由于浏览器缓存过满会清空,因此在缓存视频文件等大型资源时也应谨慎。
开发期间(During development)
¥During development
服务工作者打包用于生产,但不用于开发。因此,只有支持 服务工作者中的模块 的浏览器才能在开发时使用它们。如果你手动注册服务工作者,则需要在开发中传递 { type: 'module' } 选项:
¥The service worker is bundled for production, but not during development. For that reason, only browsers that support modules in service workers will be able to use them at dev time. If you are manually registering your service worker, you will need to pass the { type: 'module' } option in development:
import { const dev: booleanWhether the dev server is running. This is not guaranteed to correspond to NODE_ENV or MODE.
dev } from '$app/environment';
var navigator: Navigatornavigator.Navigator.serviceWorker: ServiceWorkerContainerAvailable only in secure contexts.
serviceWorker.ServiceWorkerContainer.register(scriptURL: string | URL, options?: RegistrationOptions): Promise<ServiceWorkerRegistration>register('/service-worker.js', {
RegistrationOptions.type?: WorkerType | undefinedtype: const dev: booleanWhether the dev server is running. This is not guaranteed to correspond to NODE_ENV or MODE.
dev ? 'module' : 'classic'
});开发过程中,
build和prerendered为空数组
其他解决方案(Other solutions)
¥Other solutions
SvelteKit 的服务工作线程实现旨在易于使用,对于大多数用户来说可能是一个很好的解决方案。但是,在 SvelteKit 之外,许多 PWA 应用都利用了 Workbox 库。如果你习惯使用 Workbox,你可能更喜欢 Vite PWA 插件。
¥SvelteKit’s service worker implementation is designed to be easy to work with and is probably a good solution for most users. However, outside of SvelteKit, many PWA applications leverage the Workbox library. If you’re used to using Workbox you may prefer Vite PWA plugin.
参考(References)
¥References
有关服务工作者的更多一般信息,我们推荐 MDN 网络文档。
¥For more general information on service workers, we recommend the MDN web docs.