前言
两年前,我弃用了原来的hexo博客系统,并用vuepress1.4.1版本快速搭建了自己现在的专属静态博客系统。并一直更新维护至今。博客内陆陆续续的定制了自己的个性首页和列表页,扩展了评论、footer、复制、图片预览等等博客常见功能,效果和UI实现还是让我比较满意的。但是自从vuepress升级到2.X后,就一直心动的想要将现有博客进行升级,奈何平时比较忙(懒癌晚期),所以一直没有付诸行动,最近闲下来,所有打算动起来。
这次虽然是重新从零搭建,但其实很多逻辑和1.0是相通的,所以我的博客1.0还是可以用来参考的,如果你也是vuepress1.X版本,也可以参考官方的迁移文档
VuePress2的亮点
- 简介至上
- 支持vue3.0,就是好用。
- 支持typescript,学习、开发必备技能。
- vite打包,就是快。
多语言支持
VuePress和其它博客系统的对比可以参看官方给出的介绍
项目搭建
项目创建&初始化
# 创建并进入博客文件
mkdir vuepress-blog
cd vuepress-blog
# 初始化git
yarn init
# 安装vuepress本地依赖
yarn add -D vuepress@next
# 添加ignore内容
echo 'node_modules' >> .gitignore
echo '.temp' >> .gitignore
echo '.cache' >> .gitignore
# 添加第一个md文档
mkdir docs
echo '# Hello VuePress' > docs/index.md
在 package.json
中添加一些scripts
{
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
}
命令行内运行下面代码,就可以顺利的启动一个热重载的博客项目了
yarn docs:dev
搭建首页
vuepress允许我们依赖Frontmatter->layout
来自定义页面布局;下面说一下首页
docs/.vuepress/Layouts
文件内新建Home.vue
文件,因为自定义的页面说白了也是一个组件,所以我们可以按照平时写vue3组件的方式去写首页内容<template> <view>首页</view> </template>
在
docs/.vuepress/client.ts
文件中注册Home组件import { defineClientConfig } from '@vuepress/client' import Home from './Layouts/Home.vue' export default defineClientConfig({ layouts: { Home, } })
在一开始创建的
docs/index.md
文件内,讲自定义布局组件进行引入--- title: 首页 layout: Home ---
到这我们的首页就生成好了,还是比较简单的对吧,别急,下面的列表页才是最复杂的地方
列表页面
列表页最关键的就是要如何才能拿到所有文章的数据内容,这里我们依赖官方的插件API来实现
因为我们需要创建很多md文件,但是并不一定所有的文件都需要显示在列表页,所以我们首先要约定下什么格式的文件才是我们需要的博客文档:我们可以设置黑名单来进行排除,当然我这里用的正则匹配的方式,即https://slbyml.github.io/**/**
是我们需要的博客文件,否则就是其他文件,不进行统计,比如:https://slbyml.github.io/
、https://slbyml.github.io/*.html
第一步:创建插件文件.vuepress/plugins/page.js
:
export default {
name: 'vuepress-plugin-page',
onInitialized(app) {
const lists = []
app.pages.forEach((item:Page) => {
// 排除不需要的页面
if (/^\/[\s\S]*\/[\s\S]*/.test(item.path)) {
// 没有直接将整个item放进lists是为了减少传递大量没用的数据
lists.push({
path: item.data.path,
title: item.data.title,
frontmatter: item.data.frontmatter,
git: item.data.git
})
}
// 将我们组装好的列表传递到list页面
if (item.path === '/list.html') {
item.data = {
...item.data,
lists
}
}
});
// 这里还需要进行下简单的排序
lists.sort((s1, s2) => {
return +new Date(s2.git.lastUpdated) - +new Date(s1.git.lastUpdated)
})
}
}
当然,向列表页传递数据也可以参考官方给的方案:向客户端代码传递数据
第二步:创建自定义列表布局.vuepress/Layouts/List.vue
<template>
<Navbar />
<div class="contain">
<div class="lists" v-for="item in lists" :key="item.path">
<a :href="item.path" class="link" :title="item.title">{{item.title}}</a>
<div> {{item.frontmatter.description}}</div>
</div>
</div>
</template>
<script>
import { usePageData } from '@vuepress/client'
import Navbar from '@theme/Navbar.vue'
import { ref } from 'vue'
export default {
components: {
Navbar // 官方原生的header,可以直接拿来用
},
setup() {
const page = usePageData()
const allList = ref(page.value.lists || []) // 我们在上一步传递进来的所有文章列表
return {
allList
}
}
}
</script>
<style lang="scss" scoped></style>
第三步:注册列表布局.vuepress/client.ts
import { defineClientConfig } from '@vuepress/client'
import Home from './Layouts/Home.vue'
import List from './Layouts/List.vue'
export default defineClientConfig({
layouts: {
Home,
List
}
})
第四步:创建列表文件docs/list.md
,将我们的自定义布局引入进去
---
title: 文章列表
layout: List
---
至此我们最重要的首页和列表页就完成了
文章标签收集
先来看看我文章内frontmatter
最重要的两个
---
tags:
- vuepress
- 前端
description: 文章的描述信息,
---
description
:文章自定义的描述信息,在列表页内item.frontmatter.description
方法可以拿到tags
就是我们文章的自定义标签,通过frontmatter.tags
就可以获得了,我是在列表页用到的const allTags = allList.value.reduce((previous, current) => { return previous.concat(current.frontmatter.tags) }, [])
分页
我们既然可以拿到所有的文章列表的数组,那么我们就可以通过allList
来自定义我们的分页逻辑了,还是比较简单的,对吧!
文章页用扩展的默认布局
具体文章页如果只需要官方默认的布局那可以不看这部分内容,我做的事情主要是继承了官方默认布局,并扩展了阅读进度、评论、打赏、footer等功能;
创建 .vuepress/layouts/Layout.vue
<template>
<ParentLayout>
<template #page-content-bottom>
<div class="money">打赏</div>
<div>评论</div>
</div>
</template>
<template #page-bottom>footer</template>
</ParentLayout>
</template>
<script>
import ParentLayout from '@vuepress/theme-default/layouts/Layout.vue'
export default {
components: {
ParentLayout
}
}
</script>
添加默认布局组件.vuepress/client.ts
import { defineClientConfig } from '@vuepress/client'
import Layout from './Layouts/Layout.vue'
export default defineClientConfig({
layouts: {
Layout
}
})
好了,完成这一步后,我们所有文章页都会使用这个布局方式来渲染。
观察组建可以发现我们都是通过插槽来实现的扩展,更多布局插槽可以参考官方继承,相信聪明的博友应该能过痛过它实现更有意思的东西
添加Giscus评论
评论是一个博客的灵魂,为博客提供了互动的能力,上一版博客我是用的GITALK来实现的,它是基于issue
来实现的,所以它有一些不好的地方让我不是很满意,比如:评论无法叠楼层、需要博主先登录文章页去初始化评论功能,所以这次我决定改用基于Github Discussions
的Giscus来实现;当然,如果你依然对GITALK
情有独钟,可以参考这篇文章。
通过上一步的布局扩展发现,我们可以将评论组件放置在#page-content-bottom
插槽内,Giscus
贴心的问我们提供了各种常用的引用方式:Vue组件
、React组件
、web component
等方式;我们可以使用自己喜欢的方式进行安装引用。
<Giscus
repo="slbyml/slbyml.github.io"
repoId="R_kgDOH_OGFQ"
category="Announcements"
categoryId="DIC_kwDOH_OGFc4CRZwH"
mapping="specific"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="top"
theme="light"
lang="zh-CN"
loading="lazy"
/>
我们主要说一下repo
、repoId
、category
、categoryId
怎么去获得:
首先进入Giscus官网,按照流程填写必要信息,注意仓库地址不要带https,填写完后,下面就会生成必要信息,我们直接在组件内替换对应的参数就可以了
添加自定义功能
一个完整、个性、有意思的博客肯定还会有很多自定义的JS内容要实现,比如copy自动添加版权、控制台默认输出定制log、动态页面title等等;这些大部分都有第三方组件来协助我们完成这些东西,但是如果找不到我们就需要自己去写,我们以copy自动添加版权
为例(这个也有三方插件,可以自行搜索):
新建文件.plugins/plugins/copy.js
export default () => {
function addCopy(e) {
let copyTxt = ""
e.preventDefault(); // 取消默认的复制事件
copyTxt = window.getSelection(0).toString()
copyTxt = `${copyTxt}\n作者:静水深流\n原文:${window.location.href}\n著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。`
// 将信息写入粘贴板
const clipboardData = e.clipboardData || window.clipboardData
clipboardData.setData('text', copyTxt);
}
document.addEventListener("cut", e => {
addCopy(e)
});
document.addEventListener("copy", e => {
addCopy(e)
});
}
修改.vuepress/client.ts
文件:
import { defineClientConfig } from '@vuepress/client'
import copy from './plugins/copy'
export default defineClientConfig({
async setup() {
if(!__VUEPRESS_SSR__){ // 运行在客户端
copy()
}
}
})
__VUEPRESS_SSR__
代表函数不运行在服务端(SSR)下;这个常量是Vuepress为我们提供的,更多变量参考常量
草稿功能
很多时候我们可能会同时写多篇笔记和文章,或者是写完后暂时不想发出去,这个时候,就需要一个草稿箱来暂时存储我们的文章,以防影响发表别的文章,我们有两种方式来实现
第一种就是修改.vuepress/plugins/page.js
文件,通过在代码中过滤掉特定格式的文件来实现不讲文件链接往外暴露,这种方式间接的实现了草稿箱功能,但是如果知道链接或者通过搜索功能搜索到了这个页面还是可以进去的
第二种也是我现在正在用的方式:通过官方提供的pagePatterns
功能,它可以避免某个文件被 VuePress 处理,所以我们可以通过这个功能来实现想要的效果,我是将所有下划线开头的文件作为草稿文件的
修改.vuepress/config.ts
import { defineUserConfig } from 'vuepress'
export default defineUserConfig({
pagePatterns:['**/*.md','!**/_*.md','!node_modules']
})
生成sitemap
博客只有在别人能够搜索的到才有更多创作的意义,因此我们需要生成站点地图:sitemap
,它做seo必不可少的文件,首先安装装这个插件yarn add vuepress-plugin-sitemap2 -D
;然后修改.vuepress/config.ts
import { defineUserConfig } from 'vuepress'
import { sitemapPlugin } from "vuepress-plugin-sitemap2"
import { googleAnalyticsPlugin } from '@vuepress/plugin-google-analytics' // 做google收录&上报分析用
export default defineUserConfig({
plugins: [
googleAnalyticsPlugin({
id: 'google-analytics生成的'
}),
sitemapPlugin({
hostname: 'https://slbyml.github.io'
})
]
})
然后我们就可以将生成的文件https://slbyml.github.io/sitemap.xml
进行百度收录、谷歌收录、360、搜狗、Bing等等能提交的都提交下
打包部署
我的源码文件和打包文件是分开来存储的,一个私有,一个公开。主要是因为源码中包含一些备注文件不太方便公开,因此为了方便,写了一个自动打包上传的方法,
#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e
# 生成静态文件
yarn build
# 进入生成的文件夹
cd docs/.vuepress/dist
git init
git add -A
git commit -m 'blog'
git push -f git@github.com:slbyml/slbyml.github.io.git master
cd -
Package.json中添加一个script:·"deploy": "bash deploy.sh"·,这样就可以通过yarn deploy
一键操作了,还是比较香的
后期规划
- [ ] 添加docsearch
- [ ] 自动上传CDN
友情提示
自行搜索插件的时候,请看清楚支持的是vuepress1.x
还是vuepress2.x
,现在很多插件还只是支持1.x,且有些声称支持2.x的插件在开发环境好使,打包的时候就报错,我也是踩了很多坑,以至于好几次想放弃,坚持到最后才勉勉强强的搭建完成,所以有什么问题欢迎在评论区点赞+探讨,让我们动起手来共建博客吧
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。