Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

极致Web性能 —— SPA性能指南 #14

Open
yesvods opened this issue Feb 12, 2018 · 3 comments
Open

极致Web性能 —— SPA性能指南 #14

yesvods opened this issue Feb 12, 2018 · 3 comments

Comments

@yesvods
Copy link
Owner

yesvods commented Feb 12, 2018

前言

前端框架时代,为开发体验、效率与页面性能带来,非常大的革命。大家纷纷拿起一系列打包工具(webpack/parcel etc.),配合一系列加载器快速搭建起一个 SPA 页面。

SPA 应用带来的好处非常明显;

  • 提升页面切换体验
  • 降低切换时间
  • 易于部署&前后端分离

但是也带来一系列性能问题:

  • 初始加载脚本较大
  • 首屏空白时间较长
  • 页面返回时,数据被动重新拉取

这些问题是使用 SPA 模式不可避免的,通过了解 SPA 加载运行过程,可以逐渐看清楚引起性能问题的根本原因,通过精细化应用加载,来解决这些问题。

SPA 之殇

愈发发福

比起一般的简单页面,SPA 最大的问题,就是在初始化之时引入大量框架方案脚本,这导致脚本体积随着项目发展体积愈发增大。

不仅仅是体积

很多人会关注脚本的加载体积,通过一系列方案来提升缓存命中率,减少脚本请求次数。在网络环境较差的移动端,尽量减少请求时间意义很大。

但这不是银弹,移动设备对脚本的解析、编译、执行性能较差(脚本加载参考《图说舌尖上的脚本》),即便可以完全利用缓存,执行时间也是性能一大瓶颈。

Keep SPA Fit

性能优化原则:贫则独善其身,富则兼济天下。

如何维护一个大型 SPA?

随着项目不断发展,页面不断增加,源源不断的第三方组件&工具库加入到Bundle里面,良好的 SPA 架构可以保证大型 SPA 项目依旧保持极致的性能与体验。下面介绍一个优秀性能&体验 SPA 具备的特性:

性能优化

1. 快速启动 —— 极大提升加载速度(important)

快速启动应用,并行发起 Bundle 加载&拉取初始数据。相信大家已经发现了,SPA 初始化时候,不得不等待 bundle 返回并执行后,才会发起数据加载。

由于在移动设备上(即便有缓存)bundle 加载极为耗时,我们可以充分利用这段时间将数据进行预加载。这项特性,使得后面的优化起到更加明显的效果。

如下示例代码:

// app.js
Promise.all([load('bundle'), load('data')])

2. 根据路由拆分 —— 减少初始加载体积

利用异步加载方式,在路由注册时提供异步拉取组件的方法,仅在需要进入对应路由时,对应组件才会被加载进来。

route({
  Home: () => import('@/coms/home'),
  About: () => import('@/coms/about')
})

3. 独立打包异步组件公共 Bundle —— 提高复用性&缓存命中率

HomeAbout等路由里面,可能公用一套 UI 组件,若不将异步加载公用组件统一打包,每次加载 路由时,都会额外加载一套 UI 组件。通过将公用组件提取打包成Vendor,可以减少下次进入路由加载体积与时间。

BTW:在webpack < 4时,依旧需要手动维护异步加载组件公用组件。webpack4提供更丰富的异步组件抽离方案

4. 组件预加载 —— 减少页面切换时间

当首屏加载完毕后,设备&网络处于空闲状态,可以对其他路由组件进行预加载,以便提升页面切换性能。

预加载是一个非常繁琐的过程,我们可以设计一个极小启动器,在页面渲染后快速预加载后续组件:

// 所有包含Page的路由组件均会被预加载
boostraper.loadMatch('Page')

5. 使用 ESM 语法 —— 按需打包工具库,降低 Bundle 体积

webpack4在 ESM tree shaking上做了极大优化,使得在引用工具库时候真正做到"按需打包",这要求无论是自己开发的工具库,抑或使用第三方工具库,打包&使用 ESM 版本非常必要。

6. 配合 PWA 使用,口感更佳 —— 降低首屏渲染时间,极大提升体验

根据 PWA 缓存策略,可以将访问的页面index.html缓存起来,下次打开时候优先利用缓存,再发起请求更新缓存。这使得 SPA 应用几乎不需要额外时间便可加载应用首屏文档流。

体验优化

1. 构建你的极简Skeleton Page

SPA 首屏加载面临较长时间白屏,骨架图是一个完美的"缓兵之计"。在谷歌研究员的文章 中有提到,骨架图对用户体验有极大的提升:

  • 快速展示
    • 配合 PWA 首屏缓存,骨架图可实现瞬间加载&展示,首屏视觉上有冲击性地提升
  • 稳定加载
    • 消除页面初始加载因多次重绘&资源加载导致的"抖动"

需要注意的是,骨架图应尽量保持足够小巧与简单,以确保不会严重影响页面后续加载。

2. 页面切换 Loading

无论如何优化性能加载,在页面切换时候依旧需要获取页面数据,若处理不好,可能会在数据返回前有短暂的不友好"空白"。通过以下方式可以很好处理这个问题:

  • 友好的切换前 Loading
    • 在确保组件&数据加载完毕前,可保证页面可交互性,减少用户阻塞感
  • 转场动画
    • 在大多数原生应用,转场动画属于标配
    • 即时组件&数据已经完全加载,在切换至新页面瞬间,依旧需要页面渲染时间,这段时间可能导致页面短暂空白或者"视觉阻塞"
    • 通过转场动画时间,可以很好地缓解这个问题,大多数页面保证在转场动画完毕之后依然渲染完毕

最后

除了上述提到的 SPA 优化方案,Web 性能基础也是必备的基石(如域名收敛、合理文档结构)。性能优化本质是一个页面精细化监控运营的过程,也要求我们对 Web 加载的过程与逻辑有更多的思考与理解。

@ghost
Copy link

ghost commented Mar 5, 2018

// 所有包含Page的路由组件均会被预加载
boostraper.loadMatch('Page')
这个怎么实现呢?

@terribleness
Copy link

// router.js
const Index = () => import('../page/index')
// 预加载
router.options.routes.forEach(a => {
for (let ckey in a.components) {
if (Object.prototype.toString.call(a.components[ckey]) == "[object Function]") {
a.components[ckey].call();
}
}
})

@yesvods
Copy link
Owner Author

yesvods commented Mar 5, 2018

可以自行设计一个小型加载器。

也通过loder方便实现,只要将资源加载交给loder处理,就可以控制资源组件的加载时机&时序

http://ldocs.scoii.com/docs/LazyLoading.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants