How to make webview faster (I)

作者 Zhe Wang 日期 2017-12-21
web
How to make webview faster (I)

前段时间一直在进行hybrid app的调优工作,主要工作集中在webview的优化。工程实践虽然离不开方法论的指导,但到了具体实施仍然千差万别。webview优化存在典型的加载时间与优化难度负相关的关系。这次调优,我们也分别从纯前端层面以及Xcode/Java层面进行双向优化的工作。相较而言,纯前端优化有更多传统、经典的方法论作为指导,效果更容易获取。而Xcode/Java层,就需要更多的借鉴和自我创新。今天这篇文章,记录下前端,既纯h5层面可以优化的部分思路。

前端篇

深入理解Yahoo 14 Golden Principles

大名鼎鼎雅虎十四条前端优化黄金法则,出自一位雅虎工程师所著书籍《High Performance Web Sites: Essential Knowledge for Front-end Engineers》。十四条法则如下:

  1. Make Fewer HTTP Requests
  2. Use a Content Delivery Network
  3. Add an Expires Header
  4. Gzip Components
  5. Put Stylesheets at the Top
  6. Put Scripts at the Bottom
  7. Avoid CSS Expressions
  8. Make JavaScript and CSS External
  9. Reduce DNS Lookups
  10. Minify JavaScript
  11. Avoid Redirects
  12. Remove Duplicate Scripts
  13. Configure ETags
  14. Make Ajax Cacheable

这本书是非常经典的书籍。初步看,很多建议都是老生常谈,但要知道,这本书出自2007年,那个时候,整个前端届甚至没有工程化的概念,因此这本书提出的优化方法非常具有前瞻性。比如第十二条Remove Duplicate Scripts, 如今我最常使用的webpack中比较核心的插件CommonChunkPluginDllReferencePlugin(后面会提到这两个插件)都来自于这条思路。

这本书的一个指导思想是,在一个页面的响应过程中,前端所消耗时间的占比往往远多于后端,前端优化相比于后端优化,所付出的代价又相对少很多。因此对前端进行优化是性价比非常高的选择。

当然,随着移动h5的普及,其中的一些法则并不一定非常适用,比如现在很多webview的加载都是采用离线包的方式(比如我们做的app),一些建议例如采用CDN(主要为了多domain以增大请求并发)实际上就用不着了。但对于大部分页面的访问,尤其是PC端浏览器,只要http协议没有翻天覆地的变化,这些黄金法则就不会失效。

权衡直出与前后端分离

经过几年前后端分离理念的盛行,直出又慢慢回到了大家的视野中。对于加载速度有偏执要求的,直出仍然是一种非常高效的方案。在过往,直出一直被诟病的一个问题是需要从backend controller中请求太多的数据,考虑到网络资源还不是那么充沛的年代,确实有道理。到了如今, 大多数情况下,网络并不是一个问题。当然,很多用户体量大的App应该根据网络状况做了区分。

直出:

Download HTML -> Parse HTML and Render

前后端分离:

Download HTML -> Parse HTML -> Download JavaScript or Read from Cache -> Parse JavaScript -> Render

相信不少框架都有React这类方案

import ReactDOMServer from 'react-dom/server';
ReactDOMServer.renderToString(element);

利用同构,将前后分离切换为后台直出,代价并不大。

压缩、按需加载、公共模块抽离

这里我推荐一篇文章《Webpack 打包优化之体积篇》,可以学习学习。

webpack的uglify,压缩效率还是非常明显的。

另外,就是许多第三方库要记得按需加载,比如antd-mobile,对于体积的影响还是非常大的。

针对commonChuckPlugindllReferencePlugin,越来越多的界面就只有单个bundle输出,通过commonChuckPlugin抽离出vendor实际上没有什么意义,单纯把一个文件进行了拆分却没有影响到总体体积,因为commonChuckPlugin只能对指定的js文件进行公共模块抽取,但dllReferencePlugin就不同,它是思路是先通过额外的配置文件输出一个“动态链接库”的公共库文件dll.js以及配置文件manifest.json,随后webpack在编译过程中,对于manifest.json出现的模块就不再额外编译、输出在bundle.js中。具体可以参考《彻底解决Webpack打包性能问题》

额外提下async和defer

个人感觉javascript的asyncdefer属性在如今工程化构建js的年代可以发挥的作用已经越来越小。先来看一下asyncdefer属性下js的加载过程。
async
首先看async,盲目使用async是非常危险的,对于与其他js有强依赖关系的(jquery.js, vendor.js, etc)或者需要挂载到指定DOM节点的(例如react的最终输出js),async属性几乎是禁忌,因为js的执行节点不可预测。坦白说,之前我们的离线包就犯过这种错误,提前脚本标签同时附上async属性,虽然绝大多数情况html的解析会快于js的解析,但依然会有白屏现象发生。

关于defer,倒是可以适当的使用,但js往往会从缓存读取,带来的速率提升可以说微乎其微。

上述的黄金十四条,关于脚本的放置的建议是

Put Scripts at the Bottom

网上有种说法是将script标签放在body闭合标签之前,在老浏览器上效果更佳。当然具体放在Bottom的哪里不在我们的讨论范围之内。这么做的缺点也就显而易见了,不管时间长短,白屏几乎是无可避免的。之前的一种实践方案是,使用ReactDOMServer.renderToString(element)提前拿到一些纯静态标签,放置在html文件中,用以替代一部分的白屏状态。

Tradeoff

其他的一些优化方案,其实都无不逃不出上述的黄金法则。但优化也是个权衡的过程,比如cache,IOS下wkwebview对于离线文件加载并没有针对性的缓存策略,你愿意为此深入了解wkwebview并付出多少精力。比如雪碧图,你愿意为此用ps做雪碧图同时修改css付出多少时间。

因此,还是说下移动端、h5场景下几个值得重点关注的优化方案

  1. 考虑是否使用直出,直出带来的效果最显而易见
  2. 尽可能压缩js,当然还包括css,但是前者更重要
  3. 使用缓存

Trailor

下篇文章准备说说我们是如何在Xcode/Java做的优化,更具有挑战性。