H5性能优化实践

Taro + webpack5 + vue3项目,h5页面性能加载问题

背景:pda设备较差,cpu处理能力不足,h5页面首屏加载缓慢,vconsole调试工具查看接口耗时很长(几十秒),但用抓包工具、服务端接口请求日志查看发现接口处理时间很快(几百毫秒),中间的差值到底耗时在哪里?vconsole的time并不是指服务器接口响应的时间,还包括资源排队的时间,排队时间长估计就是主要原因了

思路:用谷歌浏览器cpu降速20倍模拟场景,用屏幕录制找出耗时长的长任务(是否加载了除了首屏之外的其他资源、某个模块包是否太大阻塞了主线程)

性能指标

指标名称 英文缩写 定义 测量方法
首屏绘制时间 FCP 页面首次内容绘制的时间 使用Performance API获取first-contentful-paint时间
可交互时间 TTI 页面达到完全可交互状态的时间 使用 Lighthouse 工具生成性能报告
最大内容绘制时间 LCP 页面上最大内容元素(如图片、文本块)绘制的时间 使用PerformanceObserver监听largest-contentful-paint事件
累积布局偏移 CLS 页面在加载过程中发生的布局偏移总量 使用 Lighthouse 工具生成性能报告
总阻塞时间 TBT 页面从开始加载到完全可交互期间,主线程被阻塞的总时间 使用 Lighthouse 工具生成性能报告

原因

一、网络延迟导致加载慢

  1. 使用preload-预加载技术(未采用)
js 复制代码
<!-- 关键图标资源使用 preload --> 
<link rel="preload" href="/static/js/iconfont.js?v=3.1.2" as="script" crossorigin>
<!-- 非关键图标资源使用 prefetch -->
<link rel="prefetch" href="/static/js/mobile-color.js?v=3.1.2" as="script">

2.使用cdn (未采用)

二、资源太大导致加载慢

1.分包 chunk(已采用)

js 复制代码
// config.index.js文件 
webpackChain(chain, webpack) { 
   chain.optimization.splitChunks({
        chunks: 'all',
        minSize: 100000, // 提高到 100KB
        minChunks: 2,
        maxAsyncRequests: 8,
        maxInitialRequests: 4, // 限制首屏请求数
        automaticNameDelimiter: '~',
        cacheGroups: {
          // 核心库 - 首屏必需
          coreVendors: {
            test: /[\\/]node_modules[\\/](vue|pinia|@vue|@tarojs)[\\/]/,
            name: 'chunk-core-vendors',
            priority: 90, // 最高优先级
            chunks: 'initial', // 只包含初始 chunk
            enforce: true, // 忽略 minSize 限制
            reuseExistingChunk: true,
          },

          // 大型库 - 第一大类异步加载
          largeVendors: {
            test: /[\\/]node_modules[\\/](vue-pdf-embed|vue3-echarts)[\\/]/,
            name: 'chunk-large-vendors',
            priority: 80,
            chunks: 'all',
            enforce: true,
            reuseExistingChunk: true,
          },

          // 大型库 - 第二大类异步加载
          secondLargeVendors: {
            test: /[\\/]node_modules[\\/](nutui-taro|@nutui|echarts|lodash|lodash-es|vconsole|vuedraggable|)[\\/]/,
            name: 'chunk-secondLarge-vendors',
            priority: 70,
            chunks: 'all',
            minChunks: 1,
            minSize: 0, // 不限制最小大小
            enforce: true, // 忽略 minSize 限制
            reuseExistingChunk: true,
          },

          // 大型库 - cnhis-design-taro单独打包
          cnhisDesign: {
            test: /[\\/]node_modules[\\/](cnhis-design-taro)[\\/]/,
            name: 'chunk-cnhisDesign-vendors',
            priority: 60,
            chunks: 'all',
            enforce: true,
            reuseExistingChunk: true,
          },

          // 大型库 - cnhis-ai-agent-taro单独打包
          cnhisAiAgent: {
            test: /[\\/]node_modules[\\/]@cnhis-frontend[\\/]ai-agent-taro[\\/]/,
            name: 'chunk-aiAgent-vendors',
            priority: 50,
            chunks: 'all',
            enforce: true,
            reuseExistingChunk: true,
          },

          // 其他 node_modules 中的依赖 添加排除规则
          vendors: {
            test: /[\\/]node_modules[\\/](?!(vue|@vue|pinia|@tarojs|nutui-taro|@nutui|vue-pdf-embed|cnhis-design-taro|ai-agent-taro|vue3-echarts|echarts|lodash|vconsole|vuedraggable))[^\\/]+[\\/]/,
            name: 'chunk-vendors',
            priority: 10,
            chunks: 'all',
            minSize: 200000, // 200KB 以上才打包
            reuseExistingChunk: true,
            minChunks: 1
          },

          // 公共组件
          common: {
            test: path.resolve(__dirname, "src/components"),
            name: 'chunk-common',
            priority: 5,
            minChunks: 3,
            reuseExistingChunk: true
          },

          // 工具函数
          utils: {
            name: "chunk-utils",
            test: path.resolve(__dirname, "src/utils"),
            priority: 8,
            reuseExistingChunk: true,
            minChunks: 2
          },

          // 图标资源
          icons: {
            test: /[\\/]src[\\/]assets[\\/]js[\\/]/,
            name: 'icons',
            priority: 5,
            minChunks: 1,
            reuseExistingChunk: true,
            minSize: 10000
          },
        }
      });
}

2.页面组件、第三方库按需引入(已采用)

按需加载的实现基于 ES6 的动态导入和打包工具(如 Webpack、Rollup)的代码分割功能。当打包工具遇到 import() 表达式时,会自动将被导入的模块分割成单独的代码块(chunk),只有在运行时执行到 import() 语句时才会加载相应的代码块。

js 复制代码
import { defineAsyncComponent } from 'vue';
const HeaderPatientBox = defineAsyncComponent(() => import('@/pages-clinurse/components/header-patient-box/index.vue'))

使用 Webpack 的 import() 动态导入语法,按需加载第三方库,而不是一次性全部加载,选择性价比较好的第三方库,

js 复制代码
import { cloneDeep, range } from 'lodash-es'

3.Tree Shaking(摇树优化),移除未使用的代码,减小文件体积。打包工具已经支持,一般无需额外操作

4.nginx缓存(已采用)

  • 强缓存:Cache-Control: max-age=31536000(1年)
  • 协商缓存:Etag/Last-Modified(适合频繁更新资源)
  • 离线缓存:Service Worker + Cache API实现离线可用

5.压缩图片(已采用)

  1. 使用工具 tinypng.com/ 压缩图片,建议使用webp格式的图片

  2. 雪碧图

  3. 图片懒加载

6.SSR服务端渲染(未采用)--- 终极方案

监控工具

使用插件 webpack-bundle-analyzer,生成report.html报告,根据报告分析包的合理性

js 复制代码
1.安装插件
yarn add webpack-bundle-analyzer --dev 
2.添加配置
// config.index.js文件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; webpackChain(chain, webpack) { 
  // 添加 Bundle Analyzer
  chain.plugin('bundle-analyzer')
    .use(BundleAnalyzerPlugin, [{
      analyzerMode: 'static',       // 生成静态 HTML 报告
      openAnalyzer: false,          // 不自动打开浏览器
      generateStatsFile: false,      // 生成 stats.json
      defaultSizes: 'gzip',         // 显示 gzip 后大小
      reportFilename: path.resolve(process.cwd(), 'report.html'),
      statsFilename: path.resolve(process.cwd(), 'stats.json'),
    }]);
    }
3.在package.json的scripts里添加命令 
"report": "set NODE_ENV=production && taro build --type h5" 
4.最后执行yarn report 即可生成report.html文件

谷歌浏览器降速20倍检测,最终首屏加载快了5-6秒左右

相关推荐
EndingCoder7 分钟前
Next.js 中间件:自定义请求处理
开发语言·前端·javascript·react.js·中间件·全栈·next.js
Andy_GF10 分钟前
纯血鸿蒙 HarmonyOS Next 调试证书过期解决流程
前端·ios·harmonyos
现实与幻想~17 分钟前
Linux:企业级WEB应用服务器TOMCAT
linux·前端·tomcat
mit6.82418 分钟前
[AI React Web]`意图识别`引擎 | `上下文选择算法` | `url内容抓取` | 截图捕获
前端·人工智能·react.js
赛博切图仔24 分钟前
React useMemo 深度指南:原理、误区、实战与 2025 最佳实践
前端·react.js·前端框架
YiuChauvin1 小时前
vue2中页面数据及滚动条缓存
前端·vue.js
摸着石头过河的石头1 小时前
微信h5页面开发遇到的坑
前端·微信
zabr1 小时前
AI时代,为什么我放弃Vue全家桶,选择了Next.js + Supabase
前端·aigc·ai编程
egghead263161 小时前
React常用hooks
前端·react.js
科粒KL1 小时前
前端学习笔记-浏览器渲染管线/一帧生命周期/框架更新
前端·面试