Skip to content

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

大厂技术 高级前端 Node 进阶

点击上方 程序员成长指北,关注公众号

回复 1,加入高级 Node 交流群

项目简介

这是一个 vue3 + vite 搭建的 PC 端 web 应用,基本上使用的是 vite 的默认配置,没有专门做过性能优化。随着业务的迭代,目前项目已经成为存在 23W+ 代码行数的大工程,存在一定的性能问题,而且维护成本也越来越高。为了解决这两个问题,我们展开了专题治理。目前来看效果还是很理想的:页面加载性能提升了 52%,包体积降低了 3.43M。接下来就和大家一起探讨,如何对存量大工程进行治理以实现提升页面性能以及降低代码维护成本

现状分析

页面性能分析

通过内部工具(类似 Lighthouse + performance )对页面进行分析,如下图

基于上图及诊断结果(类似 lighthouse 下方的诊断结果)可分析出如下问题

1、首屏请求内容过大:主要指页面加载完成过程中网络请求的体积大小

2、js/css 代码覆盖率高:意味着用户加载了太多不必要的代码(要么真的是无用代码,要么是当前时点还没执行到的代码)

3、脚本解析时长占比较高:说明页面的脚本解析以及执行时长太长了,影响了页面渲染。

4、首屏 DOM 节点数量较高:页面加载完成之后页面上的 DOM 节点数量

5、未使用 Gzip 的资源 44 个: 使用 GZip 可以减小文件体积,另外也可以大大节省服务器的网络带宽

build 包分析

分析项目中的文件大小及引用情况,是优化前的重要一步,从而采取文件分包,按需引入等,那么在 vite 下我们使用 Rollup Plugin Visualizer  来进行依赖分析

  • 安装
npm install --save-dev rollup-plugin-visualizer
  • Vite 下的插件属性配置
import { visualizer } from 'rollup-plugin-visualizer';export default defineConfig({  plugins: [vue(), visualizer({    emitFile: false,    file: "stats.html", //分析图生成的文件名    open:true //如果存在本地服务端口,将在打包后自动展示  })],})

配置的参数有很多是默认的,下面表格对参数进行诠释

参数类型解释
filename/filestring生成分析的文件名
titlestringhtml 标签页标题
openboolean以默认服务器代理打开文件
templatestring可选择的图表类型
gzipSizeboolean搜集 gzip 压缩包的大小到图表
BrotliSizeboolearn搜集 brotli 压缩包的大小到图表
emitFileboolean使用 emitFile 生成文件,简单说,这个属性为 true, 打包后的分析文件会出现在打包好的文件包下,否则就会在项目目录下
sourcemapboolean使用 sourcemap 计算大小
projectRootstring, RegExp文件的根目录,默认在打包好的目录下

视图展示:执行 npm run build 就可以查看如下视图

从图中可以看到 highlight.js 下将所有的语言包都引入了,引入了 loadash 下所有的工具包,另外 echarts 等包重复打包,问题总结如下:

1、多个 JS 文件依赖重复打包,JS 文件体积大

2、依赖包全部引入,没有按需加载引入

代码重复率分析

Jscpd 工具介绍

Jscpd 是个开源的代码重复率检测工具,github 地址:https://github.com/kucherenko/jscpd/tree/master/packages/jscpd

Jscpd 安装

yarn global add jscpd

Jscpd 使用

  • 在项目的 package.json 中配置 jscpd
{  ...  "jscpd": {    "threshold": 5, // 重复率阈值    "reporters": [      "html",      "console",      "badge"    ], // report输出类型    "ignore": [      "node_modules",      "miniprogram_npm",      "pages/test",      "config/mock.js "    ], // 忽略文件/夹    "absolute": true, // report路径采用绝对路径    "gitignore": true // gitignore文件也忽略  }  ...}
  • 切换到要检测的项目的目录

  • 执行检测(更多的传参用户,请参考项目 github 地址)

jscpd ./ -o "./report/"

检测结果如下

  • 检测结果会通过 console 到控制台

  • 直观的话,可以查看 report 文件夹下面的 html,可以根据检测结果,查看重复的代码块,有针对性的重构

圈复杂度分析

什么是圈复杂度

圈复杂度(Cyclomatic complexity,CC)也称为条件复杂度或循环复杂度,是一种软件度量,是由老托马斯 ·J· 麦凯布(Thomas J. McCabe, Sr.)在 1976 年提出,用来表示程序的复杂度,其符号为 VG 或是 M。圈复杂度即程序的源代码中线性独立路径的个数。

为何要降低模块(函数)的圈复杂度

下表为模块(函数)圈复杂度与代码状况的一个基本对照表。除了表中给出的代码状况、可测性、维护成本等指标外,圈复杂度高的模块(函数),也对应着高软件复杂度、低内聚、高风险、低可读性。我们要降低模块(函数)的圈复杂度,就是要降低其软件复杂度、增加内聚性、减少可能出现的软件缺陷个数、增强可测试性、可读性。

圈复杂度代码状况可测性维护成本
1 - 10清晰、结构化
10 - 20复杂
20 - 30非常复杂
>30不可读不可测非常高

度量工具

  • CodeMetrics

一款 vscode 插件,用于度量 TS、JS 代码圈复杂度

  • ESLint

eslint 也可以配置关于圈复杂度的规则,如:

rules: {   complexity: [     'error',     {       max: 10     }   ] }

代表了当前每个函数的最高圈复杂度为 10,否则 eslint 将给出错误提示。

分析结果

通过 eslint 或 CodeMetrics 可以看到项目中存在较多的圈复杂度大于 10 的模块

问题总结

1、页面加载过多无用资源,页面完全加载时间过长

2、build 包体积大

3、代码重复率较高,圈复杂度高的模块较多,造成维护成本高

目标

基于以上问题分析,制定如下目标

  • 减小包体积,降低页面完全加载时长

  • 降低代码圈复杂度

  • 降低代码重复率

实现方案

包体积优化

库的按需引入,减小第三依赖的体积

expend-flow.js 优化
  • 优化前

从依赖图中可以分析 expend-flow 下主要引入了 highlight.js 这个高亮包,此包中引入了 languages 语言包,而我们项目中仅需要其中的几种语言支持就行,因此采用优化思路是按需引用 languages 包中需要的文件,仅打包其中引入的文件即可

  • 优化后(从 1.55M 降低到 241.17KB)

  • 优化实现

当由于 languages.js 是在 highlight.js 依赖中,可采用 patch-package 打补丁方式修改 NPM  包,引入需要的语言包即可

import hljs from 'highlight.js/lib/core';import javascript from 'highlight.js/lib/languages/javascript';hljs.registerLanguage('javascript', javascript);
lodash 优化
  • 优化前

  • 优化后 (从 642.57KB 降低到 112.11KB)

  • 优化实现

引用方式如下

import cloneDeep from 'lodash/cloneDeep';import debounce from 'lodash/debounce';

但是上述方式导入成本较高,期望如下方式引入

import {debounce,cloneDeep }from 'lodash/debounce';

因此使用 vite-plugin-imp 插件,在运行或编译阶段进行转换,vite 配置如下

import vitePluginImp from 'vite-plugin-imp';    vitePluginImp({      libList: [        {          libName: 'lodash',          libDirectory: '',          camel2DashComponentName: false,        },        {          libName: 'xx',          style(name) {            return `@ks/xx/lib/${name}/index.js`;          },        },      ],    }),
总结

上述举例了 2 个案例,就不一一列举其他按需引入的包了,核心思路就是基于包依赖视图去分析,去除重型依赖,减小三方依赖的提价,最终只引入指定的组件或工具方法

避免依赖重复打包,去除无用代码,抽离公共包

  • vite 配置如下
build: {      rollupOptions: {        output: {          manualChunks: {            'vue-vendor': ['vue', 'vue-router', 'pinia'],            'echarts': ['echarts'],            'lodash': ['lodash'],          },        },      },      terserOptions: {        compress: {          // warnings: false,          drop_console: true, // 打包时删除console          drop_debugger: true, // 打包时删除 debugger          pure_funcs: ['console.log'],        },        output: {          // 去掉注释内容          comments: true,        },      },    }

Gzip 压缩

线上的项目,一般都会结合构建工具或服务端配置 nginx,来实现 http 传输的 gzip 压缩,目的就是把服务端响应文件的体积尽量减小,优化返回速度

  • 安装
npm install -D vite-plugin-compression
  • vite 配置
import viteCompression from 'vite-plugin-compression';  viteCompression({      verbose: true,      disable: false, // 不禁⽤压缩      deleteOriginFile: false, // 压缩后是否删除原⽂件      threshold: 10240, // 压缩前最⼩⽂件⼤⼩      algorithm: 'gzip', // 压缩算法      ext: '.gz', // ⽂件类型    }),
  • 本地图片无损压缩

1、安装 vite-plugin-imagemin 插件,对项目中的图片进行压缩处理。

npm i vite-plugin-imagemin -D

2、在 vite.config.ts 中引入并使用它。

import viteImagemin from 'vite-plugin-imagemin'export default defineConfig({  // ...  plugins: [    viteImagemin({      gifsicle: {        optimizationLevel: 7,        interlaced: false      },      optipng: {        optimizationLevel: 7      },      mozjpeg: {        quality: 20      },      pngquant: {        quality: [0.8, 0.9],        speed: 4      },      svgo: {        plugins: [          {            name: 'removeViewBox'          },          {            name: 'removeEmptyAttrs',            active: false          }        ]      }    }),  ]});

降低代码圈复杂度

衡量标准

eslint 圈复杂度配置不宜大于 10

影响圈复杂度因素

  • if-else-else、switch-case、&& 、?、|| 每增加一个分支,复杂度增加 1

  • 增加一个循环结构,复杂度增加 1

  • return 在某些度量工具看来,一条 return 语句将增加整体程序的一条路径,并且如果提前返回,将增加程序的不确定性,所以在大多数计算工具中,每增加一条 return 语句,复杂度加 1

实现方案

  • 抽象配置

  • 表达式逻辑优化

  • 函数提炼与拆分

降低重复代码率

定位重复代码

  • 通过 jscpd 检查代码重复率,基于视图分析出需要优化的代码,如下图可以看出文件中重复的代码

优化方案

  • 重复逻辑整合

  • 还有通用样式整合,重复逻辑继承等方式,在此不一一列举

效果

  • 包体积从最初的 16.37M  降低到 12.94M

  • 页面完全加载从 1.9S 降低到 912ms

Node 社群

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

   “分享、点赞、在看” 支持一下