首屏加载速度优化

使用 React.lazy 优化首屏展示,减少白屏时间。

优化前后对比

首先简单看一下优化的前后的对比,数据为取 6 次的平均值。

优化前

7.8 MB transferred
8.0 MB resources
DOMContentLoaded: 5.18 s
Load: 6.33 s

展示分为两个阶段:白屏 -> 所有内容

直接展示所有内容
b1

优化后

4.6 MB transferred
4.7 MB resources
DOMContentLoaded: 1.34 s
Load: 4.54 s

展示主要分为四个阶段:白屏 -> 顶栏 -> 内容区 -> 反馈按钮(右下角)

出现顶栏
1
出现内容区
2
出现反馈按钮
3

通过数据和截图可以很明显看出,优化后首屏加载资源更少,展示内容更快。

开始

目前项目对首屏加载主要存在两个问题

  • 只对文件进行了分割(splitChunks),进入页面仍需加载所有文件
  • Suspense 位置不合理

主要针对第一点进行优化,第二点只是在第一点的基础上让用户更快的看到部分已加载的内容而已。

懒加载

针对第一点,主要依赖 React.lazy 进行懒加载优化。
React.lazy 使用方法可以参考官方文档
原理可以参考Code Splitting

因为不需要考虑网速特别差的网络,所以仅做以下 4 点优化就可以获得比较好的体验(需要极致体验可以把所有子路由都懒加载,过多网络请求反而可能影响速度,不推荐)

  • 减少不必要的依赖
  • 懒加载主要的路由组件
  • 懒加载较大的第三方库

懒加载主要的路由组件

例如 prado 由三大模块组成,对应三个组件

  • 任务调度
  • 资源调度
  • 资源管理

那么就对这三个组件进行懒加载就好了

import ResourceBasedScheduling from "components/App/ResourceBasedScheduling"; // 资源调度
import ResourceBasedManagement from "components/App/ResourceBasedManagement"; // 资源管理
import TaskBasedScheduling from "components/App/TaskBasedScheduling"; // 任务调度

// 改成
const ResourceBasedScheduling = React.lazy(() =>
  import(
    /* webpackChunkName: "resourceScheduling" */ "components/App/ResourceBasedScheduling"
  )
);
const ResourceBasedManagement = React.lazy(() =>
  import(
    /* webpackChunkName: "resourceMgmt" */ "components/App/ResourceBasedManagement"
  )
);
const TaskBasedScheduling = React.lazy(() =>
  import(
    /* webpackChunkName: "taskScheduling" */ "components/App/TaskBasedScheduling"
  )
);

懒加载较大的第三方库

使用 webpack-bundle-analyzer 或直接在 node_modules 下运行命令 du -d 1 -h | sort -h,看看哪个第三库比较大,则对其相关组件进行懒加载。

如下是 prado 的:
28M ./@antv
32M ./react-icons
41M ./echarts
47M ./@ant-design
51M ./ace-builds
52M ./typescript
70M ./infrad

可以看到除了必须的 infrad 和 @ant-design 外,还有几个比较大的第三方库 @antv、echarts 和 ace-builds。

以 echarts 为例,代码中仅有一个组件 TaskBarChart 对 echarts 有依赖,在所有使用 TaskBarChart 的地方都改成动态引入就好了:

import TaskBarChart from "components/Common/TaskBarChart";

// 改为
const TaskBarChart = React.lazy(() =>
  import(/* webpackChunkName: "taskChart" */ "components/Common/TaskBarChart")
);

此外,还可以在 webpack 的 splitChunks 中将 echart 单独分割出来,方便观察是否懒加载成功

splitChunks: {
  cacheGroups: {
    echarts: {
      name: 'echarts',
      test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/
    }
  }
}

PS: infrad 的 @ipg/feedback-client build 后很大,需要动态引入

减少依赖的文件

针对第一点,还可以主动减少依赖的第三方文件。

使用支持 es module 的第三方库

例如 lodash 改用 lodash-es(antd 依赖了 lodash,所以这一步不做也没差),或者使用按需引入的写法,如 echarts。
webpack 可以自动进行 tree shaking,减少库还能加快下载依赖的时间,好处++

删除功能重复的库

例如 antd 和 infrad、lodash 和 ramda

优化 Suspense 组件的位置

第二点比较好优化,优化 Suspense 的位置即可。

现在项目一般都由三个部分组成,分别是顶栏,侧边栏和内容区域,对着三个部分使用 Suspense 进行包裹即可,可以做到尽快展示页面内容的目的。

 <Suspense fallback={<SuspenseFallback />}>
  <CommonStyledLayout>
    <SidedMenu listMenu={listMenu} extra={<ApplicationSelect />} />
    <CommonStyledContent>
      <ConfigProvider getPopupContainer={node => node}>
        <Switch>
          <Route path={APPLICATIONS} component={Home} exact />
          <Redirect from={APPLICATIONS_DETAIL} to={ONLINE_JOB_MANAGEMENT} />
        </Switch>
      </ConfigProvider>
    </CommonStyledContent>
  </CommonStyledLayout>
</Suspense>

// 改为下面的结构
// 1、最外层无需 Suspense,因为在使用这个组件的父组件 Layout 本身就已经对其使用了 Suspense包裹
// 2、为内容区添加 Suspense 包裹

<CommonStyledLayout>
  <SidedMenu listMenu={listMenu} />
  <CommonStyledContent>
    <Suspense fallback={<SuspenseFallback />}>
      Content
    </Suspense>
  </CommonStyledContent>
</CommonStyledLayout>

代码

整体代码改动可见这笔提交:none