目录
- 概述
- 实战
- vue项目
- 问题汇总
PWA(渐进式 Web 应用,Progressive Web App) 2015提出
概述
PWA 是一种提升 Web 应用体验的技术,使其具备与原生应用相似的功能和性能。PWA不仅能够在网页上运行,还能在手机或桌面上像传统的移动应用一样进行交互,同时保留了Web应用的灵活性。
它通过借助一些先进的功能,如Service Workers、Web App Manifest 和 Push Notifications 等,来提升用户体验、优化性能,并能在离线或低网速环境下依然保持可用性。
PWA 强调的是“渐进式”,即可以根据设备的能力逐步增强其功能。
- 主要特点:
- 离线支持: 使用 Service Worker,可以使应用在没有网络连接时也能运行。Service Worker 是一种浏览器后台脚本,能够拦截并缓存网络请求,使得 Web 应用在离线状态下仍然能够继续工作。
- 推送通知: PWA 可以向用户推送通知,即使他们没有打开应用/应用没有运行,提升用户参与度。
- 响应式设计: PWA 具有响应式设计,可以适应不同的屏幕尺寸和设备,提供一致的用户体验。
- 安装到主屏幕:通常通过 Web App Manifest 文件配置,它提供了有关应用外观、名称、图标等信息。
PWA 可以通过浏览器直接安装到设备的主屏幕,用户像安装本地应用一样,直接启动 PWA,而无需经过应用商店。安装后,PWA 会像原生应用一样运行,且不显示浏览器界面。 - 快速加载: 通过资源缓存、懒加载和其他性能优化手段,PWA 可以提供比传统 Web 应用更快速的加载体验。
- 自更新:PWA 可以在后台自动更新内容和资源,使得用户始终体验到最新版本的应用。
- 提高性能
由于 Service Worker 能够缓存重要资源,PWA 可以大幅度提高应用的加载速度,减少网络请求。
- 使用场景:
需要在没有稳定网络的情况下工作,如离线文档编辑器、离线新闻阅读器等。
需要提升用户参与度的应用,通过推送通知与用户保持互动。
需要跨平台应用并提供与原生应用相似体验的 Web 应用。
实战
- PWA 的技术组成
- Web App Manifest
这是一个JSON文件,告诉浏览器如何显示应用,包括应用的名称、图标、启动页面、主题颜色等。它允许用户将 PWA 添加到主屏幕并像原生应用一样使用。
{"name": "My PWA App","short_name": "PWA","start_url": "/","display": "standalone","background_color": "#ffffff","theme_color": "#0000ff","icons": [{"src": "icons/icon-192x192.png","sizes": "192x192","type": "image/png"},{"src": "icons/icon-512x512.png","sizes": "512x512","type": "image/png"}]
}
- Service Worker
Service Worker 是一个在浏览器后台运行的脚本,它可以拦截网络请求,缓存资源并提供离线支持。Service Worker 还可以实现推送通知和后台数据同步等功能。
编写 Service Worker 注册脚本
在你的主文件(例如 index.js 或 app.js)中调用 registerServiceWorker
确保你的 manifest.json 文件配置正确
// registerServiceWorker.jsexport function register() {if ('serviceWorker' in navigator) {window.addEventListener('load', () => {const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; // 指定 Service Worker 的路径navigator.serviceWorker.register(swUrl).then(registration => {console.log('Service Worker registered with scope: ', registration.scope);}).catch(error => {console.log('Service Worker registration failed: ', error);});});}
}export function unregister() {if ('serviceWorker' in navigator) {navigator.serviceWorker.ready.then(registration => {registration.unregister().then(success => {if (success) {console.log('Service Worker unregistered');}}).catch(error => {console.log('Service Worker unregistration failed: ', error);});});}
}// index.js 或 app.jsimport React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { register } from './registerServiceWorker'; // 导入 register 函数// 注册 Service Worker
register();ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById('root')
);
创建 Service Worker 脚本
在 service-worker.js 中,你可以缓存应用资源,处理离线请求等。
// service-worker.jsconst CACHE_NAME = 'my-cache-v1';
const urlsToCache = ['/','/index.html','/styles.css','/script.js', // 你所有的静态文件,或者可以使用通配符
];// 安装阶段,缓存静态资源
self.addEventListener('install', event => {event.waitUntil(caches.open(CACHE_NAME).then(cache => {console.log('Opened cache');return cache.addAll(urlsToCache);}));
});// 激活阶段,清理过时的缓存
self.addEventListener('activate', event => {const cacheWhitelist = [CACHE_NAME];event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (!cacheWhitelist.includes(cacheName)) {return caches.delete(cacheName);}}));}));
});// 拦截网络请求,使用缓存响应请求
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(cachedResponse => {if (cachedResponse) {return cachedResponse; // 如果缓存中有匹配的资源,直接返回}return fetch(event.request); // 否则正常请求}));
});
- HTTPS
PWA 强烈推荐使用 HTTPS,因为 Service Worker 只能在安全的环境下运行,且 HTTPS 提供了更高的安全性。
vue项目
在 Vue + Vite 项目中实现 离线缓存(离线也能访问) 的最佳方案是使用 PWA(Progressive Web App)技术。你可以使用官方推荐的插件:vite-plugin-pwa,支持离线缓存、自动注册 Service Worker、更新机制等。
yarn add vite-plugin-pwa -D
修改vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { VitePWA } from 'vite-plugin-pwa';export default defineConfig({plugins: [vue(),VitePWA({registerType: 'autoUpdate', // 自动更新 Service WorkerincludeAssets: ['favicon.svg', 'robots.txt'], // 可选:缓存额外资源manifest: {name: 'My Vue App',short_name: 'VueApp',description: '我的离线 Vue 应用',theme_color: '#ffffff',icons: [{src: 'pwa-192x192.png',sizes: '192x192',type: 'image/png',},{src: 'pwa-512x512.png',sizes: '512x512',type: 'image/png',},],},}),],
});//缓存public下所有文件
import { defineConfig } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'export default defineConfig({plugins: [VitePWA({registerType: 'autoUpdate',includeAssets: ['favicon.ico', 'robots.txt', '**/*'], // ✅ 显式包含 public 下所有文件workbox: {globPatterns: ['**/*.{js,css,html,ico,png,svg,json,txt,woff2}'], // ✅ 缓存这些后缀的文件runtimeCaching: [{urlPattern: /^\/.*\.(json|png|ico|svg|html|js|css|txt)$/,handler: 'CacheFirst',options: {cacheName: 'static-assets',expiration: {maxEntries: 1000,maxAgeSeconds: 60 * 60 * 24 * 30, // 缓存 30 天},},},],},}),],
})
配置项 | 用途 | 执行时机 | 作用范围 |
---|---|---|---|
includeAssets | 👇构建时复制到 dist/ ,并加入到 PWA 资源清单 | 构建时 | 主要针对 public/ 目录的资源 |
globPatterns | 👇构建时让 Workbox 自动缓存匹配的静态资源 | 构建时 | 所有 dist/ 中能匹配的文件 |
urlPattern | 👇运行时拦截某些请求并决定是否缓存、如何缓存 | 运行时(浏览器端) | 任意运行时资源(API、图片等) |
类型 | 示例资源 | 推荐用法 |
---|---|---|
静态资源 | /robots.txt 、favicon | ✅ includeAssets + globPatterns |
公共 JSON | /data/config.json | ✅ includeAssets or runtimeCaching |
接口数据 | /api/user/list | ✅ runtimeCaching.urlPattern |
图片资源 | /images/logo.png | ✅ globPatterns + runtimeCaching |
添加图标文件(放在 public/ 目录)
在入口文件注册 Service Worker: 如果使用 vite-plugin-pwa 的 autoUpdate,你不需要手动注册,插件会自动帮你做。
if ('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js');});
}
构建项目并部署,提示 “安装应用”,并具备 离线访问能力。
-
测试
打开浏览器访问你的项目
F12 打开开发者工具 → 应用(Application) → Service Workers
断网,再刷新页面,页面依然能打开,说明离线缓存成功
service worker只能在 HTTPS环境/localhost本地测试环境 中使用,否则浏览器会阻止
service worker的东西在cache storage里面
safari只能看是否开启,看不了存的内容
-
控制哪些资源缓存
使用 workbox 的 runtimeCaching 来配置路由缓存策略
VitePWA({workbox: {runtimeCaching: [{urlPattern: /^https:\/\/your-api\.com\/.*$/,handler: 'NetworkFirst',options: {cacheName: 'api-cache',},},],},
});
- 注意开发环境不会启用离线缓存,因为防止有缓存后调试困难
要测试离线功能,必须使用 build 后的生产版本。
npm install -g serve
serve dist#oryarn build # 构建生产代码
npx serve dist # 启动本地静态服务器(正式环境)
问题汇总
- Uncaught (in promise) SecurityError: Failed to register a ServiceWorker for scope (‘https://192.168.1.x:417x/’) with script (‘https://192.168.1.x:417x/sw.js’): An SSL certificate error occurred when fetching the script.
表示正在尝试在使用 自签名证书或无效 HTTPS 证书 的本地开发服务器上注册 Service Worker,而 浏览器出于安全原因阻止了这个请求。
- 解决方法 选其一
(1) localhost地址是ok的 或者
(2)使用受信任的证书:
如果你 必须通过 IP 地址 + HTTPS 访问,你需要:
为你的 IP 或本地域名(如 my-app.local)生成一个自签名证书;
将该证书导入到本地的「受信任根证书」;
配置 Vite 使用该证书。
这过程较复杂,通常不推荐在本地开发中这么做,除非你对证书比较熟悉。
1)安装
brew install mkcert
brew install nss # 如果你使用 Firefox
Windows:直接在 https://github.com/FiloSottile/mkcert 下载对应版本,解压到 PATH 中。
2)创建本地 CA(仅第一次需要)mkcert -install
这会自动生成并安装本地根证书到受信任列表中(支持 macOS、Windows、Linux)。
3)为 IP 地址创建证书(例如 192.168.1.4)
mkcert 192.168.1.4
会生成两个文件:
192.168.1.4.pem(证书)
192.168.1.4-key.pem(私钥)
4) 配置 Vite 使用这个证书
将文件重命名为更简洁的名字:
mv 192.168.1.4.pem server.crt
mv 192.168.1.4-key.pem server.key
在 vite.config.ts 中配置:
import { defineConfig } from 'vite';
import fs from 'fs';export default defineConfig({server: {https: {key: fs.readFileSync('./server.key'),cert: fs.readFileSync('./server.crt'),},host: '192.168.1.4',port: 4173}
});
现在可以访问:https://192.168.1.x:417x。不会有证书警告,且 Service Worker 能正常注册。
2. 桌面chrome的Service Worker 正常工作,安卓chrome还是没存上
手机端检查service worker,chrome://serviceworker-internals 已被废弃,故使用:在手机上打开 DevTools(推荐)
电脑和手机连接同一个 Wi-Fi
手机开启开发者模式 + USB 调试
用数据线连接电脑
电脑 Chrome 打开:
chrome://inspect/#devices
在手机 Chrome 中打开你要调试的网页
电脑端点击 inspect,就能打开该网页的 DevTools(像桌面一样调试)
切换到 DevTools 的 “Application” → “Service Workers” 面板查看注册情况
- An SSL certificate error occurred when fetching the script.了解此错误
index.html:1 Uncaught (in promise) SecurityError: Failed to register a ServiceWorker for scope (‘https://xx/’) with script (‘https://xx/sw.js’): An SSL certificate error occurred when fetching the script.
试图在 HTTPS 本地开发环境 注册 Service Worker,但浏览器因为 SSL 证书不受信任 而阻止了注册。
必须把证书 xx.pem 安装到手机上,安装后还要启用“信任用户证书”(安卓设置中),否则浏览器依然会报“SSL 证书错误”,Service Worker 无法注册。
1)准备证书(把 .pem 转为 .crt)安卓只识别 .crt 格式证书。在电脑上把 .pem 转为 .crt:打开终端,运行:cp 192.168.1.4.pem 192.168.1.4.crt
简单改扩展名,因为 mkcert 生成的 .pem 实际上就是 X.509 格式。
- 把证书传到手机
3)安装证书 不同厂商界面略有不同
- 安卓10
打开设置 → 安全 或 安全与隐私
选择:加密与凭据 或 更多安全设置
点击:从存储设备安装 或 安装证书
选择你刚才传进去的证书
选择安装为:【VPN 和应用程序(用户)】
手机提示“已安装证书”,说明成功。
- 开启信任用户证书(某些安卓必须)
部分安卓系统(尤其是 Android 11 及以上)默认不信任用户安装的证书,你需要手动开启:
打开浏览器输入 chrome://flags,搜索 “insecure origins treated as secure”
或手动启用:设置 → 关于手机 → 连续点击版本号 7 次,开启开发者模式/【开发者选项】
找到:信任用户 CA 证书或类似选项:允许应用信任用户 CA
⚠️ 注意:不是所有 ROM 都提供这个选项,如果你的浏览器(尤其是 Chrome)仍然不信任用户证书,就只能部署到支持 HTTPS 的远程服务器(如 Vercel)。
5) 验证是否成功
打开手机浏览器,访问,看地址栏是否出现绿色 🔒 或无警告
打开 DevTools 调试(用 chrome://inspect)查看 Service Worker 是否正常注册
- 在安卓中提示“需要提供私钥才能安装证书”。
用adb安装到系统证书 或用openssl转换一下再安装
你导入的是 客户端证书(Client Certificate),而系统安装时需要:
公钥 + 私钥,并且要打包成 .p12(或 .pfx)格式。
解决办法:生成 .p12 文件(含私钥)
你需要用 openssl 将你的公钥和私钥打包成一个 .p12 文件,再传到文石设备上安装。
所需材料:一个 .crt(或 .pem)文件:公钥证书,一个 .key 文件:私钥文件(必须你自己持有)
#打包命令
openssl pkcs12 -export \-in client.crt \-inkey client.key \-out client.p12 \-name "my-cert"
系统会要求你设置一个导入密码(可以设置一个简单密码)设备安装时要求输入
5. 上述做完,安卓chrome还是不信任该网站
- 使用vite-plugin-mkcert
这是一个 Vite 插件,用于:
为你的本地开发服务器(vite dev)启用 HTTPS
自动创建并安装受信任的本地开发证书(通过 mkcert)
这对在 移动设备(如文石)上访问你的本地开发页面 非常重要,因为:
移动端通常拒绝连接未信任的 HTTP 或自签名 HTTPS,如果你配置了 mkcert,浏览器就可以直接信任你的本地 HTTPS 网站 - 还是不行,试一下:把证书放进系统 CA 目录
mkcert 安装时会自动创建一个「根证书(CA)」用于签发你本地开发用的 HTTPS 证书。这个根证书是你要导入安卓系统 CA 的那个证书,mkcert -CAROOT
找到位置
没root的将rootCA.pem发送到手机,直接安装成CA证书,成功让chrome信任了,解决问题✅
openssl x509 -inform PEM -subject_hash_old -in rootCA.pem | head -1
生成一串字符串,记住它,后面加上.0
作为新文件的名称
cp rootCA.pem 新文件名称
形如cp rootCA.pem 119xxxxa.0
通过adb命令放入已root的设备