使用 VuePress 2
🏷️ VuePress
由于服务器费用渐涨,最近尝试将博客迁移到可以使用 OSS 作为站点的静态部署方式,于是就发现了 VuePress。
VuePress 最新的版本是 v2.0.0-rc.0 ,还是 RC 版。使用中遇到了一些问题,不过好在基本功能都可以用。其中一个比较多大的问题在官方 issue 中也找到了一个暂时的解决方案 [1] 。
之所以选择 VuePress,主要还是感觉默认主题样式整体比较简洁,也支持我之前在自己的 .NET MVC 版本的博客中使用的 markdown-it 插件及其扩展插件。
另外也尝试用了一下 vue-hope 主题 [2] 。虽然功能很强大,更加适合作为博客来展示,但总归觉得颜值还是差了点意思。
这里记录一下最近使用中遇到的一些问题及解决方法。
1. Error: pageData() is called without provider.
完整日志如下:
Error: pageData() is called without provider.
at usePageData (chunk-DHNSDB55.js?v=b0e43099? [sm]:30)
at resolveArraySidebarItems (useSidebarItems.js?v=b0e43099? [sm]:79)
at resolveSidebarItems (useSidebarItems.js?v=b0e43099? [sm]:44)
at useSidebarItems.js?v=b0e43099? [sm]:24
at ReactiveEffect.fn (reactivity.esm-bundler.js:992)
at ReactiveEffect.run (reactivity.esm-bundler.js:176)
at ComputedRefImpl.get value [as value] (reactivity.esm-bundler.js:1003)
at triggerComputed (reactivity.esm-bundler.js:195)
at ReactiveEffect.get dirty [as dirty] (reactivity.esm-bundler.js:148)
at ComputedRefImpl.get value [as value] (reactivity.esm-bundler.js:1002)
2
3
4
5
6
7
8
9
10
11
有时候报的是这个错误:Cannot read properties of undefined (reading 'path')
这个是在点击 sidebar 中的文章目录时会触发这个错误,并且,如果 activeHeaderLinks
为 true
时,只要页面滚动时触发了文章目录的切换,也会触发该错误。
该错误发生后,页面很多地方的点击都不能正常工作,如:
- 点击 sidebar 切换页面时不再显示当前页面的目录;
- 切换到没有 sidebar 的页面时仍然会显示之前页面的 sidebar;
- 之前一度以为 VuePress 就是这样显示的。
- 在手机上点击 sidebar 中的目录时不会自动关闭 sidebar;
- 貌似也会影响 navbar 的点击(我当时没有配置菜单,但是官方的 issue 中有提到这个现象);
一度以为是我的 sidebar 使用方式不对导致的,不过根据官方 issue [1:1] 中的 Bug 报告,可能是由于 VuePress 和 Vue 3.4 的兼容性问题导致的。回复中暂时的对策就是使用 3.3.x 版本的 Vue。
npm install vue@3.3.13 --save-dev
另外在使用云效 Flow 部署时发现 build 的静态文件仍然有这个问题,后来尝试了几次,做了如下改动:
将 package.json 中的
"vue": "^3.3.13",
改成了"vue": "3.3.13",
。删除了提交到仓库中的
package-lock.json
文件。
具体是哪个改动有效,我也不太确定。
2. FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
这个是内存不够导致的。由于是迁移过来的博客,文章比较多,大概有 1100 多个页面。
参考网上的文章,本地开发时采用的是使用 increase-memory-limit 插件的方式。
安装 increase-memory-limit 组件
bashnpm i increase-memory-limit -D
1在 package.json 的
scripts
中添加 fix-memory-limit 脚本json"fix-memory-limit": "cross-env LIMIT=8192 increase-memory-limit"
1运行一次 fix-memory-limit 脚本
本地开发只需要运行一次就可以了,不必每次都运行。
bashnpm run fix-memory-limit
1替换
node_modules/.bin
目录下所有 .cmd 文件中的"%_prog%"
为%_prog%
[3]这一步是在 build 时出现 "node --max-old-space-size=8192"’不是内部或外部命令,也不是可运行的程序或批处理文件。 错误时才需要执行。
.cmd 应该是在 Windows 中才会被用到,估计是 Windows 系统独有的问题。
上面的操作执行后,本地环境就可以正常 build 了。
在云效 Flow 中执行 npm run docs:build
时还是会触发该错误。将编译脚本改为 npm run fix-memory-limit && npm run docs:build
后,打包会卡住。没有看到任何报错信息,不确定是哪里的问题。
最后将 build 脚本改成了如下方式 [4] 才可以在云效 Flow 中正常打包:
"docs:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' vuepress build docs",
3. 年月日目录结构时的 sidebar 设置
默认主题不支持标签、分类之类的设置,而 config 文件中的 sidebar 配置貌似只支持路径为单位。年月日为目录结构时,同一个系列的文章可能会分散在多个目录。
最终采用的方案是在文章的 frontmatter 中配置 sidebar。
写法示例如下:
sidebar:
- text: Sidebar Title
children:
- ../1/blog-1.md
- ../../11/1/blog-2.md
- ../../../2023/11/2/blog-3.md
- ./blog-4.md
2
3
4
5
6
7
缺点就是每次添加文章时,所有相关文章的 frontmatter 都需要修改。
4. 年月日目录结构时的文章索引
由于个人博客的文章内容比较松散,分类也很凌乱,而且又是以年月日为单位的目录结构,不太好像 VuePress 官网一样制作一个比较简单,但是又包含所有文章的 sidebar 和 navbar。
最后采用的方案是:
- 每个页面通过 frontmatter 中的
prev
和next
指定前后页面; - 在每个年和月的目录下添加一个索引页面。
中间也尝试使用了 vuepress-plugin-auto-catalog 插件,但是效果不太理想,自动生成的索引页面中有很多重复的内容,不知道是什么原因导致的。
由于文章较多,自己手动生成显然太过麻烦,于是使用 ChatGPT 生成了一段 Python 脚本,然后自己再改一改。
点击查看脚本
import os
import yaml
base = "docs"
def generate_list_readme():
# 构建头部内容
title = "博客归档"
# 构建头部字典
header = {
"title": title
}
# 构建头部字符串
header_str = yaml.safe_dump(header, sort_keys=False, allow_unicode=True)
# 构建内容字符串
content = f"# {title}\n\n"
subdirectories = [subdir for subdir in os.listdir(f"./{base}") if os.path.isdir(f"./{base}/{subdir}") and subdir.isnumeric()]
subdirectories = sorted(subdirectories, key=lambda x: int(x))
for subdir in subdirectories:
year = subdir.split("/")[-1]
content += f"- [{year} 年](./{year}/README.md)\n\n"
# 生成 list.md 文件
readme_content = f"---\n{header_str}---\n\n{content}"
with open(f"./{base}/years.md", "w", encoding="utf-8") as f:
f.write(readme_content)
def generate_year_readme(year):
# 构建头部内容
title = f"{year} 年"
sidebar_text = f"博客归档"
sidebar_children = []
# 遍历子目录,获取子目录下的 README.md 文件相对路径
yeardirectories = [subdir for subdir in os.listdir(f"./{base}") if os.path.isdir(f"./{base}/{subdir}") and subdir.isnumeric()]
yeardirectories = sorted(yeardirectories, key=lambda x: int(x))
for subdir in yeardirectories:
sidebar_children.append(f"../{subdir}/README.md")
# 构建头部字典
header = {
"title": title,
"sidebar": [
{
"text": sidebar_text,
"children": sidebar_children
}
]
}
# 构建头部字符串
header_str = yaml.safe_dump(header, sort_keys=False, allow_unicode=True)
# 构建内容字符串
content = f"# {title}\n\n"
subdirectories = [subdir for subdir in os.listdir(f"./{base}/{year}") if os.path.isdir(f"./{base}/{year}/{subdir}") and subdir.isnumeric()]
subdirectories = sorted(subdirectories, key=lambda x: int(x))
for subdir in subdirectories:
month = subdir.split("/")[-1]
content += f"- [{title} {month} 月](./{subdir}/README.md)\n\n"
# 生成 README.md 文件
readme_content = f"---\n{header_str}---\n\n{content}"
with open(f"./{base}/{year}/README.md", "w", encoding="utf-8") as f:
f.write(readme_content)
def generate_month_readme(year, month):
# 构建头部内容
title = f"{year} 年 {month} 月"
sidebar_text = f"{year} 年"
sidebar_children = []
# 遍历子目录,获取子目录下的 README.md 文件相对路径
subdirectories = [subdir for subdir in os.listdir(f"./{base}/{year}/{month}") if os.path.isdir(f"./{base}/{year}/{month}/{subdir}") and subdir.isnumeric()]
subdirectories = sorted(subdirectories, key=lambda x: int(x))
yeardirectories = [subdir for subdir in os.listdir(f"./{base}/{year}") if os.path.isdir(f"./{base}/{year}/{subdir}") and subdir.isnumeric()]
yeardirectories = sorted(yeardirectories, key=lambda x: int(x))
for subdir in yeardirectories:
sidebar_children.append(f"../{subdir}/README.md")
# 构建头部字典
header = {
"title": title,
"sidebar": [
{
"text": sidebar_text,
"children": sidebar_children
}
]
}
# 构建头部字符串
header_str = yaml.safe_dump(header, sort_keys=False, allow_unicode=True)
# 构建内容字符串
content = f"# {title}\n\n"
for subdir in subdirectories:
day = subdir.split("/")[-1]
content += f"## {month} 月 {day} 日\n\n"
files = [file for file in os.listdir(f"./{base}/{year}/{month}/{subdir}") if file.endswith(".md") and subdir.isnumeric()]
for file in files:
file_path = f"./{base}/{year}/{month}/{subdir}/{file}"
with open(file_path, "r", encoding="utf-8") as f:
metadata = f.read().split("---\n")[1]
md_title = yaml.safe_load(metadata)["title"]
relative_file_path = f"./{subdir}/{file}"
link = f"[{md_title}]({relative_file_path})"
content += f"- {link}\n\n"
# 生成 README.md 文件
readme_content = f"---\n{header_str}---\n\n{content}"
with open(f"./{base}/{year}/{month}/README.md", "w", encoding="utf-8") as f:
f.write(readme_content)
generate_list_readme()
for year in [subdir for subdir in os.listdir(f"./{base}") if os.path.isdir(f"./{base}/{subdir}") and subdir.isnumeric()]:
generate_year_readme(year)
for month in [subdir for subdir in os.listdir(f"./{base}/{year}") if os.path.isdir(f"./{base}/{year}/{subdir}") and subdir.isnumeric()]:
generate_month_readme(year, month)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
将脚本放在仓库根目录下运行即可。
运行后会在每个年和月的目录下生成 README.md 文件,最外层的 README.md 由于是首页,所以改为写入到了 years.md 文件。
WARNING
运行脚本会覆盖同名文件,如果之前已经存在这些文件,请注意备份或提交。
代码也比较简单,可以根据需要自行修改即可。
2024-02-22 追记
最近一段时间没更新博客,今天发现项目发布失败了。报了如下错误:
[19:18:17] TypeError: path.split is not a function or its return value is not iterable
[19:18:17] at normalizeRoutePath (file:///root/workspace/blog-vue-press_HPGY/node_modules/@vuepress/shared/dist/index.js:82:44)
[19:18:17] at resolveRoutePath (file:///root/workspace/blog-vue-press_HPGY/docs/.vuepress/.temp/.server/app.CPu5aRqi.mjs:10062:26)
[19:18:17] at resolveRoute (file:///root/workspace/blog-vue-press_HPGY/docs/.vuepress/.temp/.server/app.CPu5aRqi.mjs:10071:21)
[19:18:17] at getNavLink (file:///root/workspace/blog-vue-press_HPGY/docs/.vuepress/.temp/.server/app.CPu5aRqi.mjs:10933:36)
[19:18:17] at resolveFromFrontmatterConfig (file:///root/workspace/blog-vue-press_HPGY/docs/.vuepress/.temp/.server/app.CPu5aRqi.mjs:12007:16)
[19:18:17] at file:///root/workspace/blog-vue-press_HPGY/docs/.vuepress/.temp/.server/app.CPu5aRqi.mjs:12052:26
[19:18:17] at ReactiveEffect.fn (/root/workspace/blog-vue-press_HPGY/node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js:927:13)
[19:18:17] at ReactiveEffect.run (/root/workspace/blog-vue-press_HPGY/node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js:162:19)
[19:18:17] at get value [as value] (/root/workspace/blog-vue-press_HPGY/node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js:939:109)
[19:18:17] at file:///root/workspace/blog-vue-press_HPGY/docs/.vuepress/.temp/.server/app.CPu5aRqi.mjs:12066:44
2
3
4
5
6
7
8
9
10
11
最后发现是 VuePress 更新导致的,貌似 frontmatter 移除了好多配置项。官方文档里已经找不到我这里一直在使用的 prev
、next
和 sidebar
了,只剩下了几个基础的配置,导致可自定义性低了好多。
上面这个错误只要移除 frontmatter 里的 prev
、next
后就可以修复了。
还是希望小版本升级时不要有这么大的改动,不然文章过多时,更新起来实在是很麻烦。
更新过程中发现新版的 create-vuepress 提供了一个 blog 模式的选项。该模式下默认提供了博客列表、分类、标签和时间线页面,比较适合博客的场景。不过功能比较基础,没有分页,在文章、分类比较多时页面展示效果并不好,而且页面也会比较大。上千个文章显示在同一个页面,即使只有文章摘要,页面加载起来还是明显感觉会慢很多。