遇到即记之渐进式 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 注册、安装提示即可。

相关资源


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

相关推荐
雨季6664 小时前
构建 OpenHarmony 简易文字行数统计器:用字符串分割实现纯文本结构感知
开发语言·前端·javascript·flutter·ui·dart
小北方城市网4 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
console.log('npc')4 小时前
vue2 使用高德接口查询天气
前端·vue.js
2401_892000524 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加支出实现
前端·javascript·flutter
天马37984 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
天天向上10245 小时前
vue3 实现el-table 部分行不让勾选
前端·javascript·vue.js
qx095 小时前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript
Mr Xu_6 小时前
前端实战:基于Element Plus的CustomTable表格组件封装与应用
前端·javascript·vue.js·elementui
0思必得06 小时前
[Web自动化] 爬虫之API请求
前端·爬虫·python·selenium·自动化
混迹在开发队伍里的伪开发6 小时前
css的var用法,定义属性,全局使用
前端·css