1、检查到有新版本 弹框提示 确认更新 取消不更新
vite.config.ts
...
// 引入 VitePWA 插件
import { VitePWA } from "vite-plugin-pwa"
export default defineConfig((mode): any => {
return {
...
plugins: [
VitePWA({
// 注册 Service Worker 的方式
// registerType: "prompt", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)
registerType: "autoUpdate", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)
// 要包含到 PWA 的额外静态资源
includeAssets: [
"favicon.svg", // 网站的图标
"robots.txt", // robots.txt 文件,用于搜索引擎优化
"apple-touch-icon.png", // iOS 特定的桌面图标
"**/*.png",
"**/*.jpg",
"**/*.jpeg",
"**/*.svg",
"**/*.woff2",
"**/*.woff",
"**/*.ttf",
"**/*.otf"
],
// // 用于精细地控制服务工作者中缓存和离线功能
workbox: {
globPatterns: ["**/*.{js,css,html,png,jpg,gif,svg,woff,ttf,otf,woff2}"],
runtimeCaching: [
{
urlPattern: /\.(png|jpg|jpeg|svg|gif|woff|ttf|otf|woff2)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "CacheFirst" // 优先从缓存中查找响应,如果缓存中存在相应资源,就直接使用缓存内容返回给客户端,这样能实现快速响应,提升页面加载速度
},
{
urlPattern: /\.(js|css|html)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "StaleWhileRevalidate" // 虽然一开始返回给客户端的是缓存中的旧资源,但它会在后台持续尝试更新缓存,只要网络请求成功,缓存中的资源就能尽快更新为最新版本,相对来说能让客户端在较短时间内获取到更新后的资源,保证资源的新鲜度在一个合理的时间范围内不断更新,更适用于那些内容有一定更新频率但又允许短暂使用旧内容的场景。
}
},
// 配置 Web App Manifest
manifest: {
name: "web App", // 应用的全称,显示在安装提示或加载界面
short_name: "App", // 应用的短名称,显示在桌面图标下方
description: "web App built with Vite", // 应用的描述信息
theme_color: "#3367D6", // PWA 的主题颜色,用于地址栏、通知栏等
background_color: "#ffffff", // 启动页面的背景颜色
display: "standalone" // 显示模式:standalone 模式模拟原生应用
},
// 开发环境的特定配置
devOptions: {
enabled: true, // 启用 PWA 功能,方便开发测试
type: "classic", // 使用标准的 Service Worker 格式
navigateFallback: "index.html" // 未命中的路由返回的默认页面
}
})
],
...
})
main.ts
import { registerSW } from 'virtual:pwa-register';
const updateSW = registerSW({
onNeedRefresh() {
console.log('新版本可用,请刷新页面以更新。');
const userConfirmed = confirm('检测到新版本,是否立即刷新以应用更新?');
if (userConfirmed) {
updateSW(); // 激活新版本
}
},
onOfflineReady() {
console.log('应用已准备好离线使用。');
}
});
2.1、在下一路由切换时更新缓存版本(vite-plugin-pwa 虚拟模块 virtual:pwa-register/vue 版本)
注意:页面刷新会造成 needRefresh 为false,所以在安装完缓存后,点击路由前,刷新页面了,再去点击路由,是不会更新缓存的,如果先避免这个问题,可以使用2.2中的原生PAI编写的方法
router.ts
import { useRegisterSW } from "virtual:pwa-register/vue"
const {
needRefresh, // 是否需要刷新
// offlineReady, // 是否已准备好离线使用
updateServiceWorker // 手动更新 Service Worker
} = useRegisterSW()
// 后置守卫,保证每次路由跳转结束时关闭进度条
router.afterEach(() => {
// 更新pwa缓存
if (needRefresh.value) {
updateServiceWorker()
}
})
vite.config.ts
...
// 引入 VitePWA 插件
import { VitePWA } from "vite-plugin-pwa"
export default defineConfig((mode): any => {
return {
...
plugins: [
VitePWA({
// 注册 Service Worker 的方式
registerType: "prompt", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)
// registerType: "autoUpdate", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)
// 要包含到 PWA 的额外静态资源
includeAssets: [
"favicon.svg", // 网站的图标
"robots.txt", // robots.txt 文件,用于搜索引擎优化
"apple-touch-icon.png", // iOS 特定的桌面图标
"**/*.png",
"**/*.jpg",
"**/*.jpeg",
"**/*.svg",
"**/*.woff2",
"**/*.woff",
"**/*.ttf",
"**/*.otf"
],
// // 用于精细地控制服务工作者中缓存和离线功能
workbox: {
globPatterns: ["**/*.{js,css,html,png,jpg,gif,svg,woff,ttf,otf,woff2}"],
runtimeCaching: [
{
urlPattern: /\.(png|jpg|jpeg|svg|gif|woff|ttf|otf|woff2)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "CacheFirst" // 优先从缓存中查找响应,如果缓存中存在相应资源,就直接使用缓存内容返回给客户端,这样能实现快速响应,提升页面加载速度
},
{
urlPattern: /\.(js|css|html)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "StaleWhileRevalidate" // 虽然一开始返回给客户端的是缓存中的旧资源,但它会在后台持续尝试更新缓存,只要网络请求成功,缓存中的资源就能尽快更新为最新版本,相对来说能让客户端在较短时间内获取到更新后的资源,保证资源的新鲜度在一个合理的时间范围内不断更新,更适用于那些内容有一定更新频率但又允许短暂使用旧内容的场景。
}
skipWaiting: false, // 不跳过等待
clientsClaim: true // 激活后是否主动接管页面
},
// 配置 Web App Manifest
manifest: {
name: "web App", // 应用的全称,显示在安装提示或加载界面
short_name: "App", // 应用的短名称,显示在桌面图标下方
description: "web App built with Vite", // 应用的描述信息
theme_color: "#3367D6", // PWA 的主题颜色,用于地址栏、通知栏等
background_color: "#ffffff", // 启动页面的背景颜色
display: "standalone" // 显示模式:standalone 模式模拟原生应用
},
// 开发环境的特定配置
devOptions: {
enabled: true, // 启用 PWA 功能,方便开发测试
type: "classic", // 使用标准的 Service Worker 格式
navigateFallback: "index.html" // 未命中的路由返回的默认页面
}
})
],
...
})
2.2、在下一路由切换时更新缓存版本(操作原生service worker API 版本)
/utils/pwa.ts
let channel: any = {}
// 检查浏览器是否支持 BroadcastChannel API
if (typeof BroadcastChannel !== "undefined") {
// 创建一个广播频道
channel = new BroadcastChannel("pwa-update-channel")
// 在其他标签页中监听广播消息,触发刷新
channel?.addEventListener("message", (event) => {
if (event.data === "refresh") {
window.location.reload() // 刷新页面
}
})
} else {
console.warn("BroadcastChannel API is not supported in this browser.")
}
export function initPwa() {
localStorage.setItem("newWorkerState", "installed")
if ("serviceWorker" in navigator) {
const registerFilePath = import.meta.env.MODE == "dev" ? "/dev-sw.js?dev-sw" : "/sw.js"
// 注册service worker
navigator.serviceWorker.register(registerFilePath).then((registration) => {
console.log("updatePwa ----- Service Worker registered with scope3:", registration) // 监听 waiting 状态的 Service Worker
registration.addEventListener("updatefound", () => {
const newWorker = registration.installing
if (newWorker) {
newWorker.addEventListener("statechange", () => {
if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
// 新版本已安装,但未激活
if (localStorage.getItem("newWorkerState") !== "installed") {
localStorage.setItem("newWorkerState", "installed")
}
}
if (newWorker.state === "activated") {
pwaActivate()
}
})
}
})
})
pwaActiveWatch()
}
}
export function pwaPostMessage() {
if ("serviceWorker" in navigator) {
// 获取当前service worker
navigator.serviceWorker.getRegistration().then((registration: any) => {
console.log("pwaPostMessage --- Service Worker registered with scope:", registration) // 监听 waiting 状态的 Service Worker
if (localStorage.getItem("newWorkerState") === "installed") {
// 手动激活新 Service Worker
const newWorker = registration.waiting
newWorker && newWorker.postMessage({ type: "SKIP_WAITING" })
localStorage.setItem("newWorkerState", "")
}
})
}
}
// 需要vite-plugin-pwa插件配置 registerType: "autoUpdate" 是才可触发此事件
export function pwaActiveWatch() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("controllerchange", () => {
console.log("检测到新版本,页面将重新加载!")
pwaActivate()
})
}
}
// 激活广播
export function pwaActivate() {
localStorage.setItem("testNewWorker1", "activated1")
// 新版本激活刷新页面
// 广播消息,通知其他标签页刷新
try {
channel?.postMessage("refresh")
} catch (error) {
localStorage.setItem("testNewWorker1", "error")
}
localStorage.setItem("testNewWorker2", "activated2")
window.location.reload()
localStorage.setItem("testNewWorker3", "activated3")
}
main.ts
...
import { initPwa } from "@/utils/pwa"
initPwa()
...
router.ts
// 后置守卫,保证每次路由跳转结束时关闭进度条
router.afterEach(() => {
// 更新pwa缓存
pwaPostMessage()
})
vite.config.ts
...
// 引入 VitePWA 插件
import { VitePWA } from "vite-plugin-pwa"
export default defineConfig((mode): any => {
return {
...
plugins: [
VitePWA({
// 注册 Service Worker 的方式
registerType: "prompt", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)
// registerType: "autoUpdate", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)
// 要包含到 PWA 的额外静态资源
includeAssets: [
"favicon.svg", // 网站的图标
"robots.txt", // robots.txt 文件,用于搜索引擎优化
"apple-touch-icon.png", // iOS 特定的桌面图标
"**/*.png",
"**/*.jpg",
"**/*.jpeg",
"**/*.svg",
"**/*.woff2",
"**/*.woff",
"**/*.ttf",
"**/*.otf"
],
// // 用于精细地控制服务工作者中缓存和离线功能
workbox: {
globPatterns: ["**/*.{js,css,html,png,jpg,gif,svg,woff,ttf,otf,woff2}"],
runtimeCaching: [
{
urlPattern: /\.(png|jpg|jpeg|svg|gif|woff|ttf|otf|woff2)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "CacheFirst" // 优先从缓存中查找响应,如果缓存中存在相应资源,就直接使用缓存内容返回给客户端,这样能实现快速响应,提升页面加载速度
},
{
urlPattern: /\.(js|css|html)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "StaleWhileRevalidate" // 虽然一开始返回给客户端的是缓存中的旧资源,但它会在后台持续尝试更新缓存,只要网络请求成功,缓存中的资源就能尽快更新为最新版本,相对来说能让客户端在较短时间内获取到更新后的资源,保证资源的新鲜度在一个合理的时间范围内不断更新,更适用于那些内容有一定更新频率但又允许短暂使用旧内容的场景。
}
skipWaiting: false, // 不跳过等待
clientsClaim: true // 激活后是否主动接管页面
},
// 配置 Web App Manifest
manifest: {
name: "web App", // 应用的全称,显示在安装提示或加载界面
short_name: "App", // 应用的短名称,显示在桌面图标下方
description: "web App built with Vite", // 应用的描述信息
theme_color: "#3367D6", // PWA 的主题颜色,用于地址栏、通知栏等
background_color: "#ffffff", // 启动页面的背景颜色
display: "standalone" // 显示模式:standalone 模式模拟原生应用
},
// 开发环境的特定配置
devOptions: {
enabled: true, // 启用 PWA 功能,方便开发测试
type: "classic", // 使用标准的 Service Worker 格式
navigateFallback: "index.html" // 未命中的路由返回的默认页面
}
})
],
...
})
3、在检测更新下载缓存资源的时候,弹出加载资源的提示框,到缓存完成立即更新
/utils/pwa.ts
let channel: any = {}
// 检查浏览器是否支持 BroadcastChannel API
if (typeof BroadcastChannel !== "undefined") {
// 创建一个广播频道
channel = new BroadcastChannel("pwa-update-channel")
// 在其他标签页中监听广播消息,触发刷新
channel?.addEventListener("message", (event) => {
if (event.data === "refresh") {
window.location.reload() // 刷新页面
}
})
} else {
console.warn("BroadcastChannel API is not supported in this browser.")
}
// 派发PWA下载开始事件
function dispatchDownloadStartEvent() {
window.dispatchEvent(new CustomEvent("pwa-download-start"))
localStorage.setItem("testNewWorkerDispatchDownloadStartEvent", "down")
}
// 派发PWA下载完成事件
function dispatchDownloadCompleteEvent() {
window.dispatchEvent(new CustomEvent("pwa-download-complete"))
localStorage.setItem("testNewWorkerDispatchDownloadCompleteEvent", "complete")
}
export function initPwa() {
if (!process.env.VUE_APP_PWA) {
return
}
// 检查是否有下载标记,如果有则派发下载开始事件
if (sessionStorage.getItem("pwa_downloading") === "true") {
dispatchDownloadStartEvent()
}
localStorage.setItem("newWorkerState", "installed")
if ("serviceWorker" in navigator) {
const registerFilePath = import.meta.env.MODE == "dev" ? "/dev-sw.js?dev-sw" : "/sw.js"
// 注册service worker
navigator.serviceWorker.register(registerFilePath).then((registration) => {
console.log("updatePwa ----- Service Worker registered with scope3:", registration) // 监听 waiting 状态的 Service Worker
registration.addEventListener("updatefound", () => {
const newWorker = registration.installing
if (newWorker) {
// 当新的 Service Worker 开始安装时,触发下载开始事件
dispatchDownloadStartEvent()
sessionStorage.setItem("pwa_downloading", "true")
newWorker.addEventListener("statechange", () => {
if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
// 新版本已安装,但未激活
if (localStorage.getItem("newWorkerState") !== "installed") {
localStorage.setItem("newWorkerState", "installed")
}
}
if (newWorker.state === "activated") {
// 当 Service Worker 激活完成,触发下载完成事件
dispatchDownloadCompleteEvent()
sessionStorage.setItem("pwa_downloading", "false")
pwaActivate()
}
})
}
})
})
pwaActiveWatch()
}
}
export function pwaPostMessage() {
if (!process.env.VUE_APP_PWA) {
return
}
if ("serviceWorker" in navigator) {
// 获取当前service worker
navigator.serviceWorker.getRegistration().then((registration: any) => {
console.log("pwaPostMessage --- Service Worker registered with scope:", registration) // 监听 waiting 状态的 Service Worker
if (localStorage.getItem("newWorkerState") === "installed") {
// 手动激活新 Service Worker
const newWorker = registration?.waiting
newWorker && newWorker.postMessage({ type: "SKIP_WAITING" })
localStorage.setItem("newWorkerState", "")
}
})
}
}
// 需要vite-plugin-pwa插件配置 registerType: "autoUpdate" 是才可触发此事件
export function pwaActiveWatch() {
if (!process.env.VUE_APP_PWA) {
return
}
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("controllerchange", () => {
console.log("检测到新版本,页面将重新加载!")
pwaActivate()
})
}
}
// 激活广播
export function pwaActivate() {
if (!process.env.VUE_APP_PWA) {
return
}
localStorage.setItem("testNewWorker1", "activated1")
// 新版本激活刷新页面
// 广播消息,通知其他标签页刷新
try {
channel?.postMessage("refresh")
} catch (error) {
localStorage.setItem("testNewWorker1", "error")
}
localStorage.setItem("testNewWorker2", "activated2")
// 在刷新页面前设置下载完成标志
dispatchDownloadCompleteEvent()
sessionStorage.setItem("pwa_downloading", "false")
window.location.reload()
localStorage.setItem("testNewWorker3", "activated3")
}
main.ts
...
import { initPwa } from "@/utils/pwa"
initPwa()
...
router.ts
// 后置守卫,保证每次路由跳转结束时关闭进度条
router.afterEach(() => {
// 更新pwa缓存
pwaPostMessage()
})
vite.config.ts
...
// 引入 VitePWA 插件
import { VitePWA } from "vite-plugin-pwa"
export default defineConfig((mode): any => {
return {
...
plugins: [
VitePWA({
// 注册 Service Worker 的方式
registerType: "prompt", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)autoUpdate
// registerType: "autoUpdate", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)
// 要包含到 PWA 的额外静态资源
includeAssets: [
"favicon.svg", // 网站的图标
"robots.txt", // robots.txt 文件,用于搜索引擎优化
"apple-touch-icon.png", // iOS 特定的桌面图标
"**/*.png",
"**/*.jpg",
"**/*.jpeg",
"**/*.svg",
"**/*.woff2",
"**/*.woff",
"**/*.ttf",
"**/*.otf"
],
// // 用于精细地控制服务工作者中缓存和离线功能
workbox: {
globPatterns: ["**/*.{js,css,html,png,jpg,gif,svg,woff,ttf,otf,woff2}"],
runtimeCaching: [
{
urlPattern: /\.(png|jpg|jpeg|svg|gif|woff|ttf|otf|woff2)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "CacheFirst" // 优先从缓存中查找响应,如果缓存中存在相应资源,就直接使用缓存内容返回给客户端,这样能实现快速响应,提升页面加载速度
},
{
urlPattern: /\.(js|css|html)$/, // 匹配图片文件请求
// handler: 'NetworkOnly', // 始终只通过网络请求获取数据,不会去使用缓存中的内容
handler: "StaleWhileRevalidate" // 虽然一开始返回给客户端的是缓存中的旧资源,但它会在后台持续尝试更新缓存,只要网络请求成功,缓存中的资源就能尽快更新为最新版本,相对来说能让客户端在较短时间内获取到更新后的资源,保证资源的新鲜度在一个合理的时间范围内不断更新,更适用于那些内容有一定更新频率但又允许短暂使用旧内容的场景。
}
// {
// urlPattern: /.*\.(?:js|css|html|json)/, // 缓存静态资源
// handler: "CacheFirst", // 使用缓存优先策略
// options: {
// cacheName: "static-cache", // 设置缓存名称
// expiration: {
// maxEntries: 2000, // 最大缓存条目数
// maxAgeSeconds: 60 * 60 * 24 * 7 // 缓存过期时间(30天)
// }
// }
// }
],
skipWaiting: true, // 不跳过等待
clientsClaim: true // 激活后是否主动接管页面
},
// 配置 Web App Manifest
manifest: {
name: "web App", // 应用的全称,显示在安装提示或加载界面
short_name: "App", // 应用的短名称,显示在桌面图标下方
description: "web App built with Vite", // 应用的描述信息
theme_color: "#3367D6", // PWA 的主题颜色,用于地址栏、通知栏等
background_color: "#ffffff", // 启动页面的背景颜色
display: "standalone" // 显示模式:standalone 模式模拟原生应用
},
// 开发环境的特定配置
devOptions: {
enabled: isRunPWA, // 启用 PWA 功能,方便开发测试
type: "classic", // 使用标准的 Service Worker 格式
navigateFallback: "index.html" // 未命中的路由返回的默认页面
}
}),
]
...
})
提示弹框vue 文件
<template>
<CommonDialog
v-model:commonVisible="dialogVisible"
:dialogProps="{
title: '系统更新中',
width: '400px',
closeOnClickModal: false,
closeOnPressEscape: false,
showClose: false
}"
>
<div class="pwa-download-content">
<div class="icon-wrapper">
<el-icon class="loading-icon"><Loading /></el-icon>
</div>
<div class="message">
<p>系统资源正在下载中,预计1~2分钟。请耐心等待...</p>
<p>此过程不可中断,请勿关闭或刷新页面</p>
</div>
</div>
<template #footer>
<div class="download-footer">
<p class="footer-tip">资源下载完成后会自动关闭此窗口</p>
</div>
</template>
</CommonDialog>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from "vue"
import { useRoute, useRouter } from "vue-router"
const dialogVisible = ref(false)
const route = useRoute()
const router = useRouter()
// 检查是否有下载标记
const checkDownloadStatus = () => {
const isDownloading = sessionStorage.getItem("pwa_downloading")
dialogVisible.value = isDownloading === "true"
}
// 监听路由变化,确保在任何页面都显示弹框
watch(
() => route.path,
() => {
checkDownloadStatus()
}
)
onMounted(() => {
checkDownloadStatus()
// 监听自定义事件,用于打开/关闭对话框
window.addEventListener("pwa-download-start", () => {
sessionStorage.setItem("pwa_downloading", "true")
dialogVisible.value = true
})
window.addEventListener("pwa-download-complete", () => {
sessionStorage.setItem("pwa_downloading", "false")
dialogVisible.value = false
})
})
</script>
<style scoped lang="scss">
.pwa-download-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
.icon-wrapper {
margin-bottom: 20px;
.loading-icon {
font-size: 48px;
color: #409eff;
animation: rotate 2s linear infinite;
}
}
.message {
text-align: center;
p {
margin: 8px 0;
color: #606266;
font-size: 14px;
line-height: 1.5;
}
}
}
.download-footer {
width: 100%;
text-align: center;
.footer-tip {
color: #909399;
font-size: 12px;
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
4、如何卸载pwa
是的没有看错就是卸载,我们不否认 pwa的强大,但是 PC端使用的pwa 缺少了web 刷新页面立即更新的优点, 造成我们重要版本更新不及时的问题。建议还是用在app内嵌的H5 更加的完美。
main.ts
import { cleanupPWA } from "@/utils/cleanPwa"
// 只在生产环境且有新版本时清理 PWA 缓存
const currentVersion = process.env.VUE_APP_DATETIME
const lastVersion = localStorage.getItem("app_version")
if (import.meta.env.PROD && currentVersion && lastVersion !== currentVersion) {
cleanupPWA().then(() => {
console.log("PWA 缓存清理完成")
localStorage.setItem("app_version", currentVersion)
})
}
// 初始化init
// initPwa()
cleanPwa.ts
export async function cleanupPWA() {
try {
console.log("=== PWA 清理开始 ===")
// 检查是否已经在清理中或已清理完成
const cleaningStatus = sessionStorage.getItem("pwa_cleaning_status")
if (cleaningStatus === "completed") {
console.log("本次会话已完成 PWA 清理,无需重复执行")
return {
success: true,
message: "本次会话已完成 PWA 清理"
}
}
if (cleaningStatus === "in_progress") {
console.log("PWA 清理正在进行中,请勿重复执行")
return {
success: true,
message: "PWA 清理正在进行中"
}
}
// 标记清理状态为进行中
sessionStorage.setItem("pwa_cleaning_status", "in_progress")
// 1. 只注销 PWA 相关的 Service Worker
if ("serviceWorker" in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations()
for (const registration of registrations) {
// 只注销 PWA 相关的 Service Worker
if (registration.scope.includes("/sw.js") || registration.scope.includes("/dev-sw.js")) {
await registration.unregister()
}
}
console.log("PWA Service Workers 已成功注销")
}
// 2. 只清除 PWA 相关的缓存
if ("caches" in window) {
const cacheKeys = await caches.keys()
const pwaCacheKeys = cacheKeys.filter(
(key) =>
key.includes("workbox-") || // Workbox 缓存
key.includes("sw-") || // Service Worker 缓存
key.includes("pwa-") // 其他 PWA 相关缓存
)
await Promise.all(pwaCacheKeys.map((key) => caches.delete(key)))
console.log("PWA 缓存已成功清除")
}
// 3. 清除 PWA 相关的存储数据
const clearStorageData = () => {
// 清除 localStorage 中的 PWA 相关数据
const pwaKeys = [
"newWorkerState",
"testNewWorker1",
"testNewWorker2",
"testNewWorker3",
"testNewWorkerDispatchDownloadStartEvent",
"testNewWorkerDispatchDownloadCompleteEvent"
]
pwaKeys.forEach((key) => localStorage.removeItem(key))
// 清除 sessionStorage 中的 PWA 相关数据
sessionStorage.removeItem("pwa_downloading")
// 只清除 PWA 相关的 IndexedDB 数据库
const deleteDatabase = (dbName) => {
return new Promise((resolve) => {
const request = indexedDB.deleteDatabase(dbName)
request.onsuccess = () => resolve(true)
request.onerror = () => resolve(false)
})
}
// 只删除 PWA 相关的数据库
return Promise.all([
deleteDatabase("workbox-precache"),
deleteDatabase("workbox-runtime"),
deleteDatabase("workbox-background-sync")
])
}
await clearStorageData()
console.log("PWA 相关的存储数据已清除")
// 4. 移除 manifest 链接
const manifestLink = document.querySelector('link[rel="manifest"]')
if (manifestLink) {
manifestLink.remove()
}
// 5. 添加 no-cache 头部,仅针对 Service Worker
const meta = document.createElement("meta")
meta.httpEquiv = "Service-Worker-Allowed"
meta.content = "none"
document.head.appendChild(meta)
console.log("PWA 清理完成")
// 标记清理完成
sessionStorage.setItem("pwa_cleaning_status", "completed")
// 只在首次清理完成时刷新页面
if (!sessionStorage.getItem("pwa_refresh_done")) {
sessionStorage.setItem("pwa_refresh_done", "true")
setTimeout(() => {
window.location.reload()
}, 100)
}
return {
success: true,
message: "PWA 清理完成"
}
} catch (error) {
console.error("清理 PWA 时发生错误:", error)
return {
success: false,
message: "清理 PWA 时发生错误"
}
}
}
router
// 后置守卫,保证每次路由跳转结束时关闭进度条
router.afterEach(() => {
// 更新pwa缓存
// pwaPostMessage()
})
vite.config.ts
import { VitePWA } from "vite-plugin-pwa"
export default defineConfig((mode): any => {
return {
...
plugins: [
VitePWA({
// 禁用 PWA 功能
// disable: true,
// 注册 Service Worker 的方式
registerType: "prompt", // 自动更新 Service Worker 并刷新页面(避免用户使用旧版本)autoUpdate
// 配置 Web App Manifest
manifest: false, // 禁用 manifest
workbox: {
// 清空全局匹配模式,不缓存任何静态资源
globPatterns: [],
// 配置运行时缓存策略
runtimeCaching: [
{
urlPattern: /\.(gif)$/i,
handler: "NetworkOnly", // 只使用网络请求,不使用缓存
options: {
cacheName: "static-assets"
}
}
],
navigateFallback: null,
skipWaiting: true, // 新 Service Worker 安装后立即激活
clientsClaim: true, // 立即控制所有客户端
navigationPreload: false, // 关闭导航预加载 去掉service worker的 代理请求
cleanupOutdatedCaches: true // 自动清理过期缓存
},
// 开发环境的特定配置
devOptions: {
enabled: isRunPWA // 禁用开发环境的 PWA
}
}),
]
...
})
看完以后肯定会疑问,为什么这么复杂? 因为 上线的项目已被缓存到客户端电脑的本地。 如果第二次直接关闭PWA ,那么线上的PWA不更新, 最新版本的静态资源就会一直不更新。 所以需要先处理资源不更新的问题 发一版,第三次发版再删除PWA!!!