# 入门

> 端到端构建并发布第一个可安装 PWA：添加 manifest、注册 Service Worker、满足可安装门槛。

完成本指南后，一个普通网站会被 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

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

```json
{
  "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：

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

## 编写并注册 Service Worker

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

```js
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 默认就是 `/`，这里仍显式写出：

```js
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 已具备。

## 在 Chrome DevTools 中核对结果

打开 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 可能要到页面刷新后才检测到。

:::observed
Chrome 的 **Installability** 区块使用 DevTools 前端中的固定字符串（`front_end/panels/application/AppManifestView.ts`，main 分支，2026-10-03 读取）。本指南各步骤所预防的几条是：`Page has no manifest <link> URL`、`Manifest couldn't be fetched, is empty, or couldn't be parsed`、`Manifest doesn't contain a 'name' or 'short_name' field`、`Manifest 'start_url' isn't valid`，以及 `Manifest 'display' property must be one of 'standalone', 'fullscreen', or 'minimal-ui'`。看到其中任何一条，修正所指成员后刷新面板即可。
:::

## 安装并启动

桌面版 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`。

## 另请参阅

- [让它可安装](/zh/guides/installable/)
- [离线策略](/zh/guides/offline/)
- [manifest 参考](/zh/reference/manifest/)
- [Service Worker 生命周期](/zh/reference/service-worker/lifecycle/)
- [Installation criteria](https://web.dev/articles/install-criteria)（web.dev）
- [Making PWAs installable](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable)（developer.mozilla.org）

← 返回[指南](/zh/guides/)总览。