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