【⚡常用性能优化指南】直接实践快速上手 !

为什么做性能优化?

"从 2018 年 Google I/O 大会上提供的数据发现,页面加载时长 是影响用户体验的最大因素,页面加载时长不仅对用户体验有影响,还对业务数据有影响,当页面加载时长超过 5s 就会造成 90% 的用户流失 " --- 《从零开始建立性能优化知识体系》

再不了解性能优化就out了🙀

一位资深hr说道:"性能优化 在面试中经常拿来提问,拿区分度来说:如果罗里吧嗦列出一堆优化方式,可能就是背的八股或者缺乏自己的理解;若有自己的理解并归类总结讲出本质,或有实际项目列出各种优化收益指标,就能达到良好 ;而如果能定制行业标准、自研优化方案,那就能拿到优秀了。"

(ps:作者也是为了在面试之中,能够顶住拷打而开始了对项目的优化💪)

下面让我们通过一些简单的优化方案,在自己的项目中来试试吧!

性能诊断💉

FP(First Paint)

我们可以使用 performance 来录制分析我们详细的加载请求资源过程,其中我们可以看到 FP ,它在 FCP 的前面,即表示渲染出第一个像素点。也就是说它是我们 白屏时间 的一个结束位置。

首先看看未优化前的 FP

优化 FP 有很多方法,比如骨架屏、进度条等等,这里我们采用进度条

css 复制代码
pnpm i nprogress -S 
pnpm i @types/nprogress -D

主要是配合路由守卫来显示其效果,在我们进行鉴权逻辑判断时开始进度条,允许路由跳转后进度条结束,以下是代码示例片段:

javascript 复制代码
import router from "@/router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

router.beforeEach(async (to, from, next) => {
  NProgress.start();
  const hasToken = localStorage.getItem("accessToken");
  if (hasToken) {
    next({ path: "/" });
    NProgress.done();
  } 
});

router.afterEach(() => {
  NProgress.done();
});

优化后的 FP :2175.9ms ->802.8ms

FCP(First Contentful Paint)

FCP表示渲染出 第一个内容,这里的"内容"可以是文本、图片、canvas。

我们还可以在浏览器开发者工具中使用 Lighthouse 为项目体检一下,其中较为重要的就是 FCP,即当用户访问网页 到 浏览器开始将页面内容呈现给用户的时间,FCP 的值越小,表示页面内容首次呈现给用户的速度越快,用户感知的加载速度也会更好。因此,对于网页性能优化来说,降低 FCP 时间是一个重要的目标

"下面我们将进行一系列优化方案来提高我们的性能!"

依赖分析工具🔍

rollup-plugin-visualizer

是一个用于 Rollup 的插件,用于生成可视化的依赖包分析报告stats.html,可以通过该图表来查看和定位各个模块的体积,找出可能存在的冗余代码或者体积过大的模块

bash 复制代码
npm i rollup-plugin-visualizer -D
typescript 复制代码
import { visualizer } from 'rollup-plugin-visualizer'

plugins: [
  visualizer(),
]

我们可以看到echarts这个包占据了我们项目依赖将近一半 的空间!很可能是开发时偷懒全局引入了!!那么我们可以把它改为按需引入


优化后:

Vite优化⚡

打包优化 --- 工具🔨

  1. esbuild 是一个快速且功能丰富的 JavaScript 打包工具,可以将多个模块打包成一个最终的构建文件,并提供了代码转换和优化的功能。你可以使用 esbuild 来处理模块的转换、压缩以及其他优化。
  2. **terser **是一个专门用于 JavaScript 代码压缩和优化的工具。它可以去除代码中的空格、注释、不必要的字符,并进行变量名缩短等优化操作,使最终的代码更加精简和高效。
  3. rollup 是一个 JavaScript 模块打包器,它可以将多个模块打包成一个或多个最终的构建文件。它提供了处理模块之间依赖关系、引入外部库和插件等功能。

我们使用 TerserRollupEsbuild(默认) 进行配合打包,相互配合取长补短

build 配置中,minify 选项设置为 "terser",表明使用 Terser 进行代码压缩。(Vite 版本在 2.6.x 以上需要配置 minify: "terser"terserOptions 才能生效)。

此外,还可以根据需求在 rollupOptions 中进行更高级的构建配置。Rollup 是一个灵活而强大的打包工具,可以帮助你配置文件入口、输出路径、自定义代码拆分策略等等。

总结:Terser 进行代码压缩,Rollup 进行更高级的构建配置,而 Esbuild 则作为 Vite 默认的打包工具来处理模块打包过程。

打包优化 --- 方法🔑

chunk 分割

  • hunkFileNames选项指定了生成的引入文件的名称格式
  • entryFileNames选项指定了包的入口文件的名称格式
  • assetFileNames选项指定了资源文件(如字体、图片等)的名称格式。
  • manualChunks 对象进行配置,自定义代码分割规则。

manualChunks是一个函数,用于手动指定哪些代码块需要被单独打包。在这个示例中,如果某个模块的路径中包含"node_modules"关键字,它将被作为 vendor(供应商)代码块打包。

typescript 复制代码
rollupOptions: {
  output: {
    chunkFileNames: "js/[name]-[hash].js", // 产生的 chunk 自定义命名
    entryFileNames: "js/[name]-[hash].js", // 指定 chunks 的入口文件匹配模式
    assetFileNames: "[ext]/[name]-[hash].[ext]", // 自定义构建结果中的静态资源名称,资源文件像 字体,图片等
    manualChunks(id) {
      if (id.includes("node_modules")) {
        return "vendor";
      }
    },
  }
}

具体来说,自定义文件命名可以优化缓存策略 。当文件名中的内容发生变化时,浏览器会认为资源已更新,从而重新下载。通过使用带有 hash 的文件名,可以确保每次文件内容更改时都会生成不同的文件名,从而强制浏览器重新下载更新后的文件,防止缓存旧版本的文件。

另外,手动分割 Chunk 可以帮助优化资源的加载方式 。例如,在将第三方库单独提取为一个 vendor Chunk 后,这个 Chunk 在应用更新时一般情况下不会改变,可以设置长期缓存,减少重复下载,从而减少了网络请求并提升加载性能。根据需求合理设置 Chunk 的大小和数量,可以使浏览器可以更好地并行下载资源,并提高页面渲染的速度和用户体验。

总而言之,通过自定义文件命名和手动分割 Chunk,可以优化浏览器缓存策略和资源加载方式,从而达到更快的资源请求和加载速度,提升网页性能和用户体验。

gzip压缩

vite-plugin-compression 是一个用于 Vite 的插件,用于在构建时对静态资源进行压缩和编码。

该插件主要用于优化生产环境下的资源加载速度,通过使用压缩算法(如 Gzip、Brotli)对静态资源进行压缩,以减少文件大小,从而加快资源的传输速度。

注意浏览器是否支持gzip,在请求头中的 Accept-Encoding 判断是否支持gzip压缩。

bash 复制代码
npm i vite-plugin-compression -D
typescript 复制代码
import viteCompression from "vite-plugin-compression";

viteCompression({
  algorithm: "gzip", // 压缩算法,默认为'gzip'
  ext: ".gz", // 压缩文件扩展名,默认为'.gz'
  threshold: 10240, // 文件大小超过threshold时才会进行压缩,默认为10KB (10240 bytes)
  // 为了查看dist文件夹能减少多少体积可先设置为true
  deleteOriginFile: false, // 是否删除原始文件,默认为false 
  verbose: true, // 是否在控制台显示压缩信息,默认为true
}),

高于threshold: 10240(10kb) 的包都进行了gzip压缩

剔除调试语句

typescript 复制代码
return defineConfig({
  build: {
    target: "es2020",
    minify: "terser", // Vite 2.6.x 以上需要配置 minify: "terser", terserOptions 才能生效
    terserOptions: { 
      compress: {
        // 生产环境时移除console
        drop_console: true,
        drop_debugger: true,
      },
    },
  }
})

打包优化 --- 效果✨

deleteOriginFile: true的情况下我们可以清晰看到文件体积缩减了大半,优化前后, dist 对比:
3.06M -> 1.20M

浏览器在支持gzip压缩的情况下,会自动加载gzip压缩过的文件而不是源文件,所以为了项目的正常运行我们一般将deleteOriginFile设为false,即不删除源文件。

看看 Lighthouse 的结果:
性能:74分 -> 89分
FCP:1.9s -> 1.3s
LCP:2.4 -> 1.6s
SI:2.8s -> 1.7s

总结

  • 对依赖包的分析:找出可能存在的冗余代码或者体积过大的模块
  • 选择合适的打包工具:Terser 进行代码压缩,Rollup 进行更高级的构建配置,而 Esbuild 则作为 Vite 默认的打包工具来处理模块打包过程。相互配合、取长补短
  • 尝试优化方案:chunk分割、gzip压缩、剔除调试语句 。。。

当然还有许多优化的方案,在此就不一一列举,图片懒加载(intersectionObserver) 、路由懒加载 、

组件懒加载、CDN引入静态资源 、小图片使用精灵图或base64压缩 等等。

这是我的第一次对项目进行打包优化的一次记录,作者水平有限,如有错误欢迎在评论区指正,一起讨论!😺

相关配置

项目代码仓库

typescript 复制代码
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import { visualizer } from "rollup-plugin-visualizer";
import viteCompression from "vite-plugin-compression";

// https://vitejs.dev/config/
export default ({ mode }: any) => {
  return defineConfig({
    build: {
      target: "es2020",
      minify: "terser", // Vite 2.6.x 以上需要配置 minify: "terser", terserOptions 才能生效
      terserOptions: {
        compress: {
          // 生产环境时移除console
          drop_console: true,
          drop_debugger: true,
        },
      },
      // rollup 配置
      rollupOptions: {
        output: {
          chunkFileNames: "js/[name]-[hash].js", // 产生的 chunk 自定义命名
          entryFileNames: "js/[name]-[hash].js", // 指定 chunks 的入口文件匹配模式
          assetFileNames: "[ext]/[name]-[hash].[ext]", // 自定义构建结果中的静态资源名称,资源文件像 字体,图片等
          manualChunks(id) {
            if (id.includes("node_modules")) {
              return "vendor";
            }
          },
        },
        plugins: [
          viteCompression({
            algorithm: "gzip", // 压缩算法,默认为'gzip'
            ext: ".gz", // 压缩文件扩展名,默认为'.gz'
            threshold: 10240, // 文件大小超过threshold时才会进行压缩,默认为10KB (10240 bytes)
            deleteOriginFile: false, // 是否删除原始文件,默认为false
            verbose: true, // 是否在控制台显示压缩信息,默认为true
          }),
        ],
      },
    },
    // 配置路径别名
    plugins: [
      vue(),
      visualizer(),
      ],
    // 优化依赖项(即第三方库)的加载和打包,使它们在预构建阶段单独处理并缓存,
    // 加快开发时的启动速度
    optimizeDeps: {
      // 用于指定需要优化的依赖包的名称或路径
      include: [
        "vue",
        "vue-router",
        "pinia",
        "axios",
        "element-plus/es/components/form/style/css",
        "element-plus/es/components/form-item/style/css",
        "element-plus/es/components/button/style/css",
        "element-plus/es/components/input/style/css",
        "@vueuse/core",
        "path-to-regexp",
        "echarts",
        "@wangeditor/editor",
        "@wangeditor/editor-for-vue",
        "vue-i18n",
        "codemirror",
      ],
    },
  });
};
相关推荐
栈老师不回家1 小时前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙1 小时前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
帅比九日3 小时前
【HarmonyOS Next】封装一个网络请求模块
前端·harmonyos
bysking3 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓4 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js