- 原文地址:A Tinder Progressive Web App Performance Case Study
- 原文做者:Addy Osmani
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:pot-code
- 校对者:zwwill
最近 Tinder 在 web 端发力,公布了 PWA 应用 Tinder Online ,现已全平台支持。在技术实现上,为了应对 JavaScript 性能优化问题 采用了最前沿的技术,例如,使用 Service Workers 来应对网络弹性问题、使用 消息推送(Push Notifications) 来支撑聊天业务。本文将向各位讲解大佬们是如何处理开发中的性能优化问题的。javascript
Tinder Online 肩负着打开新市场的使命,它背后的团队但愿把它打形成一个全平台无缝体验的在线聊天平台。php
产品的 MVP 开发花了 3 个月,UI 库用了 React,状态管理用的是 Redux。最后的成果仍是显著的,在不影响功能体验的前提下,数据传输量减小到了原来的十分之一:css
上图是 Tinder Online 和手机 app 在安装过程当中所需数据量大小的比较,虽然这两个类型不一样,可是仍是具备参考意义的。 相对手机 app 来讲,PWA 只有在须要时才加载新的代码。用户边使用边下载,由于下载过程分散在整个使用过程当中,因此用户并不会察觉到。即便用户在使用中访问了应用的其余部分,综合下载量也仍是少于直接下载整个 app 所需的数据量。html
上线后的前期表现仍是不错的,用户划一划频率、发信频率以及在线时长均优于在手机 app 上的表现。总之,使用 PWA 以后(针对 PWA 和原生的比较):前端
数据显示,手机端用户主要使用的设备包括但不限于:java
再使用 Chrome User Experience report (简称 CrUX)进行分析,得知用户主要使用 4G 网络进行访问:react
注:能够参考 Rick Viscomi 在 PerfPlanet 上对 CrUX 的介绍,Inian Parameshwaran 介绍的 rUXt 在网络可视化分析这块针对大流量网站更具优点。android
在经常使用的 web 应用测试网站(WebPageTest 和 Lighthouse)上进行测试,使用 4G 网络的 Galaxy S7 能够在 5秒内 彻底加载应用:webpack
相比高端机型,配置 相对通常 的机型(例如 Moto G4)的性能表现还有很大的提高空间,这类机型的 CPU 资源比较吃紧:ios
Tinder 如今也确实在针对这方面作优化,期待他们之后的表现。
Tinder 为了加快页面的加载使用了不少技术,例如基于路由的代码分割、性能预算(performance budgets)以及静态资源持久缓存。
起初打包的文件很是巨大,严重拖慢了交互就绪的速度,对用户体验影响很大。由于打包的文件里包含了除核心交互之外的代码,这就须要使用 代码分割(code-splitting) 抽离出暂时不须要的代码,只保留核心功能。
针对这个问题,Tinder 引入了 React Router 和 React Loadable。得益于前期架构的优点,整个应用很是适合使用基于配置的方式处理路由和渲染,所以,代码分割也能很顺利的在顶层上实现。
小结:
React Loadable 是 James Kyle 开发的一个轻型库,简化了 React 中基于组件的代码分割操做,它提供的 Loadable 函数是一个高阶(higher-order)组件(建立组件的函数),专门用于处理代码分割。
下面举例说明。假若有两个组件 “A” 和 “B”,分割前,它们和入口文件一并打包,由于这两个模块目前并不须要,因此这样所有打包在一块儿是很低效的:
这里要求使用代码分割以后,组件 A 和 B 只是在须要时才加载。为此,Tinder 引入了 React Loadable、动态导入函数 import() 以及 webpack 神奇的注释语法 (用来为动态导入的模块命名):
在公共库(即 vendor)的处理上,Tinder 使用了 webpack 官方提供的 CommonsChunkPlugin 插件,把路由间公用的库单独打包到一个文件中,利用浏览器的缓存机制提高性能:
接着使用 React Loadable 提供的预加载功能 针对那些/在接下来的交互中/存在潜在加载需求/的资源/作预加载处理(译者注:注意断句):
Tinder 还用 Service Workers 对全部路由作了预缓存处理,其中就包括用户常常访问而没有作代码分割的路由。最后使用 UglifyJS 对代码进行压缩:
new webpack.optimize.UglifyJsPlugin({
parallel: true,
compress: {
warnings: false,
screw_ie8: true
},
sourceMap: SHOULD_SOURCEMAP
}),
复制代码
使用代码分割后,打包文件大小从 166KB 减到 101KB,DOM 内容加载时间(DCL)也从 5.46s 下降到 4.69s:
在 webpack 的输出中配置 [chunkhash],一方面能够用来规避开发过程当中因浏览器缓存而引起的资源不更新问题,二来也能够确保缓存有效。
因为 Tinder 使用了大量的第三方开源库,若是其中的一个依赖发生变化都会致使 [chunkhash] 的从新计算,从而致使以前的缓存失效。为了解决这个问题,Tinder 制定了一个 外部依赖白名单,另外还将 manifest 从主干中抽出,进一步改进缓存。最后,主干代码和 manifest 的打包大小都只有 160KB。
这里用到了 <link rel=preload>
,它告诉浏览器这是一个关键资源,立刻要用到,须要尽早加载。在单页应用(SPA)中,这些资源能够是打包后的 JavaScript 文件。
Tinder 将核心体验相关的资源文件进行了预加载处理,最终加载时间减小了 1s,首次绘制时间也从 1000ms 减小到 500ms。
为了达到移动端的性能指望,Tinder 引入了 性能预算。Alex Russell 在他发表过的一篇文章(Can you afford it?: real-world performance budgets)中指出:难就难在要在网络环境很差、配置也通常的移动设备上提供良好的用户体验,由于提高空间很是有限。
为了保证应用能快速就绪,Tinder 规定公共依赖和主干代码的大小要维持在 155KB 左右,分配给异步加载(懒加载)的数据量大约 55KB 左右、其余部分代码 35KB 左右,CSS 则限制在 20KB 左右。 这个规划保证了最坏状况下的性能表现。
开源工具 Webpack Bundle Analyzer 能够对依赖进行可视化分析,暴露出那些明显的可优化问题。
Tinder 主要用它来分析如下几类优化问题:
这个工具能够搭配 Webpack 的 Lodash Module Replacement 插件 使用。这个插件将模块中使用到的一些特定方法替换成实现上更简单、功能上等效的代码,从而减少打包文件大小:
Webpack Bundle Analyzer 能够集成到 Webpack 的配置中。来自 Tinder 的配置参考:
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerPort: 8888,
reportFilename: 'report.html',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null
})
复制代码
通过一系列优化以后,剩下的主要是主干部分代码。这些代码暂时就不用动了,除非架构发生变化。
Tinder 使用 Atomic CSS 建立了许多高复用的 CSS 样式,其中一些作了内联处理,在初始化绘制过程当中生效,剩下的则分散在外部 CSS 文件中(包括动画或者 base/reset 等样式)。关键样式(Critical styles)使用 gzip 压缩以后大小不超过 20KB,最近的一次构建则压缩到了 11KB 如下。
Tinder 还使用了 CSS stats 和 Google Analytics 跟踪每次发版的变化。在使用 Atomic CSS 前,页面的平均加载时间在 6.75s 左右,使用以后降到 5.75s 左右。
最后用 Autoprefixer 添加浏览器前缀:
new webpack.LoaderOptionsPlugin({
options: {
context: paths.basePath,
output: { path: './' },
minimize: true,
postcss: [
autoprefixer({
browsers: [
'last 2 versions',
'not ie < 11',
'Safari >= 8'
]
})
]
}
}),
复制代码
使用 requestIdleCallback() 将非关键事务推迟到空闲期运行,以提升运行时性能。
requestIdleCallback(myNonEssentialWork);
复制代码
什么叫非关键事务?好比作插桩标记(instrumentation beacons)。Tinder 团队为了减小刷屏时的绘制次数,对合成层(composite layers)也作了优化。
在刷屏操做中,是否使用 requestIdleCallback() 的对比:
使用前..
使用后...
Webpack 3 + 做用域提高
在 webpack 3 之前,每一个模块在打包后都会被包裹进一个独立的闭包内,可是这些包裹起来的方法(闭包)执行效率很低。对此,Webpack 3 推出了“做用域提高”机制,将多个模块打包进一个闭包中(至关于合并做用域),以提升执行速度。使用这一功能须要引入 Module Concatenation 插件:
new webpack.optimize.ModuleConcatenationPlugin()
复制代码
使用做用域提高机制后,Tinder 第三方依赖初始化解析时间减小了 8%。
React 16
React 16 优化了打包以后的文件大小,新的打包算法(Rollup)会剔除一些暂时用不到的代码。
Tinder 从 React 15 升级到 React 16 后,gzip 压缩后的依赖大小减少了约 7%。
最后,react + react-dom 压缩后从以前的 50KB 降到如今的 35KB 左右,效果拔群。这里要特别感谢 Dan Abramov、Dominic Gannaway 和 Nate Hunzaker 的付出,他们在 React 16 中为下降打包文件大小作了很多贡献。
Tinder 还用了 Workbox Webpack 插件,用于缓存 Application Shell 和核心静态资源,例如主干代码、第三方库、manifest 和 CSS。使用后,具有了较强的频繁访问抗压能力,同时用户再次使用时,启动速度也大大提升了。
用 source-map-explorer (又一个包分析工具)再次对包进行深刻分析,发现还有继续缩小网络负载的优化空间。在登录场景中,用户还没登陆的状况下,Facebook 图片、通知、私信以及验证码这些组件并不须要加载,将这些组件从关键路径(critical path)移除以后可为主干代码省下 20% 的数据量:
由于上一步移除了 Facebook 的组件,因此其相关依赖 Facebook SDK 也能够直接移除,后面须要用到的时候再进行加载(懒加载),这样就又节省了 200KB,并且初始加载时间也减小了 1 秒。
虽然 Tinder 还在继续迭代他们的产品,可是已经初见成效了,你能够随时访问 Tinder.com 关注最新进展。
感谢并祝贺 Roderick Hsiao、 Jordan Banafsheha 以及 Erik Hellenbrand,恭喜大家成功发布了 Tinder Online,谢谢大家对个人文章提出的指导意见,还有 Cheney Tsai,你的观点给了我不少启发。
相关阅读:
本文转载自 Performance Planet。 若是你对 React 还不熟悉,能够参考我推荐的教程:React for Beginners,对新手十分友好。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。