React+Vite技术栈下首屏资源优化

大厂技术  高级前端  Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

文章转载于稀土掘金技术社区——码云之上
前言

最近小组做的H5应用需要通过iframe嵌入到第三方站点里(第三方站点也是H5应用,目的是利用第三方应用的流量)。对方老板希望我们站点打开速度能快一点,不要影响到他们的用户体验。为此,专门做了一轮首屏优化。
谈到H5应用的首屏优化,首屏资源体积优化是重中之重。我们的H5应用采用的技术栈是Vite[1] + React[2] + React-Router[3] + Arco-Design[4],接下来我将详细介绍如何实现首屏资源体积减少这一目标。

问题现状

当初为了快速开发,采用的是Vite下的react-ts[5]模板

pnpm create vite h5-app --template react-ts

该模板下默认是没有任何优化措施的:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

因此构建出来的js、css产物都会集中在两个文件中:

0dbf07aec5299d243b14beb2b722928f.jpeg


通过rollup-plugin-visualizer[6]插件可以直观查看到依赖包所占的体积:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import visualizer from 'rollup-plugin-visualizer';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), visualizer()],
})

35f6efed25d7a74a1a9ccdc075039e13.jpeg

通过图可以看到 lodashechartsvconsolearco-design这些依赖包的打包体积占比很大。

当然vconsole是不需要关注的,因为生产环境打包不会囊括进去,上图体现的是测试环境构建产物体积盒图。

优化措施

通过上面的分析,可以发现当前构建包存在部分依赖产物体积过大、首屏资源没有懒加载的问题。接下来就是针对这两个问题进行优化。

构建产物按需加载

按需加载是为了减少依赖产物体积过大的问题,典型的如 lodashechartsarco-design,整个包存在很多方法、组件,而在项目实际使用到的其实只有一部分。在构建时进行按需加载,可以有效减小最终产物体积。

echarts的按需加载官方提供了解决方案,本文直接采用了官方推荐的方法[7]

// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
// 引入柱状图图表,图表后缀都为 Chart
import { BarChart } from 'echarts/charts';
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
 TitleComponent,
 TooltipComponent,
 GridComponent,
 DatasetComponent,
 TransformComponent
} from 'echarts/components';
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
 TitleComponent,
 TooltipComponent,
 GridComponent,
 DatasetComponent,
 TransformComponent,
 BarChart,
 LabelLayout,
 UniversalTransition,
 CanvasRenderer
]);
export default echarts;

对于lodasharco-design库,需要借助一个vite插件 vite-plugin-imp[8]。该插件的原理是在构建阶段将如:

import { forEach } from 'lodash'

的导入写法改为:

import forEach from 'lodash/forEach'

如此就能实现依赖的按需加载。这个插件目前支持了主流的工具库和组件库:

  • antd-mobile[9]

  • antd[10]

  • ant-design-vue[11]

  • @arco-design/web-react[12]

  • @arco-design/web-vue[13]

  • element-plus[14]

  • element-ui[15]

  • lodash[16]

  • underscore[17]

  • vant[18]

  • view ui[19]

  • vuetify[20]

@arco-design/mobile-react的按需加载

通过查看vite-plugin-imp的文档,结合@arco-design/mobile-react库的目录结构,可以得到如下配置:

{
   libName: '@arco-design/mobile-react',// npm包的名称
   libDirectory: 'esm',// 组件所在目录
   // 一些文档引入的是less样式文件,其实arco-design也提供了css样式文件,可以省去安装less的步骤
   style: name => [
      `@arco-design/mobile-react/esm/${name}/style/css/index.css`,
   ],
},

上面的配置里,libName是包名,libDirectory则需要去node_modules里查看包的目录结构,确定具体组件所在的包名,style是组件的样式文件,需要一并引入,这里也需要结合组件的目录来配置:

7e7e990a8931b6159d1fa7bc435d857f.jpeg


lodash的按需加载

同样的,参考对@arco-design/mobile-react的配置,可以得到lodash的按需加载配置:

{
    libName: 'lodash',
    libDirectory: '',
    camel2DashComponentName: false,
},

因为lodash下的二级文件直接放在根目录下,所以libDirectory设置为'',表示无需增加二级路径。
lodash下的工具函数采用的是low camelcase[21]命名法,因此camel2DashComponentName设置为false

通过上述按需加载措施,构建产物里的@arco-design/mobile-reactlodash体积减少了很多:

f91c1207040c9543639a2d7e32643e83.jpeg
vite-按需加载后体积盒图.png

具体的包体积数据如下表:

依赖包按需加载前按需加载后
@arco-design/mobile-react543.7KB160.78KB
lodash547.05KB78.23KB

构建出来的js产物体积也降低了不少:

f8de2bfd744228e42d6a7e8d024a2cf5.jpeg


路由懒加载

经过按需加载优化,总的包体积下降了一些,但是因为所有产物集中在一个包里,等于访问首屏就需要加载全量的js、css脚本,这是没有必要的。完全可以按照路由对产物进行分割,以此减少首屏体积。
对于React框架实现路由懒加载的方式有很多,本文借助了 @loadable/component[22] 这个库。

// 之前的代码(示例)
// import Page1 from '@/pages/page1';
// import Page2 from '@/pages/page2';
// import Page3 from '@/pages/page3';
// import Page4 from '@/pages/page4';
// import Page5 from '@/pages/page5';

// 采用路由懒加载后的代码
import loadable from '@loadable/component';

const Page1 = loadable(() => import('@/pages/page1'));
const Page2 = loadable(() => import('@/pages/page2'));
const Page3 = loadable(() => import('@/pages/page3'));
const Page4 = loadable(() => import('@/pages/page4'));
const Page5 = loadable(() => import('@/pages/page5'));

看一下路由懒加载后构建产物吧:

acfb2fc9eb350e12c789b977dd9fbd27.jpeg


可以发现之前整体的index-[hash].jsindex-[hash].css被拆分成了很多小文件,这便是路由懒加载的能力,将产物按路由进行分割。

代码二次分割

你以为这样就结束了吗?
no!!!
还可以继续分割。分析一下,既然路由做了懒加载,那么那些在非首页才用到的依赖是不是可以单独分割为一个chunk呢,这样的话可以进一步减少index-[hash].js的体积。说干就干,翻阅Vite官方文档,看到这样一段描述:

你可以通过配置 build.rollupOptions.output.manualChunks 来自定义 chunk 分割策略(查看 Rollup 相应文档[23])。

再看看 Rollup的文档,找到关于chunk 分割的配置 output.manualChunks[24]

{ [chunkAlias: string]: string[] } | ((id: string, {getModuleInfo, getModuleIds}) => string | void)

该选项允许你创建自定义的公共 chunk。当值为对象形式时,每个属性代表一个 chunk,其中包含列出的模块及其所有依赖,除非他们已经在其他 chunk 中,否则将会是模块图(module graph)的一部分。chunk 的名称由对象属性的键决定。

结合项目自身的特性,决定对echartsreact-markdown进行单独拆分(因为这样依赖在首页没有用到)。vite.config.ts中的配置如下:

build: {
  outDir: 'build',
  target: 'chrome87',
  cssTarget: 'chrome61',
  chunkSizeWarningLimit: 650,
  rollupOptions: {
    output: {
      manualChunks: {
        echarts: ['echarts'],
        markdown: ['react-markdown', 'remark-gfm'],
      },
      experimentalMinChunkSize: 100 * 1024,
    },
  },
},

再来看看构建产物:

307d4f5fde080076819fa102dce30460.jpeg


可以发现独立出来了markdown-[hash].js,echarts-[hash].js两个独立的 chunk。

优化效果

做了这么多的优化,最终效果如何呢?
先看下未优化前的首屏资源体积及加载耗时:

2036b8cac9f76453089987ae346c4eed.jpeg

优化之后的加载耗时:

2d54635061fff6d14d8cde5f955ffc28.jpeg

总加载资源体积减少约50% ,加载速度提升约40%
嗯嗯,效果基本达预期^_^。

小结

  1. 首屏资源优化应该先分析问题所在,识别出哪些包体积过大;

  2. 代码分割需要配合路由懒加载才能达到效果,否则即使代码进行了分割,但加载首屏还是会因为路由提前注册的原因,使得浏览器加载所有资源。

  3. 优化也有成本,比如文本中虽然通过代码分割+路由懒加载减少了首屏资源体积,但随之也带来了资源请求数增加的问题,对浏览器的并发请求带来了压力。

附录

完整的vite.config.ts配置代码:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import visualizer from 'rollup-plugin-visualizer';
import vitePluginImp from 'vite-plugin-imp';

// https://vitejs.dev/config/
export default defineConfig({
  base: '/',
  build: {
    outDir: 'build',
    target: 'chrome87',
    cssTarget: 'chrome61',
    chunkSizeWarningLimit: 650,
    rollupOptions: {
       output: {
         manualChunks: {
           echarts: ['echarts'],
           markdown: ['react-markdown', 'remark-gfm'],
         },
       },
     },
  },
  plugins: [
    react(),
    visualizer(),
    vitePluginImp({
       libList: [
         {
           libName: '@arco-design/mobile-react',
           libDirectory: 'esm',
           style: name => [
             `@arco-design/mobile-react/esm/${name}/style/css/index.css`,
           ],
         },
         {
           libName: 'lodash',
           libDirectory: '',
           camel2DashComponentName: false,
         },
       ],
     }),
  ],
})

Node 社群

 
 

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

7eb5bfbe8bad6472f3eb676f656e655d.png

“分享、点赞、在看” 支持一下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值