1. 项目概述
PWA(Progressive Web App)技术正在改变移动应用开发的游戏规则。作为一名长期奋战在一线的全栈开发者,我见证了太多团队在原生应用和H5之间艰难抉择的痛苦。直到三年前第一次将Uniapp与PWA技术结合,才真正找到了"一次开发,多端运行"的完美平衡点。
这个教程将带你用30分钟完成首个可桌面安装的离线应用。不同于官方文档的学院派风格,我会重点分享实际项目中验证过的"暴力解法"——比如如何绕过manifest.json的图标配置陷阱,以及Service Worker缓存策略的实战选择。这些经验都来自我们团队在电商、教育类项目中趟过的坑。
2. 环境准备与工具选型
2.1 开发环境配置
推荐使用VS Code + HBuilderX双编辑器方案:
- VS Code负责代码编写(插件:Volar、uniapp-snippets)
- HBuilderX用于真机调试(版本建议3.6.18+)
bash复制# 全局安装vue-cli
npm install -g @vue/cli
# 检查node版本(需>=14)
node -v
注意:避免使用npm 7+的peer dependencies自动安装功能,这可能导致uniapp模板依赖冲突。建议通过
npm config set legacy-peer-deps true降级处理。
2.2 项目初始化
使用vue-cli的preset功能快速搭建:
bash复制vue create -p dcloudio/uni-preset-vue pwa-demo
选择"默认模板(TypeScript)",这样会自带必要的类型声明。
3. PWA核心模块实现
3.1 Manifest配置实战
在public目录下新建manifest.json:
json复制{
"name": "我的PWA应用",
"short_name": "PWA Demo",
"start_url": "/",
"display": "standalone",
"background_color": "#FFFFFF",
"theme_color": "#4DBA87",
"icons": [
{
"src": "/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
图标生成技巧:
- 使用https://www.pwabuilder.com/imageGenerator 一键生成多尺寸图标
- 将图标放在public/icons目录(不是assets!)
- 确保图标文件权限为644,否则iOS可能无法加载
3.2 Service Worker深度配置
修改vue.config.js:
javascript复制const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
pwa: {
name: 'PWA Demo',
themeColor: '#4DBA87',
workboxOptions: {
skipWaiting: true,
clientsClaim: true,
runtimeCaching: [{
urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
handler: 'CacheFirst',
options: {
cacheName: 'images-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30天
}
}
}]
}
}
})
缓存策略选择原则:
- 静态资源:CacheFirst(如CSS/JS)
- 动态接口:NetworkFirst(如API请求)
- 大体积媒体:StaleWhileRevalidate(如视频)
4. Uniapp适配关键点
4.1 路由模式改造
在main.ts中强制使用history模式:
typescript复制import { createSSRApp } from 'vue'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// 强制修改路由模式
uni.addInterceptor('navigateTo', {
invoke(args) {
if (args.url.startsWith('http')) return false
if (args.url.indexOf('?') > -1) {
args.url = args.url.split('?')[0] + '.html?' + args.url.split('?')[1]
} else {
args.url += '.html'
}
return true
}
})
4.2 原生能力兼容方案
通过条件编译解决平台差异:
html复制<!-- #ifdef H5 -->
<button @click="installPWA">安装应用</button>
<!-- #endif -->
<script>
export default {
methods: {
installPWA() {
window.deferredPrompt.prompt()
window.deferredPrompt.userChoice.then(choice => {
if (choice.outcome === 'accepted') {
uni.showToast({ title: '安装成功' })
}
})
}
}
}
</script>
5. 调试与发布
5.1 Lighthouse性能优化
常见指标提升技巧:
- 首屏加载:预加载关键CSS(使用critters插件)
- 交互延迟:异步加载非必要组件
- 资源压缩:配置image-webpack-loader自动压缩
javascript复制// vue.config.js
chainWebpack(config) {
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.9], speed: 4 },
gifsicle: { interlaced: false }
})
}
5.2 多平台发布策略
平台差异处理方案:
- iOS:需额外配置apple-touch-icon
- 微信浏览器:引导用户通过右上角菜单添加
- 国内安卓:需配置腾讯X5内核兼容
html复制<!-- index.html -->
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
6. 常见问题排查
6.1 安装按钮不显示
检查清单:
- 是否满足PWA安装条件(HTTPS、Service Worker、Manifest)
- 是否触发了beforeinstallprompt事件
- 安卓Chrome版本是否≥68
调试代码:
javascript复制window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
window.deferredPrompt = e
// 显示安装按钮
})
6.2 离线缓存失效
典型原因:
- Service Worker未正确注册
- workbox配置的precache规则有误
- 资源URL包含hash导致版本不匹配
强制更新方案:
javascript复制// 在App.vue中
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => reg.update())
})
}
7. 性能优化进阶
7.1 预加载策略
在uni-app的pages.json中配置:
json复制{
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["_APP_"]
}
}
}
7.2 按需注入技巧
动态加载Service Worker:
javascript复制// main.ts
if (process.env.NODE_ENV === 'production') {
import('workbox-window').then(({ Workbox }) => {
const wb = new Workbox('/sw.js')
wb.register()
})
}
8. 项目扩展方向
8.1 消息推送集成
使用Firebase Cloud Messaging:
javascript复制// 初始化
messaging.getToken({
vapidKey: '你的公钥'
}).then(token => {
uni.setStorageSync('fcm_token', token)
})
// 接收消息
messaging.onMessage(payload => {
uni.showModal({
title: payload.notification.title,
content: payload.notification.body
})
})
8.2 数据同步方案
背景同步API示例:
javascript复制navigator.serviceWorker.ready.then(reg => {
reg.sync.register('sync-data').then(() => {
uni.showToast({ title: '已加入同步队列' })
})
})
在Service Worker中:
javascript复制self.addEventListener('sync', event => {
if (event.tag === 'sync-data') {
event.waitUntil(syncData())
}
})