遇到即记之渐进式 Web 应用(PWA)让Web拥有APP的体验

渐进式 Web 应用(PWA)入门与开发指南

标签 :PWA、渐进式Web应用、Service Worker、Web App Manifest、前端开发
摘要:本文系统介绍什么是 PWA、具备哪些能力、优缺点是什么,以及如何从零实现「可安装」的 PWA(Manifest、Service Worker、安装提示),并附带可复用的代码示例与验证方式,方便日后复习与对外分享。


📑 目录

  • [一、什么是 PWA](#一、什么是 PWA)
    • [1.1 定义](#1.1 定义)
    • [1.2 由来与标准](#1.2 由来与标准)
  • [二、PWA 的核心能力与"功能清单"](#二、PWA 的核心能力与"功能清单")
    • [2.1 官方常说的几大特性](#2.1 官方常说的几大特性)
    • [2.2 三件技术"基础设施"](#2.2 三件技术"基础设施")
  • [三、PWA 的优缺点(便于做技术选型)](#三、PWA 的优缺点(便于做技术选型))
    • [3.1 优点](#3.1 优点)
    • [3.2 缺点与限制](#3.2 缺点与限制)
  • 四、从零到"可安装"的代码怎么搭
    • [4.1 Web App Manifest](#4.1 Web App Manifest)
    • [4.2 在 HTML 中引用 Manifest](#4.2 在 HTML 中引用 Manifest)
    • [4.3 Service Worker:用 Workbox 做"可安装 + 简单缓存"](#4.3 Service Worker:用 Workbox 做"可安装 + 简单缓存")
    • [4.4 注册 Service Worker(浏览器端)](#4.4 注册 Service Worker(浏览器端))
    • [4.5 安装提示组件(引导用户点"安装")](#4.5 安装提示组件(引导用户点"安装"))
  • [五、如何验证"算不算 PWA"](#五、如何验证"算不算 PWA")
  • 六、开发时要注意什么
  • 七、小结

一、什么是 PWA

1.1 定义

渐进式 Web 应用(Progressive Web App,PWA) 是使用 Web 技术 (HTML、CSS、JavaScript)开发,但在体验上接近 原生应用 的 Web 应用。

核心特点

  • 渐进式:在不支持高级特性的浏览器里,仍以普通网页形式可用;在支持的浏览器里,逐步获得「可安装、离线、推送」等能力。
  • Web 应用:本质仍是网页,通过 URL 访问,无需上架应用商店即可分发。

💡 简单理解:用网页做的"假 App",但用起来像真 App。

1.2 由来与标准

  • 2015 年:Google 提出 PWA 概念,随后 Chrome 率先支持。
  • 标准组织 :核心能力依赖 W3CWHATWG 标准:Service Worker、Web App Manifest 等。
  • 浏览器支持:目前主流浏览器(Chrome、Firefox、Safari、Edge)均已支持 PWA 相关特性,只是支持程度略有差异。

二、PWA 的核心能力与"功能清单"

2.1 官方常说的几大特性

特性 含义 依赖技术
可安装(Installable) 可"添加到主屏幕",有独立图标和启动方式 Web App Manifest + 安装条件
离线可用(Offline-first) 弱网/无网时仍能打开已访问过的内容 Service Worker + 缓存策略
可靠(Reliable) 加载快、少白屏、少报错 Service Worker、预缓存、错误处理
体验佳(Engaging) 全屏/ standalone、推送通知、快捷方式 Manifest、Push API、Shortcuts
安全(Secure) 数据与脚本在安全上下文中运行 必须 HTTPS
可发现、可链接 仍是网页,可被搜索、分享链接 无需改现有 URL 体系

📝 说明:上面这些就是 PWA 的"功能清单",我们说的"做 PWA",其实就是逐步实现其中几项或全部。

2.2 三件技术"基础设施"

要做成 PWA,至少需要这三块:

  1. Web App Manifest(manifest.json

    告诉浏览器:名字、图标、主题色、从哪一页启动、是否全屏等,从而支持「添加到主屏幕」和安装体验。

  2. Service Worker(SW)

    在页面之外的独立线程里运行,可以拦截请求、读写缓存、在离线时返回缓存内容,是实现「离线、加速、后台同步、推送」的基础。

  3. HTTPS

    PWA 必须在安全源(HTTPS 或 localhost)下才能注册 Service Worker,这是硬性要求。


三、PWA 的优缺点(便于做技术选型)

3.1 优点

开发与维护成本低:一套 Web 代码,多端复用,无需分别维护 iOS/Android 原生 App。

无需应用商店:直接通过链接分享、推广,更新即部署即可,没有审核与上架周期。

体积小、安装快:用户"安装"的其实是入口 + 缓存策略,不必下载完整安装包。

可渐进增强:老浏览器当普通网页用,新浏览器自动获得安装、离线等能力。

利于 SEO 与分享:本质仍是网页,链接可被搜索、收藏、转发。

易于灰度与回滚:和普通前端发布一样,可按域名/路由做灰度或快速回滚。

3.2 缺点与限制

系统级能力有限:蓝牙、NFC、部分传感器、复杂后台任务等,仍不如原生。

iOS / Safari 支持不完整:例如推送、后台同步等,在 iOS 上至今支持有限或行为不一致。

依赖浏览器实现:安装入口、安装条件、外观由各浏览器决定,无法完全统一。

需自己设计缓存与更新策略:Service Worker 一旦缓存过度或更新不当,会导致"一直用旧版"或"总是重新下载"等体验问题。

调试相对复杂:SW 在独立线程、持久存活,需要熟悉 Chrome DevTools 的 Application → Service Workers 等工具。

💡 提示:了解这些,可以在技术评审时更客观地评估:什么时候上 PWA、做到什么程度、和原生 App / TWA 如何分工。


四、从零到"可安装"的代码怎么搭

下面按 Manifest → Service Worker → 安装提示 → 在 HTML 里接好 的顺序,给出一套最小可运行示例。

4.1 Web App Manifest

在站点的静态资源目录 (如 public/)下放一个 manifest.json

json 复制代码
{
  "name": "My App Full Name",
  "short_name": "Short Name",
  "description": "App description for install prompt and search engines",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "background_color": "#000000",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any"
    }
  ]
}

常见字段说明

  • name / short_name :安装后图标下显示的名称,空间有限时多用 short_name
  • start_url :从主屏点击图标时打开的地址,一般设为 / 或首页路径。
  • displaystandalone 表示"像 App 一样"单独窗口,没有浏览器地址栏;还可选 fullscreenminimal-uibrowser
  • icons:至少准备 192×192 和 512×512,系统会按场景选用。
  • scope:约定哪些路径属于"本 PWA",超出范围的会用浏览器打开。

📝 提示 :准备好多尺寸图标(如 72、96、128、144、192、384、512)后,在 icons 里都列出来即可。

4.2 在 HTML 中引用 Manifest

index.html<head> 里加上:

html 复制代码
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#000000" />

<!-- iOS Apple Touch Icon -->
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />

这样浏览器才能发现"这是一个可安装的 PWA"。

4.3 Service Worker:用 Workbox 做"可安装 + 简单缓存"

实际项目里一般不会手写裸的 Service Worker,而是用 Workbox 这类库。下面是一段示例代码:

使用说明

  • 使用 InjectManifest 时,会把构建产物(如 JS/CSS)注入到 self.__WB_MANIFEST,再交给 precacheAndRoute 做预缓存。
  • 对图片、字体等静态资源,用 Cache First 即可;对 HTML/API 可按需用 Network First 等。
javascript 复制代码
// sw-source.js - Workbox will inject __WB_MANIFEST during build
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// Precache build assets (injected at build time)
precacheAndRoute(self.__WB_MANIFEST || []);

// Cache images and fonts with Cache First strategy
registerRoute(
  ({ request }) =>
    request.destination === 'image' || request.destination === 'font',
  new CacheFirst({
    cacheName: 'static-resources',
    plugins: [
      new CacheableResponsePlugin({ statuses: [0, 200] }),
      new ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
      }),
    ],
  })
);

要点

  • precacheAndRoute:保证核心静态资源在安装阶段就进缓存,满足「可安装」和首屏速度。
  • registerRoute:为某类请求指定缓存策略,这里只是示例,实际可按项目的接口与资源类型再细分。

⚠️ 注意 :若项目里已有别的 SW 逻辑(例如代理、长连接),记得在 fetch先处理这些路径,再把剩余请求交给 Workbox,避免被缓存或错误处理。

4.4 注册 Service Worker(浏览器端)

在单页应用的入口(如 main.tsxroot.tsx)里,在合适的生命周期中注册 SW:

javascript 复制代码
// Register Service Worker in app entry (main.tsx or root.tsx)
if ('serviceWorker' in navigator && import.meta.env.PROD) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/sw.js', { scope: '/' })
      .then((reg) => {
        console.log('SW registered:', reg.scope);
        
        // Optional: Check for updates
        reg.addEventListener('updatefound', () => {
          const newWorker = reg.installing;
          newWorker?.addEventListener('statechange', () => {
            if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
              // New version available, prompt user to refresh
              console.log('New SW available, please refresh');
            }
          });
        });
      })
      .catch((err) => console.error('SW registration failed', err));
  });
}

📝 说明 :很多项目会把 SW 的构建交给 Webpack 的 workbox-webpack-plugin(或 Vite 的对应插件),/sw.js 由构建输出到 public 或根路径,这里只要保证路径一致即可。

4.5 安装提示组件(引导用户点"安装")

浏览器在满足一定条件时会触发 beforeinstallprompt,我们可以把该事件里的"安装能力"存起来,再在合适时机调 prompt() 弹出系统安装框。

React 组件示例

tsx 复制代码
// InstallPrompt.tsx
import { useState, useEffect } from 'react';

interface BeforeInstallPromptEvent extends Event {
  prompt: () => Promise<void>;
  userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
}

export function InstallPrompt() {
  const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
  const [showPrompt, setShowPrompt] = useState(false);

  useEffect(() => {
    // Already opened as PWA, no need to prompt
    if (window.matchMedia('(display-mode: standalone)').matches) {
      return;
    }
    
    // Check if user dismissed before (don't prompt again within 7 days)
    const dismissed = localStorage.getItem('pwa-install-dismissed');
    if (dismissed) {
      const dismissedTime = parseInt(dismissed, 10);
      const daysSinceDismissed = (Date.now() - dismissedTime) / (1000 * 60 * 60 * 24);
      if (daysSinceDismissed < 7) return;
    }
    
    // Listen for install prompt event
    const handler = (e: Event) => {
      e.preventDefault();
      setDeferredPrompt(e as BeforeInstallPromptEvent);
      setShowPrompt(true);
    };
    
    window.addEventListener('beforeinstallprompt', handler);
    return () => window.removeEventListener('beforeinstallprompt', handler);
  }, []);

  // Handle install
  const handleInstall = async () => {
    if (!deferredPrompt) return;
    
    try {
      await deferredPrompt.prompt();
      const { outcome } = await deferredPrompt.userChoice;
      
      if (outcome === 'accepted') {
        console.log('User accepted install prompt');
      }
      
      setDeferredPrompt(null);
      setShowPrompt(false);
    } catch (error) {
      console.error('Install failed:', error);
    }
  };

  // Handle dismiss
  const handleDismiss = () => {
    setShowPrompt(false);
    localStorage.setItem('pwa-install-dismissed', String(Date.now()));
  };

  if (!showPrompt) return null;
  
  return (
    <div className="pwa-install-banner">
      <p>安装到主屏幕,使用更方便</p>
      <button onClick={handleInstall}>安装</button>
      <button onClick={handleDismiss}>暂不</button>
    </div>
  );
}

要点

  • display-mode: standalone 判断是否已经是从主屏启动的 PWA,避免已安装用户还看到安装条。
  • localStorage 记录"用户点过暂不"的时间,避免频繁打扰。
  • 只有在 HTTPS(或 localhost)且满足浏览器安装条件时 才会触发 beforeinstallprompt,本地调试时可借助 Chrome 的"Application → Manifest"里触发的"Add to home screen"做验证。

💡 使用 :把 <InstallPrompt /> 放在根布局或首页,即可在合适时机引导用户安装。


五、如何验证"算不算 PWA"

5.1 Chrome DevTools

  1. Application → Manifest

    检查能否正确解析、是否提示可安装。

  2. Application → Service Workers

    查看 SW 是否已注册、处于 active,以及缓存列表。

5.2 Lighthouse

在 Application 面板勾选"PWA"后跑一次 Lighthouse,会给出可安装性、SW、Manifest 等评分和修改建议。

5.3 真机测试

在手机浏览器里访问你的 HTTPS 地址,看是否有"添加到主屏幕"或"安装应用"的入口,以及安装后图标、启动页、全屏是否符合预期。


六、开发时要注意什么

⚠️ 必须 HTTPS(或 localhost):Service Worker 只能在安全源下注册,生产环境必须是 HTTPS。

⚠️ Manifest 的 start_url 要在 scope:否则安装后可能打不开或行为异常。

⚠️ 图标尺寸要够 :至少 192×192 和 512×512,且格式、路径与 manifest.json 里一致。

⚠️ SW 更新策略 :SW 会长期缓存,更新后要用 skipWaiting + clients.claim() 或在客户端提示用户刷新,否则用户可能一直用旧版。

⚠️ 缓存不要过猛:API、实时数据慎用长期缓存,避免"一直看到旧数据";静态资源可大胆 Cache First。

⚠️ beforeinstallprompt 不是一定会触发:浏览器会根据「访问频次、交互、HTTPS」等条件决定是否弹出安装条,本地调试时可结合 Lighthouse / Application 面板验证。


七、小结

看到"PWA"时,你会想到:

  • 是什么:用 Web 技术做的、体验接近原生且可安装的 Web 应用。
  • 靠什么实现:Web App Manifest + Service Worker + HTTPS,三者缺一不可。
  • 能做什么:可安装、离线/弱网可用、更快更稳、推送与快捷方式(视浏览器支持而定)。
  • 优缺点:开发效率高、无需上架、易于迭代;但系统能力与部分浏览器支持不如原生,需要自己设计缓存与更新策略。
  • 代码上 :先写好 manifest.json 并在 HTML 里挂好;再用 Workbox(或等价方案)在 SW 里做预缓存和简单路由策略;最后在页面上接 beforeinstallprompt 做安装引导即可。

💡 记住:把上面几条记牢,以后无论是做技术方案还是看别人博客里的「PWA 改造」,都会更容易抓到重点。


📚 写在最后

本文内容基于实际项目中的 PWA 实践整理,示例思路与项目内的 manifest.jsonsw-source.jsInstallPrompt 保持一致,可直接用于 CSDN 发布或团队内部分享。

若要做「从零到可安装」的最小清单,可按第二节的功能清单 + 第四节的代码顺序,逐项补齐 Manifest、SW 注册、安装提示即可。

相关资源


如果觉得文章有帮助,欢迎点赞、收藏、关注! 🎉

相关推荐
念念不忘 必有回响12 小时前
viepress:vue组件展示和源码功能
前端·javascript·vue.js
C澒12 小时前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅12 小时前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘12 小时前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
恋猫de小郭13 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅20 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606121 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了21 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅21 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅21 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端