应用外壳(app shell)模型
发布于 更新于
一句话: 应用外壳模型把渲染应用 UI 外框所需的标记、样式、脚本与图片缓存进 service worker,这样对已缓存路由的重复访问就能立即从缓存中得到响应,然后再 获取内容。
应用外壳是驱动页面 UI 的最小 HTML、CSS 与 JavaScript 集合——即页面头部、 导航、布局骨架等跨路由保持不变的部分,而非随页面变化的数据。 developer.chrome.com 描述了将这一外壳缓存进 service worker、再单独加载内容 的做法;MDN 则单独说明了 service worker 可以缓存页面、样式、脚本与图片等 通用资源,这正是应用外壳模式所依赖的缓存机制。
在 install 阶段缓存外壳
Section titled “在 install 阶段缓存外壳”const SHELL_CACHE = 'app-shell-v1';const SHELL_ASSETS = ['/', '/styles/app.css', '/scripts/app.js', '/icons/icon-192.png'];
self.addEventListener('install', (event) => { event.waitUntil( caches.open(SHELL_CACHE).then((cache) => cache.addAll(SHELL_ASSETS)) );});
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((cached) => cached ?? fetch(event.request)) );});检测 service worker 支持并提供兜底
Section titled “检测 service worker 支持并提供兜底”这一缓存机制需要 service worker 拦截请求并提供缓存响应;没有它,这种模式就
没有缓存可供响应。developer.chrome.com 指出,对不支持 service worker 的浏览器,
站点仍可回退到普通的 HTTP 缓存(例如长期有效的 Cache-Control 响应头),
而不是完全失去缓存能力:
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js');} else { // 兜底:不支持 service worker,没有离线缓存的外壳。根据 developer.chrome.com // 的说明,这类浏览器仍可获得带长期 Cache-Control 响应头的服务端渲染页面, // 而非依赖这套基于 SW 的方案。}为外壳做版本管理
Section titled “为外壳做版本管理”根据 Service Workers 规范与 developer.chrome.com 的说明,service worker 在
install 阶段填充缓存,并可以在 activate 阶段显式删除过期的缓存条目。
像上面 SHELL_CACHE 那样为缓存名加上版本号,让 install 填充一个新缓存,
再在 activate 中删除旧命名缓存的条目,就是让外壳标记、样式或脚本的变更
触达已安装用户的方式。将删除范围限定在外壳自身的缓存名前缀(app-shell-)
内,这样只会清理过期的外壳版本,而不会波及数据缓存、运行时缓存等其他同源
缓存:
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys .filter((key) => key.startsWith('app-shell-') && key !== SHELL_CACHE) .map((key) => caches.delete(key)) ) ) );});应用外壳模型本身并不是一个浏览器 API——它是构建在 Service Worker API 与 Cache API 之上的一种缓存模式,这两者在当前的 Chromium、Firefox 与 Safari 中均已实现。这一模式 所依赖的更广泛预缓存 API 参见 预缓存。
- 在
install阶段只缓存外壳自身的标记、样式、脚本与图片——而不是随请求变化的 页面内容。 - 对
'serviceWorker' in navigator做特性检测,缺失时让页面照常加载。 - 给缓存名加上版本后缀,并在外壳资源变更时于
activate中删除旧版本缓存。 - 内容单独填充进外壳(网络或数据缓存)——不要试图预缓存内容随请求变化的页面。