PWA(Progressive Web App)的概念出来的时间已经不短了,由于忙于业务,一直没有时间好好去了解它。近日,花了点时间小研究了一下,我觉得很有必要做个学习记录来分享一下。
因为,PWA 的知识和概念,我们可以通过 Google 来深入了解,我不想在这里重复介绍。下面的总结和记录,是我认为比较重要的知识点。
Service Worker
首先,我们需要一个叫 Service Worker 的 HTML5 API 来帮你做各种事情,比如:离线资源缓存、消息推送、后台同步等。不过,有个前提条件,你的站点必须是 HTTPS 的环境,但在开发环境,非 HTTPS 的 localhost
和 127.0.0.1
也是可以调试的。
注册
在使用前,我们必须要在 JS 主线程(通常是页面的 JS)中注册 Service Worker。
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(function (registration) {
// 注册成功
})
.catch(function (err) {
// 注册失败:(
});
});
}
这段代码,有几个地方需要关注。sw.js
这个 Service Worker 文件,必须和当前站点同域。scope
参数将决定 Service Worker 在当前域下可控制的子目录范围。如果不传,默认是 sw.js
文件所在的路径。
安装
在注册 Service Worker 时,你可以把 sw.js
留空,但这样显然没有意义。当 Service Worker 注册成功后,就会触发 install
事件。在 install
事件中,你可以缓存静态资源,以填充浏览器的离线缓存能力。
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('cache-v1.0').then(function(cache) {
return cache.addAll([
'/',
'/index.html',
'/main.css',
'/main.js',
...
]);
})
);
});
除了 install
事件,我们还可以通过 fetch
事件,动态的缓存资源的请求。
this.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
// 如果 Service Worker 有自己的返回,就直接返回,减少一次 http 请求
if (response) {
return response;
}
// 如果 service worker 没有返回,那就得直接请求真实远程服务
// 把原始请求复制过来
var request = event.request.clone();
return fetch(request).then(function(httpRes) {
// 请求失败了,直接返回失败的结果就好了。。
if (!httpRes || httpRes.status !== 200) {
return httpRes;
}
// 请求成功的话,将请求缓存起来。
var responseClone = httpRes.clone();
caches.open('cache-v1.0').then(function(cache) {
cache.put(event.request, responseClone);
});
return httpRes;
});
})
);
});
使用 fetch
还可以对 Ajax 数据加以适当缓存处理,以实现真正的离线可用。但 fetch
事件有个缺点,在注册/安装成功后,需要再多一次访问后才能离线使用。
更新
当 sw.js
有了新的缓存策略,Service Worker 会重新触发 install
事件,但在安装完成后,只会进入 waiting
状态,直到所有打开的页面关闭,新的 Service Worker 才会在重新打开的页面中生效。
所以,我们需要通过自动更新的方法来跳过 waiting 状态。
self.addEventListener('install', function (event) {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function(event) {
event.waitUntil(
Promise.all([
// 更新客户端
self.clients.claim(),
// 清理旧版本
caches.keys().then(function(cacheList) {
return Promise.all(
cacheList.map(function(cacheName) {
if (cacheName !== 'cache-v2.0') {
return caches.delete(cacheName);
}
})
);
}),
])
);
});
Workbox
有没有觉得上面的安装和更新有点复杂和繁琐,而且,当静态资源更新后(比如用 webpack 构建),需要重新调整缓存策略。幸运的是,Google 工程师已经帮我们做好了封装,你可以直接使用 Workbox 框架来处理这些琐碎的事。
如果你的项目是用 webpack 构建的,可以直接安装 workbox-webpack-plugin
插件,具体配置可以参考 webpack 官方文档。
manifest.json
注册/安装了 Service Worker,只实现了 Web App 的离线工作,有了 manifest.json
就可以把站点添加至主屏幕。
{
"short_name": "短名称",
"name": "这是一个完整名称",
"icon": [
{
"src": "icon.png",
"type": "image/png",
"sizes": "48x48"
}
],
"start_url": "index.html"
}
在 manifest.json
文件中,除了上面示例属性外,还可以配置启动背景色、显示类型、主题颜色等等。具体可以自行 Google。
然后,我们需要使用 link
把 manifest.json
添加到 HTML 页面的头部。
<link rel="manifest" href="/manifest.json">
兼容性支持
由于 PWA 采用的技术比较新,目前,浏览器还没有达到完全支持的程度, W3C 也还处于草案阶段,还没有定稿。我们可以通过 Can I use 来查看以上技术的支持性。