How to make webview faster (I)2017-12-21
前段时间一直在进行hybrid app的调优工作,主要工作集中在webview的优化。工程实践虽然离不开方法论的指导,但到了具体实施仍然千差万别。webview优化存在典型的加载时间与优化难度负相关的关系。这次调优,我们也分别从纯前端层面以及Xcode/Java层面进行双向优化的工作。相较而言,纯前端优化有更多传统、经典的方法论作为指导,效果更容易获取。而Xcode/Java层,就需要更多的借鉴和自我创新。今天这篇文章,记录下前端,既纯h5层面可以优化的部分思路。
前端篇
深入理解Yahoo 14 Golden Principles
大名鼎鼎雅虎十四条前端优化黄金法则,出自一位雅虎工程师所著书籍《High Performance Web Sites: Essential Knowledge for Front-end Engineers》。十四条法则如下:
- Make Fewer HTTP Requests
- Use a Content Delivery Network
- Add an Expires Header
- Gzip Components
- Put Stylesheets at the Top
- Put Scripts at the Bottom
- Avoid CSS Expressions
- Make JavaScript and CSS External
- Reduce DNS Lookups
- Minify JavaScript
- Avoid Redirects
- Remove Duplicate Scripts
- Configure ETags
- Make Ajax Cacheable
这本书是非常经典的书籍。初步看,很多建议都是老生常谈,但要知道,这本书出自2007年,那个时候,整个前端届甚至没有工程化的概念,因此这本书提出的优化方法非常具有前瞻性。比如第十二条Remove Duplicate Scripts, 如今我最常使用的webpack中比较核心的插件CommonChunkPlugin、DllReferencePlugin(后面会提到这两个插件)都来自于这条思路。
这本书的一个指导思想是,在一个页面的响应过程中,前端所消耗时间的占比往往远多于后端,前端优化相比于后端优化,所付出的代价又相对少很多。因此对前端进行优化是性价比非常高的选择。
当然,随着移动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这类方案
1 | import ReactDOMServer from 'react-dom/server'; |
利用同构,将前后分离切换为后台直出,代价并不大。
压缩、按需加载、公共模块抽离
这里我推荐一篇文章《Webpack 打包优化之体积篇》,可以学习学习。
webpack的uglify,压缩效率还是非常明显的。
另外,就是许多第三方库要记得按需加载,比如antd-mobile,对于体积的影响还是非常大的。
针对commonChuckPlugin与dllReferencePlugin,越来越多的界面就只有单个bundle输出,通过commonChuckPlugin抽离出vendor实际上没有什么意义,单纯把一个文件进行了拆分却没有影响到总体体积,因为commonChuckPlugin只能对指定的js文件进行公共模块抽取,但dllReferencePlugin就不同,它是思路是先通过额外的配置文件输出一个“动态链接库”的公共库文件dll.js以及配置文件manifest.json,随后webpack在编译过程中,对于manifest.json出现的模块就不再额外编译、输出在bundle.js中。具体可以参考《彻底解决Webpack打包性能问题》。
额外提下async和defer
个人感觉javascript的async或defer属性在如今工程化构建js的年代可以发挥的作用已经越来越小。先来看一下async和defer属性下js的加载过程。
首先看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场景下几个值得重点关注的优化方案
- 考虑是否使用直出,直出带来的效果最显而易见
- 尽可能压缩js,当然还包括css,但是前者更重要
- 使用缓存
Trailor
下篇文章准备说说我们是如何在Xcode/Java做的优化,更具有挑战性。