Vue.js PWA中运行的Service Worker无法接收消息及路径问题
问题背景
首次开发VueJS PWA,已完成基础Service Worker配置:Service Worker已激活运行,应用可正常安装,但存在两个问题:
1. 核心问题
使用Chrome开发者工具推送消息时,消息无法送达Service Worker;点击更新按钮无响应。仅极少数修改代码后刷新浏览器的场景下,调试断点才可能触发,无法确定具体原因。
2. 次要问题
service-worker-dev.js存放于src文件夹时,虽会被复制并修改至dist文件夹,但浏览器无法加载该文件;移至public文件夹后才能正常访问。
相关配置代码
vue.config.js
module.exports = { transpileDependencies: [ 'vuetify' ], pwa: { name: "MyWebApp", themeColor: "#42b983", msTileColor: "#42b983", appleMobileWebAppCache: "yes", favicon32: 'img/icons/favicon-32x32.png', favicon16: 'img/icons/favicon-16x16.png', appleTouchIcon: 'img/icons/apple-touch-icon-152x152.png', maskIcon: 'img/icons/safari-pinned-tab.svg', msTileImage: 'img/icons/msapplication-icon-144x144.png', manifestOptions: { name: "MyWebApp", short_name: "MyWebApp", background_color: "#42b983", theme_color: "#4DBA87", display: "standalone", start_url: "." }, workboxPluginMode: "InjectManifest", workboxOptions: { swSrc: "src/service-worker-dev.js" } } }
main.js
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) import Home from './components/Home.vue' import Contact from './components/Contact.vue' import App from './App.vue' import vuetify from './plugins/vuetify' import "./registerServiceWorker"; const router = new VueRouter({ mode: 'history', base: __dirname, routes: [ { path: '/', component: Home }, { path: '/:userId/:commentId', component: Home }, { path: '/contact', component: Contact }, ] }); Vue.config.productionTip = false; new Vue({ router, vuetify, render: h => h(App), }).$mount('#app')
registerServiceWorker.js
/* eslint-disable no-console */ import { register } from 'register-service-worker' register(`./service-worker-dev.js`, { ready() { console.log( 'App is being served from cache by a service worker.\n' ); }, registered() { console.log('Service worker has been registered.'); }, cached() { console.log('Content has been cached for offline use.'); }, updatefound() { console.log('New content is downloading.'); }, updated(registration) { console.log('New content is available; please refresh.'); let confirmationResult = confirm("New content found! Do you want to reload the app?"); if (confirmationResult) registration.waiting.postMessage({ action: "skipWaiting" }); }, offline() { console.log('No internet connection found. App is running in offline mode.'); }, error(error) { console.error('Error during service worker registration:', error); }, message(message) { console.log(message); } }); let refreshing; navigator.serviceWorker.addEventListener("controllerchange", () => { if (refreshing) return; window.location.reload(); refreshing = true; }); navigator.serviceWorker.addEventListener("message", (e) => { debugger; console.log(e); });
service-worker-dev.js
workbox.core.setCacheNameDetails({ prefix: "MyWebApp" }); self.__precacheManifest = [].concat(self.__precacheManifest || []); workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); // install new service worker when ok, then reload page. self.addEventListener("message", async msg => { debugger; console.log("message", msg); const clients = await self.clients.matchAll({ type: 'window' }); for (const client of clients) { debugger; client.postMessage(msg); } if (msg.data.action == 'skipWaiting') { self.skipWaiting() } }); self.addEventListener('fetch', function (event) { event.respondWith(async function () { try { var res = await fetch(event.request); var cache = await caches.open('cache'); cache.put(event.request.url, res.clone()); return res; } catch (error) { return caches.match(event.request); } }()); });
更新后的registerServiceWorker.js
/* eslint-disable no-console */ import { register } from 'register-service-worker' //TODO: Replace with right service worker register(`./service-worker-dev.js`, { ready() { console.log( 'App is being served from cache by a service worker.\n' ); }, registered() { console.log('Service worker has been registered.'); }, cached() { console.log('Content has been cached for offline use.'); }, updatefound() { console.log('New content is downloading.'); }, updated(registration) { console.log('New content is available; please refresh.'); let confirmationResult = confirm("New content found! Do you want to reload the app?"); if (confirmationResult) registration.waiting.postMessage({ action: "skipWaiting" }); }, offline() { console.log('No internet connection found. App is running in offline mode.'); }, error(error) { console.error('Error during service worker registration:', error); }, message(message) { console.log(message); } }); let refreshing; navigator.serviceWorker.addEventListener("controllerchange", () => { if (refreshing) return; window.location.reload(); refreshing = true; }); navigator.serviceWorker.addEventListener("message", (e) => { debugger; console.log(e); });
解决方案
核心问题解决办法
修正Service Worker注册路径
使用InjectManifest模式时,Workbox会把src/service-worker-dev.js处理后输出到dist根目录,默认文件名是service-worker.js,而非原文件名。因此需要把registerServiceWorker.js里的注册路径改成./service-worker.js,否则浏览器会找不到正确的SW文件,导致消息无法送达。添加Push事件监听
Chrome开发者工具推送消息需要Service Worker注册push事件才能接收,当前代码缺少该逻辑。在service-worker-dev.js中添加:self.addEventListener('push', (event) => { const content = event.data?.text() || '新消息通知'; event.waitUntil( self.registration.showNotification('MyWebApp', { body: content, icon: './img/icons/apple-touch-icon-152x152.png' }) ); });修复更新无响应问题
- 关闭Chrome DevTools中「Application > Service Workers」的「Update on reload」选项,该选项会导致SW频繁重启,干扰更新逻辑。
- 手动触发更新:可以在Vue组件中添加更新按钮,绑定以下逻辑:
methods: { async checkForUpdate() { const reg = await navigator.serviceWorker.getRegistration(); if (reg) reg.update(); } } - 替换自定义
fetch缓存逻辑:当前的fetch事件可能和Workbox预缓存冲突,改用Workbox官方策略更可靠:// 替换service-worker-dev.js中的fetch事件 workbox.routing.registerRoute( ({url}) => !url.pathname.includes('/api'), // 根据需求排除API请求 new workbox.strategies.StaleWhileRevalidate({ cacheName: 'MyWebApp-runtime-cache' }) );
次要问题解决办法
无需将service-worker-dev.js移到public文件夹,只需按核心问题第一条修正注册路径为./service-worker.js即可。Workbox会自动将处理后的SW文件输出到dist根目录,浏览器就能正常加载。
内容的提问来源于stack exchange,提问作者FrenchCalculator

