PWA 入门:manifest 与 Service Worker 能带来什么
PWA(渐进式 Web 应用)能把网站装到桌面/手机上,还能离线用。听起来美好,但要清楚它的实际收益和成本。
一、三个必要条件
1. HTTPS(本地 localhost 除外)
2. manifest.json(应用信息)
3. Service Worker(可离线运行的脚本)三者齐备后,浏览器会显示「安装」提示。
二、manifest.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 里引用:
<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">关键字段:
| 字段 | 说明 |
|---|---|
display | standalone 隐藏浏览器 UI,像原生 App |
start_url | 打开时进入哪个页面 |
purpose: "maskable" | 图标可被系统裁成圆形(Android 需要) |
三、Service Worker 缓存策略
SW 是一个能拦截网络请求的后台脚本。核心是「什么时候用缓存、什么时候走网络」:
| 策略 | 行为 | 适用 |
|---|---|---|
| Cache First | 先查缓存,没有才请求 | 静态资源(JS/CSS/图片) |
| Network First | 先请求,失败用缓存 | 需要新鲜度的页面 |
| Stale While Revalidate | 先用缓存,同时后台更新 | 最常用,平衡速度与新鲜度 |
| Cache Only | 只用缓存 | 离线必需资源 |
| Network Only | 只走网络 | 不能缓存的请求(如 POST) |
一个够用的实现:
// 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")))
);
});注册:
<script>
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js").catch(e => console.warn(e));
});
}
</script>四、更新机制(最容易坑人的地方)
SW 缓存了 HTML,用户可能一直看旧版本。要点:
- 改 CACHE 版本号,新 SW 安装时会清掉旧缓存;
skipWaiting()+clients.claim()让新 SW 立即接管;- 也可以提示用户「有新版本,刷新」:
// 页面侧监听更新
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 分项检查。
# 注意:SW 只在 https 或 localhost 下工作
# file:// 协议完全不支持 —— 本站为了能离线双击打开,没有启用 SW六、它能带来什么
| 收益 | 说明 |
|---|---|
| 可安装 | 桌面/主屏有图标,打开无浏览器地址栏 |
| 离线访问 | 预缓存的内容断网也能看 |
| 加载更快 | 二次访问走缓存 |
| 推送通知 | 需要额外配置(Web Push) |
七、代价
| 代价 | 说明 |
|---|---|
| 缓存一致性 | 用户可能看到旧内容 |
| 调试复杂 | 「改了没生效」多半是 SW 缓存 |
| 需要 HTTPS | 本地开发受限 |
| iOS 支持有限 | Safari 的 PWA 体验和 Android 有差距 |
八、本站为什么没做
这是刻意的选择:
- 要支持
file://双击打开 —— SW 在该协议下完全不工作; - 本站是纯静态、体积很小,二次访问本来就快;
- 多一个 SW 就多一层「改了没生效」的困惑。
如果你的站点:
- 需要离线可用(比如工具站、文档站);
- 用户会反复访问;
- 有 HTTPS 且不需要
file://打开;
那 PWA 值得做。个人笔记站通常没必要。
九、折中方案
只做 manifest(可安装),不做 Service Worker:
<link rel="manifest" href="/manifest.json">这样能获得「添加到主屏、独立窗口」的体验,又不用承担缓存一致性的麻烦。对内容型站点这是性价比最高的选择。
十、检查清单
- HTTPS
- manifest.json 有 name/short_name/icons/start_url/display
- 图标含 192 和 512 两个尺寸
- theme_color 与页面配色一致
- SW 有版本管理和清理旧缓存的逻辑
- 用 Lighthouse 验证过
PWA 的价值取决于「用户是否需要反复、离线地访问」。想清楚这个,再决定要不要引入。
