🌑

Web 性能优化策略与实践

Read in English

Web 性能优化至关重要,实现方式也多种多样。这是我优化公司项目的笔记,总结出了几种可以在不同情况下应用的通用策略:

  • 性能分析(Profiling):在应用以下策略优化网站之前,我们需要了解网站的负载所在。Chrome 提供了一个 Profiling 工具来帮助我们做到这一点。
  • 缓存(Caching):利用缓存来减少加载时间。
  • 懒加载(Lazy Loading):通过仅在需要时加载资源来减少加载时间。
  • 接口集成(BFF):利用 BFF 来整合 API 调用。

性能分析(构建上下文)

  1. PV/UV(页面访问量/独立访客):我们可以先获取网站的 PV/UV,从而了解网站中每个页面的重要程度,哪些页面可能是负载瓶颈,以便后续决定是否要优化它们。
  2. FMP(首次有效绘制):从打开 URL 到用户可以开始使用它,需要多长时间?
  3. 性能分析:使用 Profiling 来分析打开网站的整个流程,找出哪些部分拖慢了网站速度。此部分不详细介绍,请参考:(分析运行时性能 - Chrome

经过这些步骤,我得出了以下数据:

  1. FMP:约 10000ms(极慢,这是我接手这个网站时的速度)
  2. 性能分析结果细分,初步诊断主要问题:
优先级 项目 FMP 耗时 可能的下一步
P1 UI 库初始化 300ms 1. 预加载
2. 懒加载首页未使用的部分。
P0 阻塞网络请求
- 阻塞第一部分:
- API A 请求 - 720ms
- 配置 API - 689ms
- 阻塞第二部分:
- 权限检查 - 1000ms
1600ms 1. 缓存
2. 懒加载
3. 并行请求执行
4. API 集成
P3 下载子应用 HTML 21ms -
P0 下载并运行子应用
1. UI 库初始化 800ms
2. 公共库 900ms
1700ms 1. 使用 Chrome 缓存。
P0 查询配置 1000ms 1. 整合并删除不必要的请求内容
2. 缓存
3. 预加载
P0 加载了不必要的组件 1210ms 1. 懒加载(动态导入)

缓存:高效减少加载时间

缓存是 Web 性能优化中的常用策略,尤其在处理高流量网站时。基于在大多数情况下空间不如时间宝贵的概念,将频繁访问的数据缓存在存储区域可以减少加载时间和服务器负载,使网站更具响应性和效率。

通过利用 Chrome 脚本缓存特性来减少重复公共库的下载
我们公司的微前端框架使用 eval 来加载子应用脚本:

1
2
3
4
5
6
eval(proxyWindow){
// ...code
}
eval(lodash) {
// ...code
}
OP 1

有一个更好的选项 script,它可以利用 Chrome 中的 JS 代码缓存特性来缓存编译后的代码。这样下次加载代码时:

  1. 我们不需要再次从远程下载它。
  2. 我们不需要再次编译它。

因此我们改为使用 script 作为加载微前端脚本和库的方式,并防止下载主应用和子应用共用的 UI 库等公共库。

1
2
3
4
5
6
7
8
9
10
// Execute Subapp's script
const iframe = createIframe();
patchWindow(iframe);
patchDocument(iframe);

scriptUrls.forEach(scriptUrl -> {
const script = document.createElement('script');
script.src = scriptUrl // Will create compile cache - Chrome feature
iframe.append(script);
});

懒加载:按需加载

懒加载是一种延迟加载某些资源直到真正需要时才加载的策略。这种方式可以显著减少初始加载时间并节省带宽,对于拥有大量图片或其他重型资源的网站尤其有益。

使用 React.lazy() 来延迟组件的加载

我发现有一些容器:

  1. 用户很少使用它们。
  2. 它们不影响用户在主页面上的工作流程。

因此在我进行性能分析时,它们没有必要在用户真正需要进入它们之前就加载(什么应该加载、什么应该延迟取决于用户的行为,这就是为什么我们在优化之前需要建立足够的上下文(收集数据、与产品经理和用户交流))。
所以我利用 Code-Splitting – React 来移除用户打开页面时对它们的加载:

1
2
const XXX = lazy(() => import('./containers/XXX'));
const YYY = lazy(() => import('./containers/YYY'));

请求时序

我的项目有一组如下的请求调用链:

config API 1 call -> config API 2 call -> sidebar info API call -> sidebar loading -> main list API call -> main list loading

然而,配置加载和侧边栏加载并不影响用户的操作,真正重要的是主列表。

所以我改变了请求时序,让主列表优先于其他内容加载:

main list API call -> main list loading -> config API 1 call -> config API 2 call -> sidebar info API call -> sidebar loading

这使得主列表的显示速度大大加快。

API 集成(BFF):减少请求以提升速度

由于历史原因,我网站的 API 相当陈旧:

  1. 功能相同的 API 被拆分:一些 API 做的是同样的事情,但需要用不同的参数调用,例如 getConfig(id) 会用不同的 id 被多次调用,这显著降低了浏览器中的网络请求速度,因为浏览器的网络请求池有一定的限制。
  2. 响应中有不必要的数据:API 服务于不同的网站,它们各有不同的需求。我网站的响应中有很多不必要的数据,这也减慢了网络请求速度。

在我的项目中,我实现了一个 BFF 层来整合和优化 API 调用。这涉及合并多个(14+)配置 API。

结果与总结

经过第一轮优化,我的网站在 FMP 上有了显著改善(从 10000ms 降低到 4000ms)。
优化网站的关键在于:

  1. 建立足够的上下文:你必须了解用户的行为、项目的瓶颈所在,以及优化方向,这样才能做出合理的权衡。
  2. 时序:我们不想在某些时刻加载不必要的资源(懒加载),同时我们也想提前为未来加载某些内容(预加载)。
  3. 缓存:时间通常比空间更宝贵,我们可以利用空间进行缓存,从而在未来不需要再花费时间资源。

, , — Apr 10, 2024