SvelteKit 服务工作线程

Service Worker 如同应用内部的代理服务器,负责处理网络请求。它可以让应用离线运行。即使你不需要离线支持,或者应用本身不适合实现离线功能,预先缓存构建后的 JavaScript 和 CSS 通常也能加快页面导航。

在 SvelteKit 中,只要存在 src/service-worker/index.ts 文件,它就会被打包并自动注册。

在 Service Worker 内部

要让 Service Worker 发挥作用,通常需要导入以下内容:

一个典型的 Service Worker 如下:

import { self } from '$app/service-worker';
import { version } from '$app/env';
import { immutable, assets } from '$app/manifest';
import { resolve } from '$app/paths';

// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;

// `immutable`/`assets` paths from `$app/manifest` are relative to the
// base path, so resolve them to absolute pathnames that can be matched
// against `url.pathname` in the `fetch` handler
const ASSETS = [
	...immutable.map((asset) => resolve(asset.path)), // the Vite output
	...assets.map((asset) => resolve(asset.path))  // 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);

		// `immutable`/`assets` 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...
		try {
			const response = await fetch(event.request);

			if (response.status === 200 && !response.headers.get('cache-control')?.includes('no-store')) {
				// ...and cache responses in the background for next time....
				void cache.put(event.request, response.clone());
			}

			return response;
		} catch (error) {
			// ...otherwise fall back to previously cached data if it exists...
			const response = await cache.match(event.request);

			if (response) {
				return response;
			}

			// ...or throw the error
			throw error;
		}
	}

	event.respondWith(respond());
});

类型安全

Service Worker 的运行上下文与应用其余部分不同,因此需要不同的类型。请确保项目根目录的 tsconfig.json 排除 Service Worker 代码:

{
	"extends": "$app/tsconfig",
	"include": ["src", "test"],
	"exclude": ["src/service-worker"]
}

然后在 src/service-worker/index.ts 所在目录放置独立的 tsconfig.json,通过继承 $app/tsconfig/service-worker 配置正确的类型:

{
	"extends": "$app/tsconfig/service-worker"
}

手动注册

如果需要使用自己的逻辑注册 Service Worker,可以禁用自动注册。默认注册代码会注入服务端渲染的 HTML,其形式大致如下:

if ('serviceWorker' in navigator) {
	const script_url = './service-worker.js';
	const policy = globalThis?.window?.trustedTypes?.createPolicy(
		'sveltekit-trusted-url',
		{ createScriptURL(url) { return url; } }
	);
	const sanitised = policy?.createScriptURL(script_url) ?? script_url;
	addEventListener('load', function () {
		navigator.serviceWorker.register(sanitised, { type: 'module' });
	});
}

更新 Service Worker

当作用域内发生整页导航,以及 push、sync 等功能事件结束后,浏览器会检查 Service Worker 是否有更新。客户端导航不属于这两种情况,因此在应用内切换页面本身不会让浏览器发现新部署的 Service Worker。

SvelteKit 只在错误恢复流程中调用 registration.update():如果路由模块加载失败,或导航产生错误状态,同时版本轮询检测到应用已重新部署,SvelteKit 会先更新 Service Worker,再回退到整页导航。

如果希望更及时地发现新部署,可以自行触发更新检查。例如,在根布局中,每次客户端导航后检查一次:

import { afterNavigate } from '$app/navigation';

afterNavigate(async () => {
	if ('serviceWorker' in navigator) {
		const registration = await navigator.serviceWorker.getRegistration();
		await registration?.update();
	}
});

即使存在新的 Service Worker,这也不会让它立即接管当前页面。它会先在后台安装,等旧 Service Worker 管理的标签页数量降为零后再接管。

其他方案

SvelteKit 的 Service Worker 实现以易用为目标,通常适合大多数用户。不过,在 SvelteKit 之外,许多 PWA 应用使用 Workbox。如果你习惯使用 Workbox,也可以选择 Vite PWA 插件。

参考资料

有关 Service Worker 的更多通用信息,请参阅 MDN Web 文档。

原文:SvelteKit — Service workers。本文为中文译本;示例未在本地执行。以下保留原项目 MIT 许可证。

Copyright (c) 2020 these people

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容