跳转到内容

入门

发布于

完成本指南后,一个普通网站会被 Chrome、Edge 与 Samsung Internet 识别为可安装,从主屏幕或程序坞以独立窗口启动,并从缓存提供应用壳。起决定作用的只有两个文件:Web 应用 manifest 与 Service Worker,站点其余部分不变。

你需要一个通过 HTTPS(开发阶段可用 localhost)提供的站点、两张 192 × 192 与 512 × 512 像素的正方形 PNG 图标,以及一个文本编辑器。不需要框架或构建步骤。本指南中我们把这两个文件放在站点根目录,让 manifest 的 scope 与 Service Worker 的作用域都覆盖全部页面。

Service Worker 只能在安全上下文中注册。MDN 的 Service Worker 指南写明 localhost 视为安全来源,因此 http://localhost:8080 这样的本地开发服务器可用,而 http://192.168.1.5 这样的局域网地址不可用。打开站点,在 Chrome 中确认地址栏没有 “Not secure” 标记再继续。跳过这一步时,Chrome 的可安装性检查会报告 Page isn't served from a secure origin(字符串来自 DevTools 前端源码 AppManifestView.ts)。

在站点根目录创建 /manifest.webmanifest。按 MDN 可安装性指南,Chromium 浏览器安装所要求的成员是:name 或 short_name、含 192 px 与 512 px 两项的 icons、start_url 以及 display。id 与 scope 不是安装的必要条件,但现在就定下来可以避免日后出现第二个身份:MDN 记载 id 缺省时取 start_url,之后若改动 start_url,浏览器会把它当作另一个应用。

{
"id": "/",
"name": "Field Notes",
"short_name": "Notes",
"description": "Offline-first notes that sync when you are back online.",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1f4e79",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}

maskable 图标是单独一张图,主体画在半径为宽度 40% 的中心圆内;web.dev 的 maskable-icon 文章不建议一张图同时标为 any 与 maskable,因为 maskable 所需的留白会让 any 场景下的图标变小。在每个 HTML 页面引用 manifest:

<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#1f4e79">

在站点根目录创建 /sw.js。下面这个 worker 在 install 阶段预缓存应用壳,在 activate 阶段删除旧版本缓存,并在访问网络之前先用缓存响应导航与壳文件请求。预缓存文件有变动时递增 VERSION;浏览器把字节不同的 worker 视为一次更新。

const VERSION = 'v1';
const SHELL_CACHE = `shell-${VERSION}`;
const SHELL = ['/', '/index.html', '/app.css', '/app.js', '/offline.html', '/icons/icon-192.png'];
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(SHELL_CACHE).then((cache) => cache.addAll(SHELL)));
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((key) => key !== SHELL_CACHE).map((key) => caches.delete(key)))
)
);
});
self.addEventListener('fetch', (event) => {
const { request } = event;
if (request.method !== 'GET') return;
event.respondWith(
caches.match(request).then((cached) => {
if (cached) return cached;
return fetch(request).catch(() => {
if (request.mode === 'navigate') return caches.match('/offline.html');
return Response.error();
});
})
);
});

从页面脚本注册它。in 检测让没有该 API 的浏览器照常工作;scope 选项虽然对根目录下的 worker 默认就是 /,这里仍显式写出:

if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
console.log('Service worker registered with scope', registration.scope);
} catch (error) {
console.error('Service worker registration failed:', error);
}
});
}

从 Chrome 菜单安装已不再要求 Service Worker:Chrome 团队在 Android 版 Chrome 108 与桌面版 Chrome 112 取消了 fetch 处理器要求,Chrome 112 还会忽略空的 fetch 处理器。自动安装提示(beforeinstallprompt)仍会检查 fetch 处理器,上面的 worker 已具备。

打开 DevTools(Command+Shift+P 或 Control+Shift+P,输入 application,选择 Show Application),进入 Manifest 面板。面板会列出解析后的 Identity、Presentation 与 Icons 区块;只有出现问题时才会显示 Installability 区块。在 Service workers 下,/sw.js 的 Status 一行应显示 #N activated and is running(N 为 worker 版本号);Cache Storage 应列出 shell-v1 及预缓存文件。若缓存没有出现,刷新一次:Chrome 的 DevTools 文档指出,首次向新缓存写入时 DevTools 可能要到页面刷新后才检测到。

桌面版 Chrome 会在可安装页面的地址栏显示安装图标;也可以走 More > Cast, save, and share > Install page as app…(英文界面)。Android 上的路径是 More > Install and create shortcut > Install。iOS 与 iPadOS 没有安装提示:在 Safari 中点 Share,再点 Add to Home Screen(iOS 16.4 起 Chrome、Edge、Firefox 与 Orion 的分享菜单也有同一入口)。启动已安装的应用:它不带浏览器标签页打开,首个导航带有 ?source=pwa,断网后页面仍能加载,因为应用壳来自 shell-v1。

← 返回指南总览。