PWA 入门:manifest 与 Service Worker 能带来什么

PWA(渐进式 Web 应用)能把网站装到桌面/手机上,还能离线用。听起来美好,但要清楚它的实际收益和成本。

一、三个必要条件

bash
1. HTTPS(本地 localhost 除外)
2. manifest.json(应用信息)
3. Service Worker(可离线运行的脚本)

三者齐备后,浏览器会显示「安装」提示。

二、manifest.json

json
{
  "name": "白泽网络 · 技术实践笔记",
  "short_name": "白泽网络",
  "description": "网站搭建、服务器、域名的实践记录",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "background_color": "#0c1016",
  "theme_color": "#0c1016",
  "orientation": "portrait",
  "lang": "zh-CN",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

HTML 里引用:

html
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0c1016">
<link rel="apple-touch-icon" href="/icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">

关键字段:

字段说明
displaystandalone 隐藏浏览器 UI,像原生 App
start_url打开时进入哪个页面
purpose: "maskable"图标可被系统裁成圆形(Android 需要)

三、Service Worker 缓存策略

SW 是一个能拦截网络请求的后台脚本。核心是「什么时候用缓存、什么时候走网络」:

策略行为适用
Cache First先查缓存,没有才请求静态资源(JS/CSS/图片)
Network First先请求,失败用缓存需要新鲜度的页面
Stale While Revalidate先用缓存,同时后台更新最常用,平衡速度与新鲜度
Cache Only只用缓存离线必需资源
Network Only只走网络不能缓存的请求(如 POST)

一个够用的实现:

js
// sw.js
const CACHE = "baize-v1";
const SHELL = ["/", "/index.html", "/assets/css/style.css",
               "/assets/js/app.js", "/assets/js/posts.js", "/manifest.json"];

// 安装:预缓存关键资源
self.addEventListener("install", (e) => {
  e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL)));
  self.skipWaiting();
});

// 激活:清理旧缓存
self.addEventListener("activate", (e) => {
  e.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.filter(k => k !== CACHE).map(k => caches.delete(k))
    ))
  );
  self.clients.claim();
});

// 拦截请求
self.addEventListener("fetch", (e) => {
  const req = e.request;
  if (req.method !== "GET") return;

  // 静态资源:缓存优先
  if (/\.(css|js|png|webp|svg|woff2)$/.test(req.url)) {
    e.respondWith(
      caches.match(req).then(hit => hit || fetch(req).then(res => {
        const copy = res.clone();
        caches.open(CACHE).then(c => c.put(req, copy));
        return res;
      }))
    );
    return;
  }

  // 页面:网络优先,失败回退缓存(离线可用)
  e.respondWith(
    fetch(req)
      .then(res => {
        const copy = res.clone();
        caches.open(CACHE).then(c => c.put(req, copy));
        return res;
      })
      .catch(() => caches.match(req).then(hit => hit || caches.match("/index.html")))
  );
});

注册:

html
<script>
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker.register("/sw.js").catch(e => console.warn(e));
  });
}
</script>

四、更新机制(最容易坑人的地方)

SW 缓存了 HTML,用户可能一直看旧版本。要点:

  1. 改 CACHE 版本号,新 SW 安装时会清掉旧缓存;
  2. skipWaiting() + clients.claim() 让新 SW 立即接管;
  3. 也可以提示用户「有新版本,刷新」:
js
// 页面侧监听更新
navigator.serviceWorker.addEventListener("controllerchange", () => {
  // 新 SW 接管了
});

registration.addEventListener("updatefound", () => {
  const sw = registration.installing;
  sw.addEventListener("statechange", () => {
    if (sw.state === "installed" && navigator.serviceWorker.controller) {
      console.log("有新版本可用");
    }
  });
});

缓存更新是 PWA 最大的维护成本。 如果内容更新频繁而用户看到的还是旧的,体验反而变差。

五、调试

Chrome DevTools:

  • Application → Service Workers:看状态、手动 Unregister、Update;
  • Application → Cache Storage:看缓存了什么;
  • 勾选 "Update on reload" 避免调试时被旧缓存干扰;
  • Lighthouse 会给出 PWA 分项检查。
bash
# 注意:SW 只在 https 或 localhost 下工作
# file:// 协议完全不支持 —— 本站为了能离线双击打开,没有启用 SW

六、它能带来什么

收益说明
可安装桌面/主屏有图标,打开无浏览器地址栏
离线访问预缓存的内容断网也能看
加载更快二次访问走缓存
推送通知需要额外配置(Web Push)

七、代价

代价说明
缓存一致性用户可能看到旧内容
调试复杂「改了没生效」多半是 SW 缓存
需要 HTTPS本地开发受限
iOS 支持有限Safari 的 PWA 体验和 Android 有差距

八、本站为什么没做

这是刻意的选择:

  1. 要支持 file:// 双击打开 —— SW 在该协议下完全不工作;
  2. 本站是纯静态、体积很小,二次访问本来就快;
  3. 多一个 SW 就多一层「改了没生效」的困惑。

如果你的站点:

  • 需要离线可用(比如工具站、文档站);
  • 用户会反复访问;
  • 有 HTTPS 且不需要 file:// 打开;

那 PWA 值得做。个人笔记站通常没必要。

九、折中方案

只做 manifest(可安装),不做 Service Worker:

html
<link rel="manifest" href="/manifest.json">

这样能获得「添加到主屏、独立窗口」的体验,又不用承担缓存一致性的麻烦。对内容型站点这是性价比最高的选择。

十、检查清单

  • HTTPS
  • manifest.json 有 name/short_name/icons/start_url/display
  • 图标含 192 和 512 两个尺寸
  • theme_color 与页面配色一致
  • SW 有版本管理和清理旧缓存的逻辑
  • 用 Lighthouse 验证过

PWA 的价值取决于「用户是否需要反复、离线地访问」。想清楚这个,再决定要不要引入。