各位观众老爷,晚上好!我是今晚的讲师,今天咱们不聊风花雪月,就聊聊Vue CLI的PWA插件,看看它怎么把Service Worker这玩意儿玩得转,让你的Vue应用也能离线可用,还能推送通知,甚至能添加到主屏幕,瞬间逼格满满。
开场白:PWA是个啥?Service Worker又是啥?
咱们先来个热身,简单解释一下PWA和Service Worker是啥,如果你已经滚瓜烂熟,可以直接跳到下一节。
-
PWA (Progressive Web App): 这玩意儿不是一个具体的技术,而是一种理念!就是让你的网站像原生App一样丝滑流畅,拥有离线访问、推送通知等特性。简单来说,就是让网页更像App。
-
Service Worker: 这才是真正干活的!它是一个运行在浏览器后台的脚本,可以拦截网络请求,缓存资源,推送通知等等。它是PWA的核心技术,没有它,PWA就是个空壳子。你可以把它想象成一个默默守护你的应用的管家,在你网络不好的时候,还能给你端茶倒水(提供缓存)。
第一节:Vue CLI PWA插件,一键梭哈!
Vue CLI 提供了非常方便的 PWA 插件,让咱们可以一键集成 PWA 功能,省去了手动配置 Service Worker 的烦恼。
-
安装插件:
首先,确保你已经有了一个 Vue CLI 项目。如果没有,先用
vue create my-project
创建一个。 然后,进入你的项目目录,运行以下命令安装 PWA 插件:vue add @vue/pwa
这个命令会帮你完成以下操作:
- 安装
register-service-worker
依赖。 - 在
public
目录下生成manifest.json
文件(用于配置 PWA 应用信息)。 - 在
src/registerServiceWorker.js
文件中注册 Service Worker。 - 修改
vue.config.js
文件(如果需要)。
- 安装
-
manifest.json
:PWA的身份证!manifest.json
文件是 PWA 应用的“身份证”,它包含了应用的名称、图标、描述等信息,浏览器会根据这些信息来安装 PWA 应用到用户的设备上。打开public/manifest.json
文件,你会看到类似下面的内容:{ "name": "my-project", "short_name": "my-project", "icons": [ { "src": "./img/icons/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "./img/icons/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" }
name
: 应用的完整名称,会显示在安装提示和启动画面上。short_name
: 应用的简称,如果完整名称太长,会显示这个。icons
: 应用的图标,不同尺寸的图标,用于不同的场景。start_url
: 应用启动时打开的 URL。display
: 应用的显示模式,standalone
表示以独立窗口模式运行,就像原生 App 一样。theme_color
: 应用的主题颜色,会影响状态栏的颜色。background_color
: 应用的背景颜色,在应用启动时会显示。
重点: 你需要根据你的应用信息修改这些配置,尤其是
name
、short_name
和icons
。 把icons
替换成你自己的应用图标,并确保提供不同尺寸的版本。 -
registerServiceWorker.js
:Service Worker的注册员!src/registerServiceWorker.js
文件负责注册 Service Worker。打开这个文件,你会看到类似下面的代码:import { register } from 'register-service-worker' if (process.env.NODE_ENV === 'production') { register(`${process.env.BASE_URL}service-worker.js`, { ready () { console.log( 'App is being served from cache by service worker.n' + 'For more details, visit https://goo.gl/AFskqB' ) }, 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 () { console.log('New content is available; please refresh.') }, offline () { console.log('No internet connection found. App is running in offline mode.') }, error (error) { console.error('Error during service worker registration:', error) } }) }
这段代码会在生产环境下注册 Service Worker。你可以通过
ready
、registered
、cached
等回调函数来监听 Service Worker 的状态。 这些回调函数可以帮你了解 Service Worker 的生命周期,比如什么时候缓存完成,什么时候有更新等等。 -
service-worker.js
:干活的来了!这个文件才是真正实现离线缓存的核心!但是,Vue CLI PWA 插件默认使用 Workbox 来生成
service-worker.js
文件,这个文件是自动生成的,一般情况下你不需要手动修改它。Workbox 是一套 Google 提供的 Service Worker 工具库,可以帮你简化 Service Worker 的开发。
第二节:自定义Service Worker配置,玩出花样!
虽然 Vue CLI PWA 插件已经很方便了,但有时候我们还是需要自定义 Service Worker 的配置,比如缓存策略、预缓存资源等等。
-
vue.config.js
:配置的入口!所有的 PWA 相关配置都放在
vue.config.js
文件中。打开这个文件,找到pwa
选项。如果没有,你可以手动添加一个:module.exports = { pwa: { // PWA 相关配置 } }
-
workboxPluginMode
:选择生成模式!workboxPluginMode
选项决定了 Workbox 的生成模式,有两种模式:-
GenerateSW
: 这是默认模式,Workbox 会自动生成service-worker.js
文件,并根据你的配置进行优化。 -
InjectManifest
: 这种模式允许你手动编写service-worker.js
文件,Workbox 会将你的配置注入到这个文件中。
如果你需要完全控制 Service Worker 的行为,可以选择
InjectManifest
模式。module.exports = { pwa: { workboxPluginMode: 'InjectManifest', workboxOptions: { swSrc: 'src/service-worker.js' // 指定你的 Service Worker 文件 } } }
-
-
workboxOptions
:配置的宝库!workboxOptions
选项包含了 Workbox 的所有配置项,你可以通过它来控制 Service Worker 的行为。 太多了,咱们挑几个常用的说:-
swSrc
: 指定你的 Service Worker 文件的路径,只有在workboxPluginMode
为InjectManifest
时才需要配置。 -
swDest
: 指定生成的 Service Worker 文件的路径,默认为dist/service-worker.js
。 -
importWorkboxFrom
: 指定 Workbox 的来源,可以是cdn
或local
,默认为cdn
。 如果选择local
,Workbox 会被打包到你的项目中。 -
clientsClaim
: 是否让 Service Worker 立即控制所有客户端,默认为true
。 -
skipWaiting
: 是否跳过等待阶段,立即激活新的 Service Worker,默认为true
。 -
runtimeCaching
: 配置运行时缓存策略,这是最重要的一个配置项!
-
-
runtimeCaching
:缓存策略!runtimeCaching
选项用于配置运行时缓存策略,你可以根据不同的 URL 设置不同的缓存策略。module.exports = { pwa: { workboxOptions: { runtimeCaching: [ { urlPattern: /^https://fonts.(googleapis|gstatic).com/.*/, handler: 'CacheFirst', options: { cacheName: 'google-fonts-cache', expiration: { maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days maxEntries: 30 } } }, { urlPattern: /.(?:png|jpg|jpeg|svg|gif)$/, handler: 'CacheFirst', options: { cacheName: 'images-cache', expiration: { maxEntries: 60, maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days } } }, { urlPattern: new RegExp('^https://your-api-domain.com/api/'), handler: 'NetworkFirst', options: { cacheName: 'api-cache', networkTimeoutSeconds: 5, expiration: { maxEntries: 50, maxAgeSeconds: 60 * 60 // 1 hour }, plugins: [ { handlerDidError: async ({ request }) => { console.log('Failed to fetch', request.url); } } ] } } ] } } }
上面的代码配置了三个缓存策略:
- Google Fonts: 使用
CacheFirst
策略,优先从缓存中读取,如果缓存中没有,则从网络请求,并将结果缓存起来。缓存时间为一年。 - Images: 使用
CacheFirst
策略,缓存图片资源。缓存时间为 30 天。 - API: 使用
NetworkFirst
策略,优先从网络请求,如果网络请求失败,则从缓存中读取。缓存时间为 1 小时。
handler
的选择:CacheFirst
: 优先从缓存中读取,适用于静态资源,如图片、字体等。NetworkFirst
: 优先从网络请求,适用于 API 接口,可以保证数据的实时性。StaleWhileRevalidate
: 先从缓存中读取,然后发起网络请求更新缓存,适用于对实时性要求不高的场景。NetworkOnly
: 只从网络请求,不使用缓存。CacheOnly
: 只从缓存中读取,不发起网络请求。
options
的配置:cacheName
: 缓存的名称。expiration
: 缓存的过期时间。plugins
: 插件,可以用来扩展缓存策略的功能,比如添加请求头、处理错误等。
- Google Fonts: 使用
第三节:手动编写Service Worker,掌控全局!
如果你选择了 InjectManifest
模式,就需要手动编写 service-worker.js
文件。 这需要你对 Service Worker 的生命周期和 API 有一定的了解。
-
Service Worker 的生命周期:
Service Worker 有三个主要的生命周期阶段:
-
Install: 安装阶段,在这个阶段可以缓存静态资源。
-
Activate: 激活阶段,在这个阶段可以清理旧的缓存。
-
Fetch: 拦截网络请求阶段,在这个阶段可以从缓存中读取资源,或者发起网络请求。
-
-
src/service-worker.js
:你的舞台!创建一个
src/service-worker.js
文件,并按照你的需求编写 Service Worker 代码。import { precacheAndRoute } from 'workbox-precaching' import { registerRoute } from 'workbox-routing' import { CacheFirst, NetworkFirst } from 'workbox-strategies' import { ExpirationPlugin } from 'workbox-expiration' // 预缓存资源 (Vue CLI PWA 插件会自动生成这个变量) precacheAndRoute(self.__WB_MANIFEST) // 缓存 Google Fonts registerRoute( /^https://fonts.(googleapis|gstatic).com/.*/, new CacheFirst({ cacheName: 'google-fonts-cache', plugins: [ new ExpirationPlugin({ maxAgeSeconds: 60 * 60 * 24 * 365, // 365 days maxEntries: 30 }) ] }) ) // 缓存图片 registerRoute( /.(?:png|jpg|jpeg|svg|gif)$/, new CacheFirst({ cacheName: 'images-cache', plugins: [ new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days }) ] }) ) // 缓存 API 接口 registerRoute( new RegExp('^https://your-api-domain.com/api/'), new NetworkFirst({ cacheName: 'api-cache', networkTimeoutSeconds: 5, plugins: [ new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 60 * 60 // 1 hour }) ] }) ) self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting() } })
-
precacheAndRoute
: 预缓存资源,Vue CLI PWA 插件会自动生成一个包含所有需要预缓存的资源的列表,存储在self.__WB_MANIFEST
变量中。 -
registerRoute
: 注册路由,用于拦截指定 URL 的请求,并使用指定的缓存策略。 -
CacheFirst
、NetworkFirst
:缓存策略,与vue.config.js
中的runtimeCaching
选项中的handler
作用相同。 -
ExpirationPlugin
: 缓存过期插件,用于设置缓存的过期时间和最大条目数。 -
self.addEventListener('message', ...)
: 监听来自页面的消息,用于跳过等待阶段,立即激活新的 Service Worker。
-
第四节:推送通知,搞事情!
PWA 的一个重要特性就是推送通知,可以让你的应用像原生 App 一样,主动向用户发送消息。
-
VAPID Keys:身份验证!
要使用推送通知,首先需要生成 VAPID (Voluntary Application Server Identification) Keys,这是一对公钥和私钥,用于验证你的服务器的身份。
可以使用
web-push
这个 npm 包来生成 VAPID Keys:npm install web-push --save
const webpush = require('web-push'); const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys.publicKey); console.log(vapidKeys.privateKey);
将生成的公钥和私钥保存起来,后面会用到。
-
配置 VAPID Keys:
在你的服务器端代码中,配置 VAPID Keys:
const webpush = require('web-push'); webpush.setVapidDetails( 'mailto:[email protected]', // 你的邮箱 'YOUR_PUBLIC_KEY', // 你的公钥 'YOUR_PRIVATE_KEY' // 你的私钥 );
-
订阅推送:
在客户端代码中,你需要先检查浏览器是否支持推送通知,然后获取用户的许可,才能订阅推送。
if ('serviceWorker' in navigator && 'PushManager' in window) { navigator.serviceWorker.ready .then(function(registration) { return registration.pushManager.getSubscription() .then(function(subscription) { if (subscription) { // 用户已经订阅了推送 return subscription; } const vapidPublicKey = 'YOUR_PUBLIC_KEY'; return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }); }); }) .then(function(subscription) { // 将订阅信息发送到服务器 fetch('/api/subscribe', { method: 'POST', body: JSON.stringify(subscription), headers: { 'content-type': 'application/json' } }); }) .catch(function(error) { console.error('Error subscribing to push notifications:', error); }); } function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }
navigator.serviceWorker.ready
: 等待 Service Worker 注册完成。registration.pushManager.getSubscription()
: 检查用户是否已经订阅了推送。registration.pushManager.subscribe()
: 订阅推送,需要提供 VAPID 公钥。urlBase64ToUint8Array()
: 将 Base64 编码的 VAPID 公钥转换为 Uint8Array 格式。- 将订阅信息发送到服务器,服务器需要保存这些信息,用于后续推送消息。
-
发送推送:
在服务器端,可以使用
web-push
发送推送消息:const webpush = require('web-push'); const subscription = { endpoint: '...', // 用户的订阅 endpoint keys: { p256dh: '...', // 用户的公钥 auth: '...' // 用户的授权密钥 } }; const payload = JSON.stringify({ title: 'Hello PWA!', body: 'This is a push notification!', icon: 'path/to/your/icon.png' }); webpush.sendNotification(subscription, payload) .catch(error => console.error(error));
subscription
: 用户的订阅信息,从数据库中获取。payload
: 推送消息的内容,包含标题、内容和图标。
第五节:添加到主屏幕,更像App!
PWA 还可以添加到用户的主屏幕,就像原生 App 一样。 当用户访问你的 PWA 应用时,浏览器会提示用户是否要添加到主屏幕。
-
manifest.json
:关键!要让 PWA 可以添加到主屏幕,
manifest.json
文件必须配置正确。 确保name
、short_name
、icons
、start_url
和display
等字段都已正确配置。 -
Service Worker:默默支持!
Service Worker 必须注册成功,才能让 PWA 可以离线访问和添加到主屏幕。
-
HTTPS:安全保障!
PWA 必须部署在 HTTPS 协议下,才能正常工作。
总结:PWA,让你的网站起飞!
通过 Vue CLI PWA 插件,我们可以很方便地将 PWA 功能集成到 Vue 应用中,让你的网站拥有离线缓存、推送通知和添加到主屏幕等特性,提升用户体验,让你的网站更像原生 App。
当然,PWA 的内容还有很多,比如后台同步、Web Share API 等等,需要进一步学习和探索。 希望今天的讲座能帮助你入门 PWA,让你的网站起飞!
感谢各位的观看,下课!