跳到主要内容
Huan Du

huandu.me

10 分钟阅读

PWA 的过去、现在和未来

PWA 想解决的是「网页也能离线用」这件事,但它真正的对手不是技术,而是适用场景——以及一项能在用户无感时运行的能力必然带来的滥用风险。

  • #engineering
  • #thoughts
  • #product

这篇文章是有感于 QCon Beijing 2017 的一个分享《如何打造自己的 PWA 应用》,希望在这个分享之外再扩展一下对 PWA 的自我思考。

首先推荐大家读一下这个 QCon 分享的 slides,写得非常详实,里面详细说明了 PWA(Progressive Web Application)的各种特性、折衷、现状和渐进增强的方法。分享中拿来做 demo 的那个 slides 制作工具,是钟恒(分享人)在 360 期间的一项半业余半正式的工作,一开始纯粹是为了验证 PWA 能力而写的,后来尝试产品化但最终失败了,比较可惜,不过这个过程也让他积攒了很多实打实的经验。

PWA 作为一项新技术,最需要关注的肯定是它打算要解决的问题。简单来说,PWA 希望提供一个前端完全可控的资源缓存机制,并且提供一系列方便 web 应用离线运行的基础能力,从而让 web 应用可以逼近原生应用的体验。

提到资源缓存,前端领域很早就有了浏览器缓存技术,这是加速网页浏览速度的最常用技术。在当年 JavaScript 还很羸弱的时代,仅通过瘦客户端(浏览器)和服务器之间的通信就可以很好地协商解决缓存问题。时至今日,这种缓存的工作机制依然是前端面试几乎必考的题目之一,也是前端性能优化的首要关注点。这种缓存有几个致命的缺陷,包括缓存时效无法控制、离线时无法使用等,使得缓存只能用于提升在线应用的访问速度。

为了让 web 应用真的能完全在离线状态下使用,Application Cache 出现了,但是由于一些设计失误,它在灵活性、安全性上有致命的缺点,易用性也不够好,火了一小段之后就渐渐消失了。当标准刚出现、Chrome 刚开始支持这个功能的时候,我正在开发基于 Chrome 的 H5 游戏,非常需要用这个能力来提升游戏加载速度,于是就用上了。除去当时碰到的各种浏览器 bug(比如缓存文件没有正常更新什么的),整体感觉是灵活性不足。

比如 manifest 文件用起来就挺麻烦。为了避免 CDN 错误地缓存老文件,我们得不断地给资源文件改名,每次更新资源需要更新页面对应的 manifest 文件,这使得 manifest 内容不断变化。由于 manifest 自己也放在 CDN 上面,为了避免 CDN 不更新还得修改 manifest 的文件名,于是就得每次都修改 html 里面指向的 manifest 文件名,于是 html 的内容又变化了,它的文件名又要改……这一来,好几个文件都被连锁影响,非常繁琐。

当时我就思考过使用同期出现的 Web Worker 来代替 Application Cache,通过异步加载资源并将内容存入 LocalStorage 里面,仅仅将 Application Cache 用于缓存 html 页面和 Web Worker 程序本身。听起来似乎挺美好,但这又遇到很多问题,比如跨域,当时(2011 年)CDN 支持跨域的还不多,Web Worker 又只能使用 XHR 来发送请求,遂作罢。

又过了许多年,Service Workers 终于出现了,这真是个比较完美的方案,基于 Service Workers 的 PWA 也就顺理成章地出现了。

一个 W3C 标准从制定到可用一般要经过很长的时间。主推 Service Workers 的公司是 Google 和 Mozilla,考虑到他们一向希望 web 应用更加强大,这很符合他们的一贯风格,他们也很快支持了最新标准。微软一向对新技术保持谨慎,考虑到 Edge 15 已经开始测试 Service Workers 这个功能,想必内部已经没有决策问题,正式发布只是时间问题。Apple 的态度则比较暧昧,一直说计划中,但至今没有谱,仔细想想,Apple 很可能在这件事情上动力不足,PWA 就像微信小程序一样,如果不加以阻止,App Store 的统治地位说不定就要受到威胁了。

在 Can I Use 上可以看到,当前也只是在 PC 上 Service Workers 的普及率还行(也不太能作为核心功能使用),移动端则一片红,比较惨了。幸好 PWA 天生 progressive,可以通过渐进增强的方法来尽可能把好处占到。从钟恒的 slides 可以看到一些渐进增强的思路和方法,比较 hack,在这里就不重复了。

从 Chrome 开发者工具里 Service Workers 的列表中可以看到,国内外不少公司已经开始使用它提升体验,大部分跟 PWA 关系不大。

比如 Twitter 的 Service Worker 基本功能就是接受推送消息并显示本地通知弹窗,最终打开对应的 Twitter 页面展示内容(入口代码在 PushServiceWorker 类)。YouTube 的实现也类似,由于代码混淆了没看懂,就不详细分析了,但从监听的事件来看,它并没有处理 fetch 事件,主要都在做一些跟 notification 相关的东西,猜测应该跟 Twitter 实现的功能类似。

国内真正使用了 PWA 的是百度网盘的 Service Worker,虽然混淆了但还是能轻松看懂,非常短非常经典,可以去读一下。

对于 PWA 和 Service Workers 的未来,现在还看不清楚,主要问题可能不是技术问题(能不能),而是一个适用场景问题(适不适合)。我觉得 PWA 肯定可以成为 H5 页面的标配,但 H5 本身却很难承载一个公司最核心的产品形态,只是一种补充。

数年前,我们已经看到 Facebook 宣称投入 H5 是一个“错误”的言论。当时我还是一个 Facebook app 开发者,看到了 Facebook 为了让 PC 版本应用搬到移动终端上做出的努力,当然最后还是失败了,主要是实在不好用,性能也扛不住,但可行性确实没问题。是不是说只要随着时间推移,浏览器足够给力,就能够重新在移动端上拥抱全 H5 的应用呢?我觉得也未必。我们看到 Apple 一直不支持开发者定制 web view,这种新标准、各种性能优化、各种 bug 修复何时能用上,谁都不知道。相比等待不确定的消息,还不如用 native 代码写出性能好又没有 bug 的业务代码。Android 这边虽然整体情况好些,但自身版本分裂的问题,加上并不是很多开发者具有定制浏览器内核的能力,将公司的未来赌在 H5 之上有些风险过大了。

相比用 PWA 在浏览器里实现移动应用,React Native 及其他类似方案则显得靠谱多了,毕竟它们没有放弃直接使用 native API 的能力。操作系统推出新能力后,浏览器一般得过一段时间才能反映到一些标准接口上,这个时间窗口很可能超过一年。业务是经不起这样的等待的,与其使用 PWA 这样的技术,还不如写个 hybrid 程序更方便呢。有了 hybrid,Service Workers 里面引以为傲的功能也就不稀奇了,native 很容易就能实现一些 JS Bridge 接口来帮助浏览器离线运行;与其遵循规范,不如自己定义一个简化版接口让接口更简单。要知道,Service Worker 运行的环境要求极为苛刻,无论是 context、加载脚本的方式(importScripts),还是与业务代码通信等,都有各种限制,我们如果只是需要缓存一下资源,这些限制都是没什么意义的。

PWA 似乎本身就没有把取代原生 app 作为目标。在 Why build a Progressive Web App? 里提到,这些吸引人的特性建立在“一个产品已经在用 H5、且运行于浏览器之中”这个前提上,是 PWA 能够额外带来的好处,这里并没有劝说移动应用开发者转而投入 PWA 的怀抱。

如果要说 PWA 有什么不好的地方,安全性可能算一个。Service Worker 从设计上就要求页面跑在 HTTPS 之下,这条硬性约束从一开始就有;但在早期,除了这一条,浏览器几乎没有给它加更多限制——注册一个 Service Worker 不需要用户授权,用户也看不到它的存在,网页关闭之后它还能被 push、background sync 这类事件唤醒,继续在后台运行。这种“用户完全无感”的能力很快就被用到了歪路上,最典型的是挖矿:安全研究里已经有一类明确的攻击面叫恶意 Service Worker,攻击者注册一个恶意 Service Worker,就能把访问者的设备变成一台挖矿机,也可以拿它搭出 web 层的僵尸网络、发起 DDoS。用户能感觉到的,只有风扇变响、电量掉得飞快。浏览器厂商后来逐步做了缓解:Service Worker 现在空闲时会被终止,只在事件到来时被唤醒,长期驻留比当年难得多。类似的事情或许也会发生在 PWA 的其他能力上,一旦发生,那些强依赖 PWA 的公司肯定会受到极大伤害,一个理智的商业公司不会允许这样的风险存在。

这种担心不是空穴来风,WebSocket 就留下过两次教训。第一次出在协议本身:早期草案没有处理好透明代理的场景,被认为可能被用来投毒缓存,Chrome 等浏览器紧急发版把 WebSocket 临时禁用,直到协议层面把这个洞补上。第二次出在握手环节:WebSocket 不受同源策略约束,服务端如果不校验 Origin,攻击者就能借用用户的登录态建立连接,这就是跨站 WebSocket 劫持(CSWSH)。两次都不是“技术本身不好”,而是能力给出去之后,边界没有同时划清楚。

参考链接

评论

  1. 正在加载评论…

发表评论

只支持纯文本:内容会原样显示,不会解析 HTML、Markdown 或链接。