首页 > 其他分享 >博客构建性能优化笔记 | 提速 3 倍

博客构建性能优化笔记 | 提速 3 倍

时间:2024-06-12 22:33:46浏览次数:11  
标签:插件 const 提速 博客 耗时 笔记 优化 RSS

笔者的博客基于 VitePress 搭建的,使用其自定义主题能力完成博客主题 @sugarat/theme 的搭建。

前段时间有群友反馈说使用主题构建后耗时增加非常明显。

前后耗时大概增加了 10 倍,过于离谱了。

断断续续的投入差不多 1 个月的时间完成了优化,效果还是很明显。

至此写篇文章记录&分享一下优化过程。

先看一下优化前后的效果

  • 测试项目:笔者的博客,差不多 490 篇文章。
  • 测试机器:Mac Mini (M1, 2020)

仅开启博客相关的样式能力

VitePress 默认主题 优化后主题 优化前主题
16.38s 20.56s 32.36s
对比目标 +4.18s +15.98s

开启拓展能力

RSSpagefind 离线搜索

优化后 优化后 优化前
RSS不开启HTML生成 + 离线搜索 RSS + 离线搜索 RSS + 离线搜索
25.70s 30.93s 50.85s
+9.4s +14.55s +34.47s

小结

整体提速约 3 倍:

  • 只开启基础能力:额外耗时从 16s 缩短至 4s
  • 拓展能力耗时:额外耗时从 34s 缩短到 10 s

问题定位

先定位耗时的位置,再想办法进行优化。

我们可以直接用 console.timeconsole.timeEnd 打印出耗时信息。

console.time('flag')
// 执行代码
console.timeEnd('flag') // 打印出耗时

主要关注有循环和外部调用的逻辑,在其前后加上打印耗时。

简单打了几个点,就有如下的结果咯 ⏰。

在主题入口和两个插件都有一段类似的代码逻辑,读取文件内容构造 meta 信息。

优化方式

异步操作文件

读取文件内容用于提取 frontmatter 信息,生成描述,标题等内容,会用于首页渲染。

使用 fs.promises 异步操作文件,这样可以避免阻塞进程。

// 原
fs.readFileSync(filePath, 'utf-8')
// 新
fs.promises.readFile(filePath, 'utf-8')

异步创建子进程

主要通过调用 git 指令获取文件最后的修改时间,用于展示文章的最后的修改时间。

原来使用的 spawnSync,同样也是同步执行的方法。

使用 spawn + Promise 替换 spawnSync,避免阻塞进程。

// 原
spawnSync('git', ['log', '-1', '--pretty="%ci"', url])

// 新
const child = spawn('git', ['log', '-1', '--pretty="%ai"', url])

使用缓存

在日志里可以发现,Vite 插件里 load 钩子在 vitepress build 时执行了2次。

因此针对会重复执行的逻辑,可以添加添加一段缓存读写的逻辑,能明显降低二次执行相关逻辑的时间。

时间的获取使用 Map 缓存文件的日期信息,在文件路径不变的情况下复用上一次获取的内容

const cache = new Map()

const cached = cache.get(url)
if (cached) {
  return cached
}

并发执行异步操作

如果是 await new promise 在执行的时候才创建和获取 promise 结果,提升不是特别明显。

比如 spawn 创建子进程调用,配合 await promise,在文章数量较多时,依然会有明显的耗时。

所以可以将文件内容和 git 时间的获取动作提前且并发执行。

const contentPromises = files.reduce((prev, f) => {
  prev[f] = {
    contentPromise: fs.promises.readFile(f, 'utf-8'),
    datePromise: getFileBirthTime(f)
  }
  return prev
}, {})

但在测试的时候发现这样写偶尔会执行出错或提升不明显,大概是并发的执行的 Promise 和 spawn 创建子进程过多的关系。

于是引入 p-limit 来控制并发的 promise 数。

import os from 'node:os'
import pLimit from 'p-limit'

const limit = pLimit(+(process.env.P_LIMT_MAX || os.cpus().length))
const metaPromise = limit(() => getArticleMeta())

这里默认值使用os.cpus().length来获取 CPU 核心的数量,这样创建的子进程能充分利用上多核的能力,不然并行值调得再大,也不会有明显的提升。

非必要第三方能力提供开关

有些能力,可能没有用到,但是打开就是会增加额外的耗时,对文件做了不改变内容的分析与处理

在测试中发现 RSS 生成 HTML 的逻辑非常耗时,文件内容越多,耗时越多。

const fileContent = fs.readFileSync(file, 'utf-8')
const { createMarkdownRenderer } = await import('vitepress')
const mdRender = await createMarkdownRenderer()
const html = mdRender.render(fileContent)

vitepress 内置使用的 markdown-it ,并且内置了许多的插件,html 作为 RSS 内容的组成也不是必要的部分,因此可以做成可选的能力,交由用户选择是否开启,同时将生成的方式也做成可配置的,用户可以传入更加精简的生成方法。

另一个是 markdown 图表的渲染,主题内置的 mermaid 相关插件,发现打开即使页面里没有使用,也会增加额外的耗时,且会增加非常的多。

因此将这个也弄成默认关闭,由用户自己选择是否开启,深度优化需要修改对应插件的源码,还没来得及研究这个计划后续再做。

最后

个人觉得代码应该还有优化空间,下来再探索一下,攒一波有重大突破再来分享分享。

博客本身的优化,之前也发文章分享过来,感兴趣的可以看看:博客性能优化笔记

没错:已经拉满了!

标签:插件,const,提速,博客,耗时,笔记,优化,RSS
From: https://www.cnblogs.com/roseAT/p/18244842

相关文章

  • Python3 笔记:字符串的 replace() 和 expandtabs()
    1、replace()方法把字符串中的old(旧字符串)替换成new(新字符串),如果指定第三个参数max,则替换不超过max次。语法:str.replace(old,new[,max])参数:old:将被替换的子字符串。new:新字符串,用于替换old子字符串。max:可选参数,如果填写则表示替换不超过max次。str1='old......
  • 《代码大全2》阅读笔记01
       《代码大全2》是一本经典的软件开发指南,其中详细介绍了软件开发中的各个方面,如编程技巧、设计原则、代码测试等。通过丰富的案例和具体实践,作者强调了软件工程中的“实用性原则”,即以实际问题为出发点,注重解决问题的有效性和效率。从书中学习了软件开发中的“面向对象设......
  • 《代码大全2》阅读笔记02
    《代码大全2》这本书通过具体案例和实用技巧,深入浅出地介绍了软件开发中的各个关键技术和方法。阅读本书不仅让我对软件开发有了更深入的理解,还使我明白了设计、编程、测试等方面的重要性。书中强调了软件工程中的实用性原则,即解决问题的有效性和效率最重要。学习了面向对象设计原......
  • 《代码大全2》阅读笔记03
     《代码大全2》通过丰富的案例和具体实践,详细介绍了软件开发中的各个方面,如编程技巧、设计原则、代码测试等。书中强调了软件工程中的“实用性原则”,提倡以实际问题为出发点,注重解决问题的有效性和效率。学习了软件开发中的“面向对象设计”原则,将软件系统分解为对象,并设计良好的......
  • 初阶C语言(01)—学习笔记
    if语言if语句其一C语言被称为结构化的程序设计语言,包括顺序结构、选择结构(ifswitch)和循环结构(for while dowhile)。因为今天要下雨,所以必须带伞。这就是一个简单的选择语句。例如:如果你的年龄大于18岁,那么输出成年。#include<stdio.h>intmain(){ intage=20;......
  • Python学习笔记6:pychram相关知识及安装教程,后续需要学习的入门知识
    上篇文章说了,今天去公司重新装一下IDE,最后也是把过程这边再记录一下,有需要的可以参考一下。关于pychrampychram是什么?PyCharm是由JetBrains公司开发的一款流行的Python集成开发环境(IDE)。它专为Python语言设计,提供了许多方便的功能来帮助开发者编写、测试和调试Python代码......
  • C++学习笔记,文件操作;文件写入读取
    目录5文件操作5.1文本文件5.1.1写文件5.1.2读文件 5.2二进制文件  5.2.1写文件5.2.2读文件 5文件操作程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放通过文件可以将数据持久化C++中对文件操作需要包含头文件<fstream>文件类型分为两......
  • 学习笔记——路由网络基础——路由优先级(preference)
    1、路由优先级(preference)路由优先级(preference)代表路由的优先程度。当路由器从多种不同的途径获知到达同一个目的网段的路由(这些路由的目的网络地址及网络掩码均相同)时,路由器会比较这些路由的优先级,优选优先级值最小的路由。路由来源的优先级值(Preference)越小代表加......
  • 最详细的JS学习笔记(连载)第二章、函数(参数)
    4、函数的参数(1)、函数的参数是什么函数的参数是用一个数组来表示的,可以在函数内部通过arguments对象来访问,但是arguments不是一个Array实例;(2)、形参和实参形参:定义在函数名后面小括号中的变量叫做形参变量。定义了形参,也不一定非要给他传值,如果定义了,但是执行的时候......
  • RAG PAPTOR 示例代码理解笔记
    RAGPAPTOR示例代码理解笔记0.源代码文件1.部分代码理解笔记故事背景导入工具固定种子(随机种子)全局降维函数局部降维函数获取最佳聚类数函数GMM聚类函数执行聚类函数嵌入函数嵌入并聚类文本函数格式化文本函数嵌入、聚类并总结文本函数递归嵌入、聚类并总结函数总结......