1. 项目概述
作为一名从2015年就开始接触混合开发的老兵,我见证了Uniapp从诞生到成为跨平台开发首选框架的全过程。最近两年,PWA(渐进式Web应用)技术的成熟让Web应用具备了媲美原生App的体验,而Uniapp+PWA的组合更是让开发者如虎添翼。
但新手在起步阶段往往会卡在配置环节——不是少了manifest.json就是漏了service worker注册。今天我就带大家彻底搞明白Uniapp+PWA开发中最关键的3个配置项,这些都是我踩过无数坑后总结的实战经验。
2. 核心配置解析
2.1 manifest.json - 应用的身份凭证
这个文件相当于你PWA应用的"身份证",决定了应用如何出现在用户设备上。在Uniapp项目中,它应该放在/static目录下。以下是必须配置的核心字段:
json复制{
"name": "MyPWAApp",
"short_name": "PWAApp",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4DBA87",
"icons": [
{
"src": "/static/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
关键提示:
display属性建议使用standalone而不是fullscreen,否则Android设备会隐藏状态栏导致布局错位。这是很多新手容易忽略的细节。
2.2 serviceWorker配置 - 离线缓存的核心
在/src目录下创建registerServiceWorker.js:
javascript复制if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js', {
scope: '/'
}).then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
然后在main.js中引入:
javascript复制import './registerServiceWorker'
常见坑点:scope必须与start_url匹配,否则缓存会失效。我遇到过因为写成
./导致缓存不生效的情况,调试了整整一天才发现问题。
2.3 vue.config.js - 构建配置关键
javascript复制module.exports = {
pwa: {
name: 'MyPWAApp',
themeColor: '#4DBA87',
msTileColor: '#000000',
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'black',
// 关键配置:workbox插件配置
workboxPluginMode: 'InjectManifest',
workboxOptions: {
swSrc: './src/service-worker.js',
swDest: 'service-worker.js',
exclude: [/\.map$/, /manifest\.json$/]
}
}
}
经验之谈:
InjectManifest模式比GenerateSW更灵活,可以自定义缓存策略。但需要自己编写service-worker.js,下面会详细说明。
3. 深度配置实战
3.1 自定义service-worker.js
在/src目录下创建service-worker.js:
javascript复制import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'
// 预缓存关键资源
precacheAndRoute(self.__WB_MANIFEST)
// 动态缓存策略
registerRoute(
({url}) => url.pathname.startsWith('/api'),
new StaleWhileRevalidate({
cacheName: 'api-cache',
plugins: []
})
)
// 安装后立即激活
self.addEventListener('install', () => {
self.skipWaiting()
})
// 激活时清理旧缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== 'api-cache') {
return caches.delete(cacheName)
}
})
)
})
)
})
3.2 缓存策略选择指南
| 策略类型 | 适用场景 | 代码示例 | 优缺点 |
|---|---|---|---|
| NetworkFirst | 实时性要求高的数据 | new NetworkFirst() |
保证最新但耗流量 |
| CacheFirst | 静态资源 | new CacheFirst() |
快速但可能过期 |
| StaleWhileRevalidate | 可容忍短暂过期的数据 | new StaleWhileRevalidate() |
平衡速度与新鲜度 |
| NetworkOnly | 必须实时获取 | new NetworkOnly() |
完全依赖网络 |
我的选择经验:对API请求使用StaleWhileRevalidate,对静态资源使用CacheFirst,这样能在性能和实时性间取得最佳平衡。
4. 调试与优化技巧
4.1 必备调试工具
- Chrome开发者工具 → Application → Manifest:检查manifest配置
- Chrome开发者工具 → Application → Service Workers:调试SW生命周期
- Lighthouse审计:全面检测PWA合规性
4.2 常见问题排查
问题1:添加到主屏幕按钮不显示
- 检查manifest.json是否可访问
- 确保start_url是相对根路径
- 验证icons配置的图片尺寸是否正确
问题2:离线无法访问
- 检查service worker是否注册成功
- 确认缓存策略是否包含必要资源
- 测试是否在build后生成了正确的service-worker.js
问题3:更新不生效
- 修改service-worker.js的版本号
- 在activate事件中添加缓存清理逻辑
- 使用skipWaiting()强制更新
5. 进阶优化方案
5.1 按需缓存策略
javascript复制// 缓存用户访问过的页面
registerRoute(
({request}) => request.mode === 'navigate',
async ({event}) => {
try {
const networkResponse = await fetch(event.request)
const cache = await caches.open('pages-cache')
cache.put(event.request, networkResponse.clone())
return networkResponse
} catch (error) {
const cachedResponse = await caches.match(event.request)
return cachedResponse || await caches.match('/offline.html')
}
}
)
5.2 后台同步实现
javascript复制// 注册后台同步
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(
sendDataToServer().then(() => {
return Promise.resolve()
})
)
}
})
async function sendDataToServer() {
const requests = await indexedDB.getAll('pendingRequests')
return Promise.all(requests.map(req =>
fetch(req.url, {
method: req.method,
body: req.body
})
))
}
5.3 推送通知集成
javascript复制// 注册推送通知
self.addEventListener('push', (event) => {
const data = event.data.json()
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png'
})
)
})
// 处理通知点击
self.addEventListener('notificationclick', (event) => {
event.notification.close()
event.waitUntil(
clients.openWindow(event.notification.data.url)
)
})
6. 项目实战建议
-
图标资源准备:至少准备192x192和512x512两种尺寸的PNG图标,这是添加到主屏幕的硬性要求。可以使用https://realfavicongenerator.net/ 一站式生成所有需要的图标和manifest配置。
-
测试策略:在开发阶段,我习惯在Chrome的Application → Service Workers中勾选"Update on reload",这样可以避免每次修改都要手动清除缓存的麻烦。
-
版本控制技巧:每次发布新版本时,我会在service-worker.js顶部添加
const VERSION = '1.0.2',这样只需修改版本号就能触发所有客户端的更新。 -
离线回退方案:一定要准备一个offline.html页面,当网络不可用且没有缓存时显示友好的离线提示。这个文件也应该被预缓存。
-
性能监控:集成Workbox的日志记录功能,监控实际用户的缓存命中率,根据数据优化缓存策略:
javascript复制import { setLogLevel } from 'workbox-core'
setLogLevel('debug')
通过以上配置,你的Uniapp+PWA应用就已经具备了离线访问、添加到主屏幕、推送通知等核心PWA特性。记住,PWA不是一蹴而就的,需要根据用户反馈不断优化缓存策略和用户体验。