用 Markdown 写文档是很方便的,Typora 可以实时预览是一个好用的编辑器,配合 iPic 可以将粘贴进来的图片自动上传再替换成链接,VuePress 可以将文档打包生成静态网站,为了在写完文档后更加方便的发布,除了普通的配置外还需要做一个自动生成文档列表的功能,写完文档后不用做其他配置修改用一条命令即可发布。
自动生成侧边栏
配置侧边栏类型有多种,我的需求是侧栏显示某一个分类下的所有文章列表,通过头部的 nav 切换分类。默认情况下侧边栏会自动地显示由当前页面的标题(headers)组成的链接,并按照页面本身的结构进行嵌套,通过设置 themeConfig.sidebarDepth = 0 禁止显示嵌套,只显示文章标题(自动提取h1标题)。示例效果:

sidebar 设置成多个侧边栏,配置格式如下,children 的值每一项都是字符串链接,侧栏标题都会从文章内取,如果需要显示地指定链接的文字,使用一个格式为 [link, text] 的数组。
sidebar: {
'/git/': [
{
title: "Git",
collapsable: true,
sidebarDepth: 0,
children: [
"/git/",
"/git/one",
"/git/two",
["/git/rebase", "变基"]
]
}
]
}
自动生成侧栏是通过读取文件夹,生成一个对象作为 sidebar 的 value 值,属性值都是一个文件夹路径,children 是文件夹下的所有文档的链接。假设文件目录结构如下:
├─ README.md
├─ contact.md
├─ about.md
├─ git/
│ ├─ README.md
│ ├─ one.md
│ └─ two.md
└─ vue/
├─ README.md
├─ three.md
└─ four.md
生成我需要的 sidebar 数据格式。
// 文档路径
const docsPath = process.cwd() + '/docs'
const fs = require('fs')
// 获取文件名
function getFileName(root, dir) {
let path = root + dir
let fileNames = []
let readmeContent = ''
fs.readdirSync(path).forEach(file => {
// 只处理 .md 文件
if (/.md$/.test(file)) {
let name = file.replace(/(README)?.md$/, '')
fileNames.push(dir + name)
}
})
// 做排序,可以将文档命名成:1.xx.md, 2.xx.md
fileNames.sort()
return fileNames
}
// 文件夹路径
let dirs = [
{
path: '/git/',
title: 'Git'
},
{
path: '/vue/',
title: 'Vue'
},
] // 读取 /docs 下的文件夹生成 dirs 也是可以的,不过我需要设置 title,使用手动配置
module.exports = dirs.reduce((obj, item) => {
obj[item.path] = [
{
title: item.title,
collapsable: true,
sidebarDepth: 0,
children: getFileName(docsPath, item.path)
}
]
return obj;
}, {})
自动生成 README.md
每一个文件夹下都需要一个 README.md 文件,当访问 xxx/git/ 时就是将 /git/README.md 这个文件作为首页打开,如果文件不存在则显示 404,所以我的做法是生成一个 README.md 文件,作为目录页,显示当前分类下的所有文章链接,VuePress文档-主题首页内容是主题功能的介绍,也有一个本栏的主要内容列表,虽然和侧边栏的内容重复,但手机端页面默认是不显示侧边栏的,就可以直接点击目录进入文章了。
在获取文件名时读取文件,从文件内容中截取标题,拼接成一个完整的 README.md 文件。
let readmeContent = ''
fs.readdirSync(path).forEach(file => {
// 只处理 .md 文件
if (/.md$/.test(file)) {
if (!/README.md/.test(file)) {
// 取文档中的 title
let title = fs.readFileSync(path + file, 'utf-8').match(/(?<=#\s+)[^\s]+(?=\s)/m)
readmeContent += `+ [${title ? title[0] : name}](${file})\n`
}
}
})
// 关闭`在GitHub上编辑此页`功能
readmeContent = '---\neditLink: false\n---\n# 目录\n' + readmeContent
fs.writeFileSync(root + dir + 'README.md', readmeContent)
生成的 README.md 文件内容:
---
editLink: false
---
# 目录
+ [title1](link1)
+ [title2](link2)
完整的代码:
// /docs/.vuepress/sidebarConf.js
// 文档路径
const docsPath = process.cwd() + '/docs'
const fs = require('fs')
// 获取文件名
function getFileName(root, dir) {
let path = root + dir
let fileNames = []
let readmeContent = ''
fs.readdirSync(path).forEach(file => {
// 只处理 .md 文件
if (/.md$/.test(file)) {
let name = file.replace(/(README)?.md$/, '')
fileNames.push(dir + name)
if (!/README.md/.test(file)) {
// 取文档中的 title
let title = fs.readFileSync(path + file, 'utf-8').match(/(?<=#\s+)[^\s]+(?=\s)/m)
readmeContent += `+ [${title ? title[0] : name}](${file})\n`
}
}
})
// 关闭`在GitHub上编辑此页`功能
readmeContent = '---\neditLink: false\n---\n# 目录\n' + readmeContent
fs.writeFileSync(root + dir + 'README.md', readmeContent)
// 做排序,可以将文档命名成:1.xx.md, 2.xx.md
fileNames.sort()
return fileNames
}
let dirs = [
{
path: '/git/',
title: 'Git'
},
{
path: '/vue/',
title: 'Vue'
},
]
module.exports = dirs.reduce((obj, item) => {
obj[item.path] = [
{
title: item.title,
collapsable: true,
sidebarDepth: 0,
children: getFileName(docsPath, item.path)
}
]
return obj;
}, {})
新浪图床403问题
推送到 github pages,打开页面出现图片403的情况,搜索后找到解决办法,在<head> 中增加 <meta name="referrer" content="no-referrer" /> 后图片正常。新浪图床外链图片不显示(403)解决办法。
应该是新浪开始限制外链图片的调用了,所以被禁止访问了。在页面引入图片、JS 等资源,都会产生新的 HTTP 请求,浏览器一般都会给这些请求头加上表示来源的 Referrer 字段,禁用referrer后则可避免。
在配置文件 config.js 中增加:
modules.exports = {
head: [
['meta', { name: 'referrer', content: 'no-referrer' }],
]
}
评论功能
评论的所有功能用一个 <comment/> 组件实现,不管用第三方评论工具还是自己写接口,逻辑都放在这个组件内,再通过插件的方式在每个页面中使用这个组件即可。
先新建插件文件 comment-plugin.js,插件需要做的就是返回一个有 globalUIComponents 属性的对象,给每一个页面注入一个全局 UI 组件。在 .md 文件中直接使用 <comment/> 组件也是可以实现的,但避免直接修改原文件,还是使用插件的方式。
// /docs/.vuepress/comment-plugin.js
module.exports = {
return {
name: 'comment-plugin',
globalUIComponents: [
'comment',
]
}
}
新建一个评论组件 /docs/.vuepress/components/comment.vue。打包的时候会自动注册成全局组件。
<template>
<div class="comment-wrap" v-if="show">
<div id="commentEl" class="comment"></div>
</div>
</template>
<script>
// import Valine from 'valine'
export default {
name: 'Comment',
data() {
return {
show: false
}
},
mounted() {
import('valine').then(module => {
window.Valine = module.default;
this.fetchComment();
})
},
methods: {
fetchComment() {
// 目录页面不显示评论
this.show = /.html$/.test(this.$route.path);
this.show && this.$nextTick(() => {
new Valine({
el: '#commentEl',
appId: '<appId>',
appKey: '<appKey>',
path: this.$page.key
})
})
}
},
watch: {
'$route': function(route) {
this.fetchComment();
}
}
}
</script>
<style scoped>
.comment-wrap {
padding-left: 20rem;
}
.comment {
max-width: 740px;
margin: 0 auto;
padding: 0 2.5rem 4rem;
}
@media (max-width: 959px) {
.comment-wrap {
padding-left: 16.4rem;
}
}
@media (max-width: 719px) {
.comment-wrap {
padding-left: 0;
}
}
@media (max-width: 959px) {
.comment {
padding: 0 2rem 2rem;
}
}
@media (max-width: 419px) {
.comment {
padding: 0 1.5rem 1.5rem;
}
}
</style>
评论功能使用的是 valine。其中 path 表示当前文章页路径,用于区分不同的文章页,以保证正确读取该文章页下的评论列表, $page.key 是页面唯一的 hash key,更改文章内容 key 值不变,就可以实现文章和评论一一对应。
使用插件:
// /docs/.vuepress/config.js
module.exports = {
plugins: [
require('./vuepress-plugin-comment.js'),
],
}
使用自定义域名
按照VuePress文档-部署,如果发布到自定义域名,将 deploy.sh 文件第13行注释去掉即可。
#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e
# 生成静态文件
npm run docs:build
# 进入生成的文件夹
cd docs/.vuepress/dist
# 如果是发布到自定义域名
echo 'www.example.com' > CNAME
git init
git add -A
git commit -m 'deploy'
# 如果发布到 https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pages
cd -
在域名服务商做一个解析配置,点击修改记录,类型选择 CNAME,记录值填 <USERNAME>.github.io。