2022-12-03-【优化】前端性能优化

前端性能优化

优化方式分类 1、免费

包体积 ng配置 缓存 代码压缩 gzip,webpack、vite压缩 最小化HTTP请求、响应大小 JavaScript中的性能优化,1.避免JS动画 2.节流(滚动加载、提交按钮)和防抖(搜索、鼠标移动,窗口resize) 图片懒加载 使用CSS3 代替图片 提取公共代码

2、花费 cdn 带宽 cpu/mem

背景

为满足不同企业对系统登录页个性化设计,且需要独立域名访问的要求,另外合同层面及部署层面强要求,将页面进行拆分部署,以达到合规目的。

遂将原有应用一分为二,将登录页独立部署(每新增一个企业使用,独立部署一套个性化UI的登录NG),所有访问请求需通过登录页ng进行代理。

考虑到每个企业的部署成本,需给出该部署架构下登录页节点的最优资源配置;为找到最合理的部署资源配置参数,我们将对系统进行压测以获取CPU、内存的部署资源参数。

部署架构图

压测目的&体验目标

  • 为节省腾讯云部署资源,找出最优资源配置;

根据架构师按用户使用情况进行预估,给到的最大并发10,合理并发5,通过压测工具jmeter进行测试; 将资源不断减半压缩,以测试当前部署架构下,系统的抗压能力,最终找到最低的资源配置参数,以达到降低成本的目的。

渲染目标 FP及FCP 在2秒内完成,体验优秀 LCP(Largest Contentful Paint)代表了页面的速度指标,小于 2.5秒 FID(First Input Delay)代表了页面的交互体验指标,100ms 以内 CLS(Cumulative Layout Shift)代表了页面的稳定指标,0.1或以下

__ 初始部署资源 __ request 1C1G, limit 2C2G
__ 目标部署资源 __ request 0.5C512M,limit 1C1G

压测结果【优化前&优化后】

  1. 并发测试 测试工具:jmeter 腾讯云环境:单节点测试(开放主机端口进行访问) 测试方法:
    • 利用大体积静态资源(JS/CSS)的http请求,预估页面并发下资源加载时长
    • 大文件http请求(动态GZIP压缩)JS文件 519K
    • 大文件http请求(动态GZIP压缩)css文件 43.6K
    • 普通http Get请求

    腾讯云资源

    优化前

    • [测试1] request:1C1G limit:2C2G
      • 10并发
      • 5并发
    • [测试2] request:0.5C512M limit:1C1G
      • 10并发 0.5C512MB10T.old.jpg
      • 5并发 0.5C512MB5T.old.jpg
    • [测试3] request:0.3C256M limit:0.5C512G
      • 10并发 0.3C256MB10T.old.jpg
      • 5并发 0.3C256MB5T.old.jpg
  2. 页面性能测试 测试工具:Lighthouse
  • 优化前 0.3C256MB5T.old.jpg

优化思路

影响并发的主要因素有服务器端及客户端的网络、CPU、内存。而影响前端访问性能的因素分为两个部分,加载效率以及运行效率。

加载时优化 1、包体积优化 2、CDN加载 3、GZIP(动态+静态) 4、网络优化,合并请求、减少资源大小

运行时优化 1、减少重排 重绘 2、避免页面卡顿、 3、长列表(虚拟列表)、代码性能、防抖节流

由于项目中体积最大的依赖:UI组件库tdesign只有最新版本的CDN资源,不提供历史版本的CDN,仅优化其他小体积的,改造意义不大。

通过TOP指令监控腾讯云部署节点的CPU/MEM使用情况,观察数据变化分析结果得出,影响低并发的最大因素为网络,而同等带宽下影响下载时长的最大因素为下载资源的体积,所以优化最大资源体积会取得最明显的优化效果。

工程背景

"vue": "2.6.11",
"vite": "2.8.6",

打包优化

依赖包体积检查

bundlejs.com 这样的工具可以用来做快速的检查,但是根据实际的构建设置来评估总是最准确的。

原始打包后静态资源分析 原始包体积

1. 关闭一些打包配置项

  • 这个东西一般是在测试阶段调试使用的
    build: { 
        terserOptions: {
          compress: {
            //生产环境时移除console
            drop_console: true,
            drop_debugger: true,
          },
        },
      //   关闭文件计算
        reportCompressedSize: false,
      //   关闭生成map文件 可以达到缩小打包体积
        sourcemap: false, // 这个生产环境一定要关闭,不然打包的产物会很大
    }
    

原始包体积

2. webpackChunkName 按路由配置moudle合并chunk

引入包vite-plugin-webpackchunkname,使vite 支持。

配合动态路由配置

优化前 82个文件 优化后 53个文件

  • 2.1 装包
    yarn -D add vite-plugin-webpackchunkname
    
  • 2.2 配置 vite.config.js ``` js // vite.config.js import { manualChunksPlugin } from ‘vite-plugin-webpackchunkname’ // Other dependencies…

export default defineConfig({ plugins: [ manualChunksPlugin(), ] })

* 说明:Support for user defined manual chunks 支持用户自定义chunks *

#### 3. 拆分依赖包
- 根据依赖包体积进行规划,均匀划分vendor大小,均匀拆分依赖;
- 通过gzip压缩降低2/3 体积。
##### 3.1 vite 相关配置
- 包体积分析

安装依赖

yarn -D add rollup-plugin-visualizer

- 配置分析

plugins: [ vue(), visualizer({ open:true, //注意这里要设置为true,否则无效 gzipSize:true, brotliSize:true }) ],

- 合理分包
```js
/* build.rollupOptions.output.manualChunks */
rollupOptions: {
  // input: viteMultiPages,
  output: {
    manualChunks: (id) => {
      if (
        id.indexOf('node_modules/lodash/') !== -1 ||
        id.indexOf('node_modules/tinycolor2/') !== -1 ||
        id.indexOf('node_modules/aegis-web-sdk/') !== -1 ||
        id.indexOf('node_modules/sortablejs/') !== -1 ||
        id.indexOf('node_modules/@babel/') !== -1 ||
        id.indexOf('node_modules/regenerator-runtime/') !== -1 ||
        id.indexOf('node_modules/clipboard/') !== -1 ||
        id.indexOf('node_modules/dayjs/') !== -1 ||
        id.indexOf('node_modules/@popperjs/') !== -1
      ) {
        return 'vendor-utils';
      }
      if (
        id.indexOf('node_modules/core-js/') !== -1 ||
        id.indexOf('node_modules/@vue/') !== -1 ||
        id.indexOf('node_modules/vue/') !== -1 ||
        id.indexOf('node_modules/vue-router/') !== -1 ||
        id.indexOf('node_modules/vuex/') !== -1 ||
        id.indexOf('node_modules/axios/') !== -1
      ) {
        return 'vendor-core';
      }
      if (id.indexOf('node_modules/@wecity/') !== -1 || id.indexOf('node_modules/tdesign-icons-vue/') !== -1) {
        return 'vendor-tdgv';
      }
      // if (id.indexOf('node_modules/tdesign-vue/es/') !== -1) {
      //   return 'vendor-td';
      // }
    },
  },
},
// TODO 打包效果与上面有点差异,暂未分析原因
// if (id.includes('node_modules')) {
//   const arr = id.toString().split('node_modules/')[1].split('/');

//   switch (arr[0]) {
//     case 'lodash':
//     case 'lodash-es':
//     case 'tinycolor2':
//     case 'aegis-web-sdk':
//     case 'sortablejs':
//     case '@popperjs':
//     case '@babel':
//     case 'js-base64':
//       return 'vendor-utils';
//     case 'core-js':
//     case '@vue':
//     case 'vue':
//     case 'vue-router':
//     case 'vuex':
//     case 'axios':
//       return 'vendor-core';
//     case '@wecity':
//     case 'tdesign-icons-vue':
//       return 'vendor-tdgv';
//     default:
//       return null;
//   }
// }

优化后包体积

3.2 webpack4 拆包相关配置 SplitChunksPlugin
  1. 包文件分析
    • yarn -D add webpack-bundle-analyzer
    • 添加配置
      // vue.config.js 
      chainWebpack: (config) => {
      if (process.env.NODE_ENV === 'production') {
       config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin);
      }
      }
      
  2. 合理拆分module、chunck,配置如下
    module.exports = {
      pages: {
     index: {
       .....
       .....
       // 1、引入新拆分的chunk
       chunks: ['chunk-vendors', 'vendors-utils', 'vendors-tdgv', 'vendors-core', 'index']
     },
      },
      // 2、webpack4 拆分chunk
    optimization: {
      splitChunks: {
     cacheGroups: {
       'vendors-core': {
         name: 'vendors-core',
         test: /[\\/]node_modules[\\/](vue|vue-router|axios|@vue|vuex)/,
         chunks: 'all',
         priority: 10,
         enforce: true,
       },
       'vendors-tdgv': {
         name: 'vendors-tdgv',
         test: /[\\/]node_modules[\\/](@wecity|tdesign-icons-vue)/,
         chunks: 'all',
         priority: 8,
         enforce: true,
       },
       'vendors-utils': {
         name: 'vendors-utils',
         test: /[\\/]node_modules[\\/](lodash|tinycolor2|aegis-web-sdk|clipboard|core-js)/,
         chunks: 'all',
         priority: 8,
         enforce: true,
       },
       'chunk-vendors': {
         name: 'chunk-vendors',
         test: /[\\/]node_modules[\\/]/,
         chunks: 'all',
         priority: 1,
         enforce: true,
       },
     },
      },
    }
    

4. 静态GZIP

  • 4.1 安装依赖
yarn -D add vite-plugin-compression
  • 4.2 配置vite.config.js
import compress from 'vite-plugin-compression';
...
defineConfig({
  plugins: [
    // 压缩10kb以上的文件
    compress({ threshold: 10240 }),
    ...
  ]
})

命名chunkFileNames

命名chunkFileNames,会额外生成文件,未研究明白。。。

/* build.output.chunkFileNames */
/* 添加一下配置,将按组件文件名 生成chunk 文件 */
output: {
  chunkFileNames: (chunkInfo) => {
    console.log(`===========${chunkInfo.facadeModuleId}`);
    const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/') : [];
    const fileName = facadeModuleId[facadeModuleId.length - 2] || '[name]';
    return `${fileName}.[hash].js`;
  },
},

优化结果

通过前端包体积优化及静态GZIP,最大资源大小由519kb 降低到 199kb;0.3核256MB服务器,10并发下的耗时从7.7s降低到2.6s左右,能够满足低并发下正常使用;瓶颈在依赖包Tdesign的大小无法在做更细粒度拆分。

优化后

  • request:0.3C256M limit:0.5C512G
    • 10并发 0.3C256MB10T.old.jpg
    • 5并发 0.3C256MB5T.old.jpg
  • lighthouse 结果 0.3C256MB5T.old.jpg

参考文档

vue官方性能优化 Puppeteer+Lighthouse 2022 前端性能优化最佳实践 JMETER压力测试 常见的性能优化方案

参考指标

页面渲染

DOMContentLoaded < 2s

用户体验三大核心指标

Google 在20年五月提出了网站用户体验的三大核心指标

  1. Largest Contentful Paint (LCP) LCP 代表了页面的速度指标,虽然还存在其他的一些体现速度的指标,但是上文也说过 LCP 能体现的东西更多一些。一是指标实时更新,数据更精确,二是代表着页面最大元素的渲染时间,通常来说页面中最大元素的快速载入能让用户感觉性能还挺好。 ``` 标签
在svg中的image标签