各位老铁,晚上好!我是今天的主讲人,很高兴能和大家一起聊聊Vue项目中的PWA离线缓存策略。今天咱们不搞那些虚头巴脑的,直接上干货,争取让大家听完就能上手,让你的Vue项目也拥有“断网不断电”的超能力!
PWA:让你的Vue应用更上一层楼
首先,咱们先简单回顾一下什么是PWA。PWA (Progressive Web App) 是一种使用 Web 技术构建,但拥有原生 App 体验的 Web 应用。它具有以下特点:
- 可靠性 (Reliable): 即使在网络状况不佳或离线状态下也能快速加载。
- 快速 (Fast): 响应迅速,提供流畅的用户体验。
- 吸引力 (Engaging): 具有类似原生 App 的交互体验,例如添加到主屏幕、推送通知等。
其中,离线缓存是PWA的核心特性之一。想象一下,用户在地铁里打开你的Vue应用,即使信号再差,也能流畅浏览之前访问过的内容,是不是很酷?
离线缓存策略:选择比努力更重要
实现离线缓存,最关键的就是选择合适的缓存策略。不同的策略适用于不同的场景,选对了事半功倍,选错了可能适得其反。
咱们常用的缓存策略主要有以下几种:
缓存策略 | 描述 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|
Cache-first | 优先从缓存中获取资源,如果缓存中没有,则从网络获取,并将获取到的资源添加到缓存中。 | 静态资源(例如图片、CSS、JS文件),一旦缓存后不需要频繁更新的资源。 | 速度快,离线可用。 | 如果资源更新了,用户可能无法立即获取到最新的版本。 |
Network-first | 优先从网络获取资源,如果网络请求失败,则从缓存中获取。 | 需要实时更新的数据,例如新闻资讯、股票行情等。 | 保证用户始终获取到最新的数据。 | 如果网络不稳定,用户可能需要等待较长时间才能获取到资源。 |
Cache-only | 只从缓存中获取资源,如果缓存中没有,则返回错误。 | 预先知道所有资源都已缓存的情况,例如离线游戏。 | 速度快,完全离线可用。 | 如果缓存中没有资源,则无法获取。 |
Network-only | 只从网络获取资源,不使用缓存。 | 永远不需要缓存的资源,例如用户行为统计接口。 | 保证用户始终获取到最新的数据。 | 如果网络不稳定,则无法获取资源。 |
Stale-while-revalidate | 先从缓存中获取资源,同时在后台更新缓存。当下次请求相同资源时,会返回缓存中的旧版本,并同时更新缓存。 | 对实时性要求不高,但希望尽快显示数据的资源,例如用户头像、文章列表等。 | 速度快,且可以保证最终一致性。 | 用户可能会看到旧版本的数据,直到缓存更新完成。 |
选择哪种策略,需要根据你的应用场景和数据特点来决定。没有一种策略是万能的,需要灵活运用。
Service Worker:离线缓存的幕后英雄
有了缓存策略,接下来就要靠Service Worker来落地实现了。Service Worker 是一个运行在浏览器后台的脚本,它可以拦截网络请求,并根据缓存策略来决定是返回缓存中的资源,还是从网络获取资源。
注册 Service Worker
首先,在你的Vue项目的入口文件(例如 main.js
)中注册 Service Worker:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(err => {
console.log('ServiceWorker registration failed: ', err);
});
});
}
这段代码会检查浏览器是否支持 Service Worker,如果支持,则注册 service-worker.js
文件。
编写 Service Worker 脚本
接下来,我们需要编写 service-worker.js
文件,来实现具体的缓存逻辑。
const CACHE_NAME = 'my-vue-app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/static/css/app.css',
'/static/js/app.js',
'/static/js/chunk-vendors.js',
'/img/logo.png' // 你的logo
];
// 安装 Service Worker
self.addEventListener('install', event => {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// 拦截网络请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Not in cache - return fetch request
return fetch(event.request).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two independent copies.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
// 激活 Service Worker
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
这段代码做了以下几件事情:
- 定义缓存名称和需要缓存的URL列表:
CACHE_NAME
定义了缓存的名称,urlsToCache
定义了需要预先缓存的资源列表。 - 安装 Service Worker: 在
install
事件中,我们将urlsToCache
中的资源添加到缓存中。 - 拦截网络请求: 在
fetch
事件中,我们首先尝试从缓存中获取资源,如果缓存中没有,则从网络获取,并将获取到的资源添加到缓存中。 - 激活 Service Worker: 在
activate
事件中,我们清理旧版本的缓存。
这段代码实现了一个简单的 Cache-first
策略。当用户访问 urlsToCache
中的资源时,会优先从缓存中获取,如果缓存中没有,则从网络获取,并将获取到的资源添加到缓存中。
更加复杂的缓存策略
上面的例子只是一个简单的 Cache-first
策略,实际项目中可能需要更复杂的策略。例如,我们可以使用 workbox
来简化 Service Worker 的开发。
workbox
是 Google 提供的 PWA 工具库,它提供了各种缓存策略和工具,可以帮助我们更轻松地实现离线缓存。
首先,安装 workbox-webpack-plugin
:
npm install workbox-webpack-plugin --save-dev
然后,在 vue.config.js
文件中配置 workbox-webpack-plugin
:
const { GenerateSW } = require('workbox-webpack-plugin');
module.exports = {
// ...
configureWebpack: {
plugins: [
new GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [
{
urlPattern: /^https://fonts.(?:googleapis|gstatic).com/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: /.(?:png|jpg|jpeg|gif|svg|webp)$/,
handler: 'CacheFirst',
options: {
cacheName: 'images-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 30, // <== 30 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: /.(?:js|css)$/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'static-resources',
expiration: {
maxEntries: 60,
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
})
]
}
// ...
}
这段代码会使用 workbox-webpack-plugin
自动生成 service-worker.js
文件,并配置了三种缓存策略:
CacheFirst
:用于缓存 Google Fonts 和图片资源。StaleWhileRevalidate
:用于缓存 JS 和 CSS 资源。
你可以根据自己的需求配置不同的缓存策略。
消息推送:与用户保持连接
除了离线缓存,PWA 还可以实现消息推送功能,即使应用不在前台,也能向用户发送通知。
获取用户授权
首先,我们需要获取用户的授权才能发送消息推送。
function requestPushPermission() {
return new Promise(function(resolve, reject) {
const permissionResult = Notification.requestPermission(function(result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then(function(permissionResult) {
if (permissionResult !== 'granted') {
throw new Error('用户拒绝了推送授权!');
}
});
}
这段代码会向用户请求推送授权,如果用户同意,则返回 granted
,否则返回 denied
或 default
。
获取推送订阅
获取用户授权后,我们需要获取用户的推送订阅信息,才能向用户发送消息推送。
function subscribeUser() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'你的 VAPID 公钥'
)
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log('Received Push Subscription: ', JSON.stringify(pushSubscription));
// 将推送订阅信息发送到服务器
sendSubscriptionToServer(pushSubscription);
return pushSubscription;
});
}
}
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;
}
这段代码会获取用户的推送订阅信息,并将其发送到服务器。其中,applicationServerKey
是 VAPID 公钥,用于验证推送请求的合法性。
服务器端发送消息推送
获取到用户的推送订阅信息后,服务器端就可以向用户发送消息推送了。
服务器端需要使用 Web Push 协议来发送消息推送。常用的 Web Push 库有 web-push
(Node.js) 和 pywebpush
(Python)。
以 Node.js 为例,使用 web-push
库发送消息推送:
const webpush = require('web-push');
// VAPID keys should only be generated only once.
const vapidKeys = webpush.generateVAPIDKeys();
webpush.setVapidDetails(
'mailto:[email protected]',
vapidKeys.publicKey,
vapidKeys.privateKey
);
// 使用推送订阅信息发送消息推送
const pushSubscription = {
endpoint: '用户的推送订阅 endpoint',
keys: {
auth: '用户的推送订阅 auth',
p256dh: '用户的推送订阅 p256dh'
}
};
const payload = JSON.stringify({
title: 'Hello PWA!',
body: 'This is a test notification.',
icon: '/img/logo.png'
});
webpush.sendNotification(pushSubscription, payload)
.then(result => console.log(result))
.catch(error => console.error(error));
这段代码会向指定的推送订阅发送消息推送。其中,payload
是消息推送的内容,可以包含标题、正文、图标等信息。
Service Worker 处理消息推送
当服务器端发送消息推送后,Service Worker 会接收到推送消息,并显示通知。
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
const notificationTitle = 'PWA Demo';
const notificationOptions = {
body: event.data.text(),
icon: '/img/logo.png',
badge: '/img/badge.png'
};
event.waitUntil(self.registration.showNotification(notificationTitle,
notificationOptions));
});
这段代码会在 Service Worker 接收到推送消息时,显示一个通知。
总结
今天我们聊了Vue项目中的PWA离线缓存策略和消息推送功能。希望大家能够掌握这些知识,让你的Vue应用更上一层楼!
记住,PWA 不是一蹴而就的,需要不断地学习和实践。希望大家能够多尝试、多思考,打造出更好的 PWA 应用!
好了,今天的讲座就到这里,谢谢大家!