【前端】PWA
目录
- 概述
- 实战
- 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,而 浏览器出于安全原因阻止了这个请求。
- 解决方法
- localhost地址是ok的
- 使用受信任的证书
如果你 必须通过 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 能正常注册。