vite + vue3 实现 PWA
步骤
- 第一步:首先保证 vue3-vite 项目已经被创建并完成依赖安装
- 第二步:开始安装 PWA 插件
shell
pnpm add vite-plugin-pwa workbox-build workbox-precaching workbox-window -D- 第三步:编写 public/service-worker.js SW核心脚本
js
// 发版必须升级版本号!!!
const CACHE_VERSION = "pwa-v1.0.0";
const CACHE_NAME = CACHE_VERSION;
// 缓存白名单(首页 + 静态资源)
const CACHE_ASSETS = ["/", "/index.html"];
// =======================================
// 安装:缓存静态资源
// =======================================
self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) => cache.addAll(CACHE_ASSETS))
.then(() => self.skipWaiting())
);
});
// =======================================
// 激活:清理旧版本缓存
// =======================================
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((keys) => {
return Promise.all(
keys
.filter((key) => key !== CACHE_NAME)
.map((key) => caches.delete(key))
);
})
.then(() => self.clients.claim())
);
});
// =======================================
// 拦截请求:缓存策略
// =======================================
self.addEventListener("fetch", (event) => {
const request = event.request;
const url = new URL(request.url);
// 只处理同源请求
if (url.origin !== self.origin) return;
// 接口 → 不走缓存,只走网络(保证数据最新)
if (request.url.includes("/api/")) {
event.respondWith(fetch(request));
return;
}
// 静态资源 → 缓存优先
event.respondWith(
caches.match(request).then((cacheRes) => cacheRes || fetch(request))
);
});
// =======================================
// 接收页面消息:立即更新
// =======================================
self.addEventListener("message", (event) => {
if (event.data === "refresh") {
self.skipWaiting();
}
});第四步:创建 public/logo.png 桌面图片
第五步:创建 public/manifest.webmanifest 内容如下
js
{
"name": "管理系统",
"short_name": "后台",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#409EFF",
"icons": [
{
"src": "logo.png",
"sizes": "192x192",
"type": "image/png"
}
]
}- 第六步:修改 vite.config.js
js
import { VitePWA } from "vite-plugin-pwa";
plugins: [
vue(),
VitePWA({
// 关键:使用自定义 service-worker
strategies: "injectManifest",
srcDir: "public",
filename: "service-worker.js",
// 关闭自动生成 manifest(我们自己写)
manifest: false,
// 开发环境也能调试 PWA
devOptions: {
enabled: true,
type: "module",
},
}),
]- 第七步:在 main.js 中初始化,在 app.mount("#app"); 下面追加
js
async function initPWA() {
if ("serviceWorker" in navigator) {
const { registerSW } = await import("virtual:pwa-register");
const updateSW = registerSW({
onNeedRefresh() {
// 发版了!提示用户更新
if (confirm("系统有新版本,是否立即更新?")) {
updateSW(true);
}
},
onOfflineReady() {
console.log("✅ 离线功能已启用");
},
});
}
}
// 初始化 PWA
initPWA();- 第八步:生产环境 nginx 配置
shell
server {
listen 80;
server_name 你的域名.com;
# 自动跳 HTTPS(正式环境强烈建议开)
# return 301 https://$host$request_uri;
root /usr/share/nginx/html; # 改成你 dist 上传的路径
index index.html index.htm;
# ==========================================
# 1. 解决 Vue 刷新 404
# ==========================================
location / {
try_files $uri $uri/ /index.html;
expires -1; # 页面不缓存
}
# ==========================================
# 2. PWA 核心:service-worker 绝对不缓存
# ==========================================
location /service-worker.js {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
add_header Content-Type "application/javascript";
try_files $uri $uri/ /index.html;
}
# ==========================================
# 3. manifest 不缓存(PWA 桌面图标配置)
# ==========================================
location /manifest.webmanifest {
expires -1;
add_header Cache-Control "no-store";
add_header Content-Type "application/manifest+json";
}
# ==========================================
# 4. 静态资源缓存(js/css/img/fonts 等)
# ==========================================
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 7d; # 缓存 7 天
add_header Cache-Control "public, max-age=604800, immutable";
access_log off;
}
# ==========================================
# 5. 接口反向代理(/api/* 转发到后端)
# ==========================================
location /api/ {
proxy_pass http://你的后端IP:端口/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Cache-Control "no-store"; # 接口绝不缓存
}
# ==========================================
# 6. 禁止访问隐藏文件
# ==========================================
location ~ /\. {
deny all;
}
# ==========================================
# 7. 日志
# ==========================================
access_log /var/log/nginx/your-project-access.log;
error_log /var/log/nginx/your-project-error.log;
}
# -------------------------------------------
# HTTPS 版本(正式环境必须用,PWA 推荐 HTTPS)
# -------------------------------------------
# server {
# listen 443 ssl http2;
# server_name 你的域名.com;
# ssl_certificate /etc/nginx/ssl/你的证书.pem;
# ssl_certificate_key /etc/nginx/ssl/你的密钥.key;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256;
# ssl_prefer_server_ciphers on;
# root /usr/share/nginx/html;
# index index.html;
# 下面配置和上面 80 端口完全一样 ……
# }测试是否成功
- 启动开发服务 -> 打开 Chrome F12 → Application [Service Workers → 看到 registered /activated/running,Cache Storage → 看到缓存文件] →
- F12 → Network → 勾选 Offline → 刷新页面 → 页面依然能显示 = PWA 成功
- chrome 浏览器右上方有导出图标 → 点击打开 → 沉浸式APP
说明
shell
# vite.config.ts 配置
开启 service-worker 自定义
开启 manifest 自定义
开启 开发环境能调试 PWA
# public/manifest.webmanifest
桌面图标配置
# public/service-worker.js
核心缓存逻辑
# index.html
引入图标配置
# main.ts
注册 PWA + 自动更新提示(最重要)关键
shell
1. 每次发版必须升级 CACHE_VERSION,例如 pwa-v1.0.1
2. service-worker.js 不能被服务器缓存,需要配置 nginx
3. 线上环境必须使用 HTTPS