PWA:( Progressiv 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
,在这里我们可以调用模态框组件,提醒用户是否刷新页面使用最新版;