我之前的博客是用一个php程序做的,安全性不是很好,使用起来也不是很舒服。于是在2022年5月份,我决定动手对博客进行重构,重构前我已经确定好我需要的是什么样的。
必须要具备的: markdown书写、Git托管内容、代码高亮、内容标签聚合、支持静态化(兼顾SEO)
最好能支持的: 无需后端、自动发布、内容搜索、支持代码复制、能有单独的页面、文章详情带目录导航。
最初我计划自己写一套markdown的解析逻辑,自己根据各种配置去进行内容关联,在调研的过程中发现了一些或许能满足我需求的程序,比如: hexo dumi gatsby.js vuepress1 vuepress2 vitepress nuxt.js
最终我选择了vuepress2来重构何喜阳博客,我觉得这个是比较好的,能满足功能需求、操作也相对简单。
在我重构时(2022.05)vuepress2仍处于beta版本。
博客功能的简单实现
vuepress2对博客的支持其实没那么好,不像vuepress1有官方插件,目前比较好的是vuepress2贡献者做的vuepress-plugin-blog2
插件,但一两个小地方不是很符合我的想法,而且里面的功能我也不是全部需要,于是我参考这个插件的源码自己实现了一个简易版本的供自己使用。主要步骤如下:
以下内容需要有一定的开发基础,仅当做思路分享,如果没有任何经验建议直接使用vuepress-theme-hope这个主题,内置了vuepress-plugin-blog2等众多插件,上手比较简单。
首先我们要创建一个插件,
通过插件API 的 onInitialized
我们能够读取所有页面内容
// 以下内容都是要写到 onInitialized 中,或通过其调用
const tagMap = new Map()
// 依据内容时间进行排序,也可以在这一步过滤掉不符的内容
const articleSort = (a, b) => (a.frontmatter.date > b.frontmatter.date ? 1 : -1)
const articles = app.pages.sort((a, b) => articleSort(a, b))
articles.forEach((item, index) => {
if (item?.frontmatter?.tag?.length > 0) {
// 收集标签信息。
// 由于我没有使用分类,因此只需要标签,收集分类信息也是这个逻辑
item.frontmatter?.tag?.forEach((tag) => {
if (!tagMap.has(tag)) {
tagMap.set(tag, [])
}
tagMap.get(tag).push(item)
})
}
// 处理上一页下一页,在主题模板中可以读取到这个数据
if (index !== 0) {
const { path, title } = articles[index - 1]
articles[index].data.prev = { path, title }
}
if (index !== articles.length - 1) {
const { path, title } = articles[index + 1]
articles[index].data.next = { path, title }
}
})
通过以上步骤我们就拿到了一个 articles 的数据,这个数据中包含了所有的文章信息,包括标签信息,上一页下一页信息等。 也拿到了一个 tagMap ,这里包含了所有的标签信息,以及对应标签的文章。接下来我们就可以根据这些内容去创建 文章内容列表、标签列表、标签内容列表了,基本上已经满足一个博客的需求了。
// 按照每页10篇内容,通过createPage生成文章列表页面,标签内容列表也是同样的逻辑
const pageSize = Math.ceil(articles.length / 10)
for (let index = 1; index < pageSize; index++) {
// frontmatter 有些信息是根据主题模板使用的,layout表示要通过什么模板渲染,和在markdown写是一样的
const frontmatter = {
layout: 'ArticleList',
title: `博客内容 - 第${index + 1}页`,
type: 'article',
page: index + 1,
description: `博客内容 - 第${index + 1}页`,
}
app.pages.push(
await createPage(app, {
path: `/article/index_${index + 1}.html`.toLowerCase(),
frontmatter,
}),
)
}
app.pages.push(
await createPage(app, {
path: `/article/`,
frontmatter: {
layout: 'ArticleList',
title: `博客内容`,
page: 1,
description: '博客内容',
},
}),
)
通过 createPage 方法我们已经成功创建了内容列表。
主题模板中怎么知道渲染哪些数据呢?此时我们还需要通过 app.writeTemp 把收集到的内容写入临时文件,在页面模板中读取并展示。
// 写入临时文件。articles是上面的文章列表,article.js 是我们起的名字,在主题模板中使用
app.writeTemp('articles.js', `export default ${JSON.stringify(articles)}`)
// 读取临时文件
import articles from '@temp/articles'
// 读取 frontmatter
import { usePageData, usePageFrontmatter } from '@vuepress/client'
const pageFrontmatter = usePageFrontmatter()
接下来就是根据 frontmatter 提供的page页码来决定渲染第x-第y条文章数据了,你也可以在主题模板中自己实现一个页码来切换显示。
我在这里遇到的一点问题是使用 <route-link>
标签切换页码时数据不会变化,可能是我操作不对,目前通过一个折中的方式解决,也在这里写出来,接下来对内容列表做一些改动。
const reload=()=>{
// 此处需要你自己实现,需要把影响数据的逻辑写到reload方法中
}
// 进来直接调用一次
reload()
onMounted(() => {
window.reload = reload
})
onUnmounted(() => {
window.reload = null
})
借助 enhance 方法在路由切换时触发 reload 方法来实现数据切换。
// .vuepress/client.(ts/js)
import { defineClientConfig } from '@vuepress/client'
export default defineClientConfig({
enhance({ app, router, siteData }) {
router.afterEach(() => {
// @ts-ignore
if (!__VUEPRESS_SSR__) {
// 主动创建的列表页,在列表页切换页码时,数据渲染有问题,暂时通过这个事件来解决
window['reload'] && window['reload']()
}
})
},
})
到这里,博客功能就已经实现了。借助vuepress2已经自动渲染了我们的博客内容,我们还实现了内容列表、内容列表分页,标签列表,上一页下一页等功能。
markdown图片宽高自定义
我在做完博客功能后,要对以前的内容进行整理迁移,同时还需要写几篇新的内容当做作品介绍,但遇到了markdown图片显示不符合预期的问题。 查了一下,markdown的图片是不支持指定宽高的,只能借助模板的样式来固定一个大小,但有些图片需要很宽的显示、有些图片需要很小的显示,如果直接调整图片大小也可以,但会影响到图片放大预览的清晰度,而且后续操作很麻烦。我就通过参数的方式借助JS修改了图片大小
markdown写法,添加?w= &h= &fit= 参数

在模板里增加逻辑:
const isNumber = (text) => {
return /^[0-9]*$/.test(text)
}
const imgsSetW = () => {
const imgs = document.querySelectorAll('img')
imgs.forEach((img) => {
const src = img.getAttribute('src')
const query = new URLSearchParams(src.split('?')[1])
const w = query.get('w')
const h = query.get('h')
const fit = query.get('fit')
// 修改元素的style
img.style.width = isNumber(w) ? `${w}px` : w
img.style.height = isNumber(h) ? `${h}px` : h
img.style.objectFit = fit
img.style.maxWidth = '100%'
img.style.maxHeight = '100%'
})
}
onMounted(() => {
imgsSetW()
})
同时为了避免图片有较大幅度的宽高变化引起视觉不适,还通过CSS给带参数的图片限定了初始大小:
img {
&[src*='w='],
&[src*='fit='] {
max-width: 200px;
}
&[src*='h='],
&[src*='fit='] {
max-height: 200px;
}
}
这就是markdown自定义图片大小功能的实现了,可能不是特别好,如果什么好的方案也请分享给我。
草稿功能实现
草稿功能最初打算使用Frontmatter配置类似 status: draft ,再通过插件API来过滤掉。操作过程中发现通过vuepress2的配置项就能很好的解决草稿功能,
export default defineUserConfig({
//...其他配置项
pagePatterns: ['**/*.md', '!**/_*.md','!node_modules'],
})
指定所有以 _下划线开头的内容 和 node_modules 的内容不参与渲染,我不需要被渲染的内容暂时通过_开头就行了。
其他
这篇内容主要还是记录我使用vuepress2重构了博客,顺带说了一些我遇到的问题以及解决方案,还有一些内容没写进来,也还有一些功能要做,后续我会再单独分享,博客都已经重构完成了,接下来也要多写内容了。