也许首先有必要说明一下 pwa 是什么。
pwa 并不是某个特定的技术,而是一个 web 应用程序理念。其是通过一系列 web 技术,渐进增强式地提升 web 应用程序的体验,让 web 程序具备原生应用的特性。
pwa 式的 web 应用程序可以被安装、可以被添加到桌面、可以缓存资源以节省流量、可以推送消息等。
pwa 是一个激动人心的理念,但是其普及也困难重重。原因就不说了,很多。不过,对于个人网站而言,pwa 还是很值得尝试的,反正也没啥坏处。
我使用 pwa 的目的很简单——让网站可以添加到桌面。相比于直接发送一个快捷方式到桌面(某些手机会支持,但并不是所有),pwa 更具扩展性,体验更好,也是尝试新技术的机会。
前言结束,可以开始正文了。首先,要说明一下,vue 之类的框架在使用 webpack 构建应用的情况下是有现成的插件或者模板的。我并没有使用现成的插件或者模板,因为是既有项目改造(用的是 vue-cli2,不是 vue-cli3),所以还是自己组织结构和相关文件和配置比较好。
第一步。在项目根目录新建 pwa 文件夹,用来存放需要的文件。我这里只有简单丁四个文件:
pwa/ ├── logo192.png ├── logo512.png ├── manifest.json └── sw.js
两张图片,一张是添加到桌面使用,另一张是启动屏使用。manifets.json 是 pwa 配置文件(ps:命名随意)。
// manifest.json
{
"name": "品词轩",
"short_name": "品词轩",
"description": "品词轩-在古典中寻得一方宁静",
"icons": [{
"src": "./logo192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "./logo512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"background_color": "#339999",
"theme_color": "#339999",
"display": "standalone",
"orientation": "portrait",
"start_url": ".",
"scope": "/"
}
内容比较简单,基本一看就知道各字段是做什么的。这个文件的其他内容可以在 MDN 上找到。
// sw.js
var cacheStorageKey = 'poem-0.0.2'
var cacheList = []
self.addEventListener('install', e => { // install 事件,它发生在浏览器安装并注册 Service Worker 时
// e.waitUtil 用于在安装成功之前执行一些预装逻辑
e.waitUntil(
caches.open(cacheStorageKey)
.then(cache => cache.addAll(cacheList))
.then(() => self.skipWaiting())
)
})
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response != null) {
return response
}
return fetch(e.request.url)
})
)
})
self.addEventListener('activated', function(e) {
e.waitUntil(
// 获取所有cache名称
caches.keys().then(cacheNames => {
return Promise.all(
// 获取所有不同于当前版本名称cache下的内容
cacheNames.filter(cacheNames => {
return cacheNames !== cacheStorageKey
}).map(cacheNames => {
return caches.delete(cacheNames)
})
)
}).then(() => {
return self.clients.claim()
})
)
})
sw.js 的内容基本是从网上拷贝的。这个文件会运行在 service worker 线程中,并且会拦截各种网络请求。文件中监听的 fetch 事件回调就是对请求进行的处理。我这里会尝试从缓存获取,如果缓存没有才发起请求。在一开始,我将跟路径“/”添加到了 cacheList 中。其结果是首页被缓存,导致后续发版无法更新。还好当时是本地测试,在浏览器开发工具 application 面板清除缓存并将 cacheList 置空后,问题迎刃而解。当然,这种做法并不好,实际上可以通过检测缓存版本解决。不过,算了,我只是需要将网站添加到桌面。
2021-12-24 更新:
对于只需要添加桌面图标,不需要缓存内容,我找到了更简单的版本。
const cacheName = 'kodcloud';
const staticAssets = [];
self.addEventListener('install', async e => {
});
self.addEventListener('activate', e => {
self.clients.claim();
});
self.addEventListener('fetch', async e => {
});
如代码所示,这是可道云的代码。
四个文件准备好后,再修改一下 /index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="manifest" href="manifest.json">
<title>品词轩</title>
<!-- <link rel="icon" href="favicon.ico" type="image/x-icon" /> -->
<script>
window.v = window.version = '4.0.1'
// 检测浏览器是否支持SW
if(navigator.serviceWorker != null){
navigator.serviceWorker.register('sw.js',{scope: '/'})
.then(function(registartion){
//注册成功
console.log('支持sw:',registartion.scope)
}).catch(function (err){
console.log('fail')
});
}
</script>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
第 6 行的 <link /> 标签引用 manifest.json 文件,下面的脚本部分则是注册serviceWorker。
最后,配置 webpack,确保打包的时候会将 pwa 里的文件打包到根目录(因为作用域是根目录)。
// /build/webpack.dev.conf.js
……
new CopyWebpackPlugin([
……
{
from: path.resolve(__dirname, '../pwa'),
to: config.build.assetsPath,
ignore: ['.*']
}
])
……
至此,大功告成。