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

笔者的博客基于 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 打印出耗时信息。

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

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

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

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

优化方式

异步操作文件

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

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

js 复制代码
// 原
fs.readFileSync(filePath, 'utf-8')
// 新
fs.promises.readFile(filePath, 'utf-8')

异步创建子进程

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

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

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

js 复制代码
// 原
spawnSync('git', ['log', '-1', '--pretty="%ci"', url])

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

使用缓存

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

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

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

js 复制代码
const cache = new Map()

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

并发执行异步操作

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

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

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

js 复制代码
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 数。

js 复制代码
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 的逻辑非常耗时,文件内容越多,耗时越多。

js 复制代码
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 相关插件,发现打开即使页面里没有使用,也会增加额外的耗时,且会增加非常的多。

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

最后

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

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

没错:已经拉满了!

相关推荐
GDAL10 天前
VitePress 文件路由解析:从 Markdown 到 HTML 的映射艺术
前端·html·vitepress
大宝贱1 个月前
基于ffmpeg.wasm实现的视频格式转换工具
ffmpeg·wasm·webassembly·vitepress·视频格式转换
Justin3go2 个月前
第三次重构个人博客(基于Vitepress)
前端·vue.js·vitepress
橙红年代4 个月前
在VitePress中实现内联Vue组件
前端·vue.js·vitepress
gqkmiss5 个月前
使用 Vitepress 构建博客并部署到 github 平台
前端·前端框架·vue·github·博客·vitepress
gqkmiss5 个月前
VitePress 构建的博客如何部署到 Netlify 平台?
github·博客·vitepress·netlify
gqkmiss5 个月前
VitePress 构建的博客如何部署到 github 平台?
vue·github·vitepress
MiyueFE5 个月前
【TeaTools/auto-sync-blog】掘金下午茶团队自动化掘金文章同步工具 2.0 发布啦~~
前端·vitepress
无声20175 个月前
VitePress 实现归档与标签分类
前端·vitepress