PWA:( Progressive web apps,渐进式 Web 应用)
PWA
是现代 web
开发的一个新的理念,他不依赖于某个特定的 API
,而是使用各种技术和模式来开发的 web
应用,并且同时具备 web
应用和原生应用的特性,以此来达到最佳 web
体验的目标;
一个应用可以称为 PWA
,应该具备以下特点:
Discoverable 内容可以通过搜索引擎发现。
Installable 可以出现在设备的主屏幕。
Linkable 你可以简单地通过一个 URL 来分享它。
Network independent 它可以在离线状态或者是在网速很差的情况下运行。
Progressive 它在老版本的浏览器仍旧可以使用,在新版本的浏览器上可以使用全部功能。
Re-engageable 无论何时有新的内容它都可以发送通知。
Responsive 它在任何具有屏幕和浏览器的设备上可以正常使用——包括手机,平板电脑,笔记本,电视,冰箱,等。
Safe 在你和应用之间的连接是安全的,可以阻止第三方访问你的敏感数据。
所以,判断一个 web
应用是否是 PWA
需要看它是否同时具备原生应用的特性,比如:桌面图标,离线缓存,消息推送等;当然,他的好处也是很多的,比如:快!真的非常快,并且离线可访问;用户可以同意添加图标到主屏方便下次访问;还可以实现系统级的消息推送;总之,就是不断的接近原生应用的体验!
实现一个 PWA
需要的核心技术包括:Service Worker
+ Manifest.json
+ HTTPS
Service Worker
是一个注册在指定资源和路径下的事件驱动 worker
,因此它同其他类型 worker
一样不能访问 DOM
,不允许使用同步的 API
,比如 localStorage
,但是他能拦截并修改访问的资源请求,通过多种缓存策略来对资源进行缓存和更新;
worker
:if ('serviceWorker' in navigator) {
navigator
.serviceWorker
.register('/sw-test/sw.js', {
scope: '/sw-test/'
})
.then(function(reg) {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
sw
注册之后,浏览器会尝试安装并激活它,安装完成之后会触发 install
事件,为了达到离线缓存的目的,需要使用一个新的存储 API
- caches
,这个 API
是 sw
上的一个全局对象,他可以用来存储网络请求过来的资源,与浏览器标准存储不一样的是,他是特定你的域的持久化缓存;
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
return cache.addAll([
'/sw-test/app.js'
]);
})
);
});
任何被 sw
控制的的被请求时,都会触发 fetch
事件,通过监听该事件可以控制请求的具体响应内容;
如上,安装成功后可以将一批指定的资源缓存起来,那么现在就可以拦截请求,然后将匹配到的缓存结果作为响应,或者重新请求新版的资源,甚至可以响应指定的内容,你拦截了,那么你说了算!
// 响应已缓存的请求
this.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
);
});
// 响应自定义内容
const res = new Response('<p>Hello, service worker!</p>', {
headers: { 'Content-Type': 'text/html' }
});
event.respondWith(res);
// 缓存获取失败重新请求最新的
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
Service Worker
如果刷新页面后有新版的 sw
,新版的会在后台安装,安装后并不会立即生效,当没有页面在使用旧的版本的 sw
时,新版的就会激活并响应请求;
// 更新到 v2 版本
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v2').then(function(cache) {
return cache.addAll([
'/sw-test/app.js'
]);
})
);
});
当有了新版本,旧版本还在运行的时候,为了避免缓存数据太多占满磁盘空间,需要对旧的缓存进行清理;通过监听 activate
事件,来对旧的缓存进行清理;
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['v2'];
event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (cacheWhitelist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
);
});
该文件里列出了将应用添加至桌面的所有配置信息,如果修改了该文件,已添加到桌面的应用样式不会改变,需要重新添加到桌面:
<link rel="manifest" href="/manifest.json" />
例如:
{
"name": "京东 PLUS 会员",
"short_name": "京东 PLUS",
"description": "随时随地分享新鲜事",
"icons": [
{
"src": "https://img10.360buyimg.com/img/jfs/t1/65477/24/1867/249268/5d03380eE9c52b872/a7a2864e42dde553.gif",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "https://img10.360buyimg.com/img/jfs/t1/65477/24/1867/249268/5d03380eE9c52b872/a7a2864e42dde553.gif",
"sizes": "512x512",
"type": "image/png"
}
],
"share_target": {
"action": "compose",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
},
"start_url": "/?standalone=1",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#F3F3F3",
"theme_color": "#F3F3F3",
"related_applications": [],
"prefer_related_applications": false
}
https
是在 http
的基础上对数据进行加密传输,涉及一次非对称加密与一次对称加密,即便传输过程中数据被劫持,只要私钥没有泄露,黑客也束手无策,所以,数据安全性非常高,HTTPS 数据传输过程
letsencrypt
是一家为全球网站免费提供 https
证书的机构,并且支持泛域名,非常值得推荐使用,大公司有钱的请无视;
certbot
是 letsencrypt
官方提供的一个用来获取和更新 https
证书的工具,https://certbot.eff.org/ 在它的网站上可以根据自己的系统及服务情况,具体选择如何使用,下面以 CentOS 7
和 Nginx 1.16.0
为例,看看如何免费为网站架上 https
;
certbot
客户端wget https://dl.eff.org/certbot-auto
chmod a+x ./certbot-auto
./certbot-auto --help
nginx
这一步用来验证域名是否可访问,后面该工具会在配置的 root
对应目录下创建临时的文件,如果不可以访问,将无权获取对应域名的证书;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /path/to/www;
}
location = /.well-known/acme-challenge/ {
return 404;
}
别忘了重启 nginx
:
sudo nginx -s reload
./certbot-auto certonly --webroot -w /path/to/www -d famanoder.com -d www.famanoder.com
此处 -w
为上一步 nginx
里配置的 root
,-d
是需要获取证书的域名,-d
可以多次使用,也可以直接生成泛域名的证书,-d *.famanoder.com
,良心之作啊,很多云服务上不仅收费贵,还不支持泛域名,一个域名花一笔钱,此处,大公司有钱的请无视!
证书生成成功后,可以选择是否自动续签,因为每次生成的证书有效期为 3 个月;
./certbot-auto renew --dry-run
通过这个命令可以测试上一步的自动续签是否可用;
也可以手动更新证书:
./certbot-auto renew -v
或者通过命令设置自动更新:
./certbot-auto renew --quiet --no-self-upgrade
listen 443 ssl;
server_name .famanoder.com;
index index.html;
root /path/to/www;
ssl_certificate /etc/letsencrypt/live/famanoder.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/famanoder.org/privkey.pem;
可另行查阅更多的如何在 nginx
上配置 https
;
以上对 pwa
做了基本的介绍,以及阐述了一个 pwa
应用所需的基础设施,接下来使用 offline-plugin
在项目里实战 pwa
;
首先,在我们应用的入口文件中将其引入,保证当前页面与 service worker
能够通信;
import(/* webpackChunkName: "offline" */'offline-plugin/runtime')
.then(offline => {
offline.install({
onUpdating: () => {
console.log('SW Event:', 'onUpdating');
},
onUpdateReady: () => {
console.log('SW Event:', 'onUpdateReady');
offline.applyUpdate();
},
onUpdated: () => {
console.log('SW Event:', 'onUpdated');
// window.location.reload();
// alert('有新版可用,是否刷新?');
},
onUpdateFailed: () => {
console.log('SW Event:', 'onUpdateFailed');
}
});
});
该插件会根据 webpack
打包生成的文件,生成 sw.js
文件,配置 webpack
:
// webpack.config.js
const OfflinePlugin = require('offline-plugin');
module.exports = {
// ...,
plugins: [
// ...,
new OfflinePlugin({
ServiceWorker: {
events: true
}
})
]
}
这样,我们的应用就能够支持 pwa
了,可在控制台查看相关信息;
使用 pwa
还有非常重要的一点,就是如何更新,如上所述,sw
在安装新版本后并不会立即激活,大多数时候都需要用户再一次刷新页面才会生效,当然,这个还跟具体的缓存策略有关,目前,观察一些 pwa
网站,会发现当有新版更新后,网站会通过一个模态框来提醒用户是否立即刷新页面使用最新版本,该插件的 runtime
里提供了 onUpdated
钩子,当最新版安装完成后会通知页面触发 onUpdated
,在这里我们可以调用模态框组件,提醒用户是否刷新页面使用最新版;
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.