探究 PWA 的实现与应用 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
famanoder
V2EX    前端开发

探究 PWA 的实现与应用

  •  
  •   famanoder
    famanoder 2019-07-27 16:33:52 +08:00 3435 次点击
    这是一个创建于 2269 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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

    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,这个 APIsw 上的一个全局对象,他可以用来存储网络请求过来的资源,与浏览器标准存储不一样的是,他是特定你的域的持久化缓存;

    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); } })); }) ); }); 

    Manifest.json

    该文件里列出了将应用添加至桌面的所有配置信息,如果修改了该文件,已添加到桌面的应用样式不会改变,需要重新添加到桌面:

    <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

    https 是在 http 的基础上对数据进行加密传输,涉及一次非对称加密与一次对称加密,即便传输过程中数据被劫持,只要私钥没有泄露,黑客也束手无策,所以,数据安全性非常高,HTTPS 数据传输过程

    letsencrypt 是一家为全球网站免费提供 https 证书的机构,并且支持泛域名,非常值得推荐使用,大公司有钱的请无视;

    certbotletsencrypt 官方提供的一个用来获取和更新 https 证书的工具,https://certbot.eff.org/ 在它的网站上可以根据自己的系统及服务情况,具体选择如何使用,下面以 CentOS 7Nginx 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 做了基本的介绍,以及阐述了一个 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,在这里我们可以调用模态框组件,提醒用户是否刷新页面使用最新版;

    2 条回复    2019-07-28 21:42:05 +08:00
    zhangkc
        1
    zhangkc  
       2019-07-27 18:26:13 +08:00 via iPhone
    给个
    julyclyde
        2
    julyclyde  
       2019-07-28 21:42:05 +08:00
    散文啊,啥内容都有
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2837 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 00:20 PVG 08:20 LAX 17:20 JFK 20:20
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86