
SvelteKit$app/service-worker模块完全指南Service Worker 中的类型安全self与离线缓存实践【免费下载链接】kitweb development, streamlined项目地址: https://gitcode.com/gh_mirrors/kit/kit$app/service-worker是 SvelteKit 为 Service Worker 运行环境提供的专用模块它只导出一个核心绑定——被类型化为ServiceWorkerGlobalScope的self并在开发模式下对在非 Service Worker 上下文中导入本模块的行为进行运行时拦截。本文以该模块为入口结合仓库中documentation/docs/30-advanced/40-service-workers.md的完整用法与源码实现讲解如何编写类型安全、可预缓存、可自动注册与更新的 SvelteKit Service Worker帮助你实现离线访问与更快的二次导航。模块定位只在 Service Worker 中可用的self在 SvelteKit 中$app/service-worker是一个带严格使用限制的运行时模块。它的完整 API 极其精简——只导出一个self其源码位于 packages/kit/src/runtime/app/service-worker/index.js/// reference no-default-libtrue/ /// reference libesnext / /// reference libwebworker / import { DEV } from esm-env; /** * The execution context of a service worker. This export exists to make it easier to * use service workers with the correct types, provided the importing module is governed * by a tsconfig.json that extends [$app/tsconfig/service-worker](https://link.gitcode.com/i/71cc16a6d6012644f703e4955ec536e0). */ export const self /** type {ServiceWorkerGlobalScope} */ ( /** type {unknown} */ (globalThis.self) ); if (DEV) { if ( typeof ServiceWorkerGlobalScope undefined || !(self instanceof ServiceWorkerGlobalScope) ) { throw new Error(The $app/service-worker module can only be imported into a service worker); } }源码透露了两层关键设计类型化的执行上下文self本质就是globalThis.self只是通过 JSDoc 断言为ServiceWorkerGlobalScope。在 Service Worker 中self是全局作用域对象fetch、install、activate等事件都挂载在它上面。有了正确的类型后监听器回调中的event会被自动推断为FetchEvent/ExtendableEventcaches等 API 也能获得完整补全无需手工声明类型。开发期运行保护借助esm-env提供的DEV标志模块在开发模式下会检查当前全局对象是否真的处于 Service Worker 上下文self instanceof ServiceWorkerGlobalScope否则直接抛出The \$app/service-worker module can only be imported into a service worker错误。这防止开发者误在page.svelte、layout.ts等普通运行环境中导入该模块。由于DEV在生产构建中会被静态替换为false这段校验不会进入产物。与姊妹模块的分工$app/service-worker并非孤立存在一个可用的 Service Worker 通常会同时引入以下模块均与$app/service-worker处于同一模块体系见 documentation/docs/98-reference模块导出用途$app/service-workerselfService Worker 的类型化全局上下文$app/envversion等基于部署版本号创建唯一缓存名见 20-$app-env.md$app/manifestimmutable、assets、prerendered、routes构建产物、静态资源与预渲染页面清单见 20-$app-manifest.md$app/pathsresolve、asset、base等将清单中的相对路径解析为可匹配的绝对路径名见 20-$app-paths.md其中$app/env的version导出实现见 packages/kit/src/runtime/app/env/index.js它从#app/env转发browser、dev、building、version四个值而$app/manifest的实现则是 packages/kit/src/runtime/app/manifest/index.js 中对sveltekit:generated/app-manifest.js的整体转发。入口约定src/service-worker自动打包与注册在 SvelteKit 项目中只要存在src/service-worker/index.ts或src/service-worker.ts/.js文件它就会被自动打包并默认注入注册脚本。该入口位置由配置项files.serviceWorker决定默认值为src/service-worker见 packages/kit/src/core/config/index.js 与 packages/kit/src/exports/vite/public.d.ts。开发与生产两种模式下服务工作者脚本的提供方式不同参见 packages/kit/src/exports/vite/dev/index.js 与 packages/kit/src/exports/vite/build/service-worker.js开发模式访问{base}/service-worker.js时返回一条import {base}/{绝对路径}的动态转译入口即开发时不打包、按需转译便于热更新调试生产构建使用 Vite 的独立serviceWorker环境将入口打包为service-worker.js同时把$app/manifest的真实数据通过define注入见下文清单注入一节产物输出在构建目录的根位置。编写一个完整的 Service Worker下面这段来自 documentation/docs/30-advanced/40-service-workers.md 的示例展示了$app/service-worker与$app/env、$app/manifest、$app/paths的典型协同用法预缓存构建产物与静态资源采用immutable/assets 走缓存、其余请求网络优先、离线回退缓存的策略。/// file: src/service-worker.js /// reference no-default-libtrue/ /// reference libesnext / /// reference libwebworker / // ---cut--- 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()); });这个示例有几点值得注意的工程细节以版本号命名缓存version来自$app/env每次部署都会变化因此cache-${version}天然区分不同部署的缓存配合activate阶段的旧缓存清理实现新部署即新缓存、旧缓存自动淘汰。路径必须解析$app/manifest导出的immutable/assets路径相对于paths.base直接用url.pathname匹配会失败因此先经resolve()来自$app/paths转换为绝对路径名。请求过滤与响应克隆fetch处理器仅关心GET写入缓存时使用response.clone()避免缓存占用消耗原始响应体。缓存策略分级对带内容哈希的immutable资源直接命中缓存它们永远不会变可安全长期缓存对页面等动态请求则网络优先、失败回退缓存保证离线可用。谨慎选择缓存对象官方文档提醒某些场景下过期的数据可能比离线不可用更糟同时浏览器会在缓存过大时自动清空因此像视频这类大文件要慎重缓存。immutable、assets、prerendered、routes四个导出的完整类型声明位于 packages/kit/src/types/ambient.d.tsimmutable是 Vite 构建产物开发期为[]assets对应static目录或config.files.assets指定目录中的文件prerendered是预渲染页面与端点routes则以{ id, page, endpoint }形式描述每条可匹配路由的能力。清单注入$app/manifest的数据从哪来$app/manifest的内容由同步阶段的 packages/kit/src/core/sync/write_app_manifest.js 生成开发期直接写入真实的assets与routesimmutable、prerendered为空数组构建期则先写入__SVELTEKIT_MANIFEST_IMMUTABLE__等占位标识符避免内容哈希的循环依赖——清单数据所在 chunk 使用固定文件名其导入方的哈希不受清单内容变化影响。真正的数值在构建收尾阶段由 packages/kit/src/exports/vite/build/index.js 注入构建 Service Worker 前通过builder.environments.serviceWorker.config.define将__SVELTEKIT_MANIFEST_ASSETS__、__SVELTEKIT_MANIFEST_IMMUTABLE__、__SVELTEKIT_MANIFEST_PRERENDERED__、__SVELTEKIT_MANIFEST_ROUTES__替换为真实清单再执行 Service Worker 环境的构建。因此你代码里import { immutable } from $app/manifest拿到的是部署时刻真实的构建产物与静态资源清单天然适配预缓存 版本化缓存名的离线方案。类型安全Service Worker 的 tsconfig 隔离Service Worker 运行在与应用其余部分不同的上下文Worker 线程需要不同的类型。$app/service-worker的self之所以能获得正确的ServiceWorkerGlobalScope类型前提是遵循官方推荐的 tsconfig 隔离方案其说明见 documentation/docs/30-advanced/40-service-workers.md 与 21-$app-tsconfig-service-worker.md在项目根tsconfig.json中排除 Service Worker 代码/// file: tsconfig.json { extends: $app/tsconfig, include: [src, test], exclude: [src/service-worker] }在src/service-worker/目录下放置独立的tsconfig.json继承专为 Service Worker 定制的配置/// file: src/service-worker/tsconfig.json { extends: $app/tsconfig/service-worker }$app/tsconfig/service-worker预置了lib: [esnext, webworker]等适合 Worker 环境的编译选项你也可以在继承基础上覆写自己的compilerOptions限制条件与$app/tsconfig一致。这样self上的addEventListener(fetch, ...)、event.respondWith(...)、caches.open(...)等调用都会获得完整的类型检查与自动补全。自动注册机制与serviceWorker配置项默认情况下SvelteKit 会把注册脚本注入到服务端渲染的 HTML 中。该注入逻辑位于 packages/kit/src/runtime/server/page/render.js生成的脚本大致如下同时可见trustedTypes支持与type: module注册方式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 }); }); }配置项速览serviceWorker配置对象的定义位于 packages/kit/src/core/config/options.js配置项默认值说明serviceWorker.registertrue是否自动注入注册脚本设为false可关闭自动注册改用你自己的注册逻辑serviceWorker.optionsundefined传给navigator.serviceWorker.register的选项对象如scope值类型直接透传给浏览器files.serviceWorkersrc/service-workerService Worker 入口位置两个细节值得注意CSP 联动校验当配置了 CSP 的require-trusted-types-for: [script]且serviceWorker.register为true时config.directives[trusted-types]必须包含sveltekit-trusted-url否则配置校验会直接抛错见 packages/kit/src/core/config/index.js。这正是注入脚本里创建sveltekit-trusted-urlTrusted Type policy 的原因。注册选项透传服务端写入的service_worker_options见 packages/kit/src/core/sync/write_server.js会在注册时强制合并type: module因此你无需也不应在serviceWorker.options里重复设置type。如需手动注册例如在自定义时机、自定义 scope 下注册可设置serviceWorker.register: false并在自己的代码中调用navigator.serviceWorker.register(/service-worker.js, { type: module })。更新机制何时拉取新部署的 Service Worker浏览器只会在两种时机检查 Service Worker 更新其作用域内发生整页导航以及push、sync等功能性事件触发时。SvelteKit 的客户端导航不在这两类之列因此仅靠应用内导航不会自动拉取新部署的 Service Worker。SvelteKit 自身的处理策略见 documentation/docs/30-advanced/40-service-workers.md是仅在错误恢复流程中调用registration.update()——当某个路由模块加载失败、或导航产生错误状态且version.pollInterval轮询默认 3 600 000 ms见 packages/kit/src/core/config/options.js检测到应用已重新部署时会先更新 Service Worker再回退到整页导航。如果你想更积极地让新部署生效可以在根布局中自己触发更新检查import { afterNavigate } from $app/navigation; afterNavigate(async () { if (serviceWorker in navigator) { const registration await navigator.serviceWorker.getRegistration(); await registration?.update(); } });需要理解的是update()触发的是后台安装不会立刻让新 Service Worker 接管当前页面只有当现有 Service Worker 管理的标签页数量归零后新版本才会接管。这一点也解释了为什么版本化缓存名cache-${version}配合activate清理是新旧部署平滑切换的关键——即使新旧 Service Worker 短暂共存各自使用的缓存也是彼此隔离的。注意事项小结$app/service-worker只能在 Service Worker 文件中导入普通页面/服务器代码中导入会在开发模式直接抛错生产构建时immutable/prerendered才有完整数据开发期为[]相关调试逻辑要考虑到这一差异预缓存前务必用$app/paths的resolve处理 base path避免缓存路径与实际请求路径不匹配缓存策略需权衡新鲜度与可用性immutable资源可长期缓存动态页面建议网络优先若要关闭自动注册需同时处理好 CSPtrusted-types配置与手动注册脚本见 packages/kit/src/core/config/index.js。延伸阅读Service workers进阶指南 提供了完整的背景与 Workbox、Vite PWA 插件等替代方案的对比$app/manifest、$app/env、$app/paths参考文档分别给出了配套模块的完整 API 说明。【免费下载链接】kitweb development, streamlined项目地址: https://gitcode.com/gh_mirrors/kit/kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考