Lex's Blog

Vuepress 博客搭建

技术的沉淀免不了分享和记录,用本文记录了一下自己搭建博客的过程。

本文将一步步带你实现:

  • 使用 vuepress + gitPage 搭建并部署你的博客网站
  • 利用 nodejs fs api 根据项目目录自动配置博客侧边栏
  • 使用 nodejs child_process 一键部署你的博客网站

让赶快我们开始吧~

Warning

vuepress v1.5.0 存在热更新不生效的问题,请注意升级版本

本项目使用 yarn,如使用 npm 请自行替换

项目初始化

首先让我们新建一个文件夹作为项目的根目录, 然后进入此目录

 mkdir blog
 cd blog

接着来进行 git 和 npm 的初始化工作,并创建 .gitignore 文件,在 .gitignore 中忽略 node_modules 文件夹

 npm init -y
 git init
 echo node_modules >> .gitignore

我们使用 src 目录作为项目的入口目录,创建一下它

mkdir src

至此,项目目录已经搭建完毕,接下来开始配置一下 vuepress

安装配置 Vuepress

安装 vuepress 依赖

 yarn add vuepress

在 src 目录中创建 .vuepress目录,并在 .vuepress 中创建 config.js

cd src
mkdir .vuepress
touch config.js

config.js 中写入以下代码

module.exports = {
  // 项目的基础路径,可以理解为github的仓库名称
  base: "/blog/",
  // 打包的输出目录,这里使用docs是为了配合gitpage部署,下面会讲
  dest: "./docs",
  // 博客的标题
  title: "Lay",
  // 描述
  description: "lay的博客",
  // 主题配置
  themeConfig: {
    // 侧边栏
    sidebar: ["/"],
    // 开启滚动效果
    smoothScroll: true,
    // 仓库地址,用于编辑跳转使用
    repo: "https://github.com/lei4519/blog",
    // 开发目录
    docsDir: "src",
    // 开启页面编辑功能
    editLinks: true,
    editLinkText: "在 GitHub 上编辑此页",
    // 更新时间文字
    lastUpdated: "最后更新时间",
  },
}

src目录 中新建 README.md 文件,用作博客的首页,并增加如下内容

touch README.md
---
home: true
heroText: 博客
tagline: 前端技术分享
actionText: 开始阅读 →
---

配置 package.json script 字段

"scripts": {
  "dev": "vuepress dev src",
  "build": "vuepress build src"
}

运行 yarn dev,我们可以在浏览器中看到搭建好的博客首页

接着我们在 src 中新建 hello.md, 在里面随便写点内容,然后在 README.md 中新增 actionLink 属性,值为我们的路径(/blog/应保持和 config.js 中的 base 属性一直)

---
home: true
heroText: 博客
tagline: 前端技术分享
actionText: 开始阅读 →
actionLink: /blog/hello
---

接着我们点击首页的开始阅读按钮,就会跳转至 hello 页面了

ok,到此我们的项目配置已经完成,接下来让我们配合 gitpage 来部署我们的博客。

项目部署

首页执行 yarn build 来打包代码

然后在 github 创建一个新的仓库,上传我们刚才写的代码(这个步骤就不写了,不会的可以自行百度)

上传成功之后,进入刚才创建的仓库中,点击 Settings 进入仓库配置

进入之后直接向下滚动找到 GitHub Pages 选项,在 Source 选项中选择 master barnch /docs folder

这样就可以直接使用我们仓库的 docs 目录作为我们网站的根目录,并且我们每次需要部署线上网站时,只需要 yarn build 之后推送代码到 git 仓库,线上博客就会自动更新,完全不需要额外的操作,十分方便。

封装部署操作

刚才我们提到,每次更新部署,只需要 yarn build 之后推送代码到 git 仓库即可,虽然很简单,但是也要以下命令一顿操作才可以

yarn build
git add .
git commit -m '这次的更新信息'
git push

每次这样执行显然不符合程序员的 don't repeat 原则,我们很容易就会发现上面的命令中只有提交信息是每次变化的,那我们可不可以将不变的部分提取出来呢?

当然是可以的,既然是前端开发,那我们就是用原生的 nodejs 来简单实现一个命令行交互的功能

在项目根目录新建 release.js,并将 package.json script 字段调整如下

"scripts": {
  "dev": "vuepress dev src",
  "release": "node release.js"
}

接着我们来完善 release.js,代码如下

// 封装一个命令行执行函数
function sh(commitMsg) {
  const { spawn } = require("child_process")
  // 创建一个运行bash的进程
  const bash = spawn("bash")
  // 错误事件监听
  bash.on("error", function () {
    console.log("error")
    console.log(arguments)
  })
  // 输出信息监听
  bash.stdout.on("data", onData)
  bash.stderr.on("data", onData)
  function onData(data) {
    process.stdout.write(data)
  }
  // 运行结束事件
  bash.on("close", (code) => {
    console.log(`打包完成:${code}`)
    process.exit(code)
  })
  // 像bash中写入以下命令
  bash.stdin.write(`vuepress build src
  git add .
  git commit -m ${commitMsg}
  git push`)
  // 开始执行
  bash.stdin.end()
}
// 检测执行此文件时是否传入了提交信息
// process.argv: ['node', 'release.js', '提交信息']
if (!process.argv[2]) {
  // 如果没有传入提醒输入git信息
  process.stdin.on("data", (data) => {
    data = data.toString()
    if (!data.toString().trim()) {
      console.log("请输入git提交信息:")
    } else {
      sh(data)
    }
  })
  console.log("请输入git提交信息:")
} else {
  // 如果传入了直接执行命令行
  sh(process.argv[2])
}

接下来我们只需要运行 node release.js 提交信息 就可以直接将代码推送到 github 上了。

根据项目目录自动配置 Sidebar

以上的配置还有一些问题,如果你看了 vuepress 文档,就会发现 slider 的配置虽然有 auto 选项,但是实际效果并不如人意,而自己去配置又太过繁琐,那有没有什么办法可以不使用官方的 auto 属性又可以自动配置侧边栏呢?

当然是可以的,前端的朋友一定不会陌生 webpack 中的 require.context 函数,我们经常使用这个来自动生成路由配置等信息,在这里我们也可以用这个思路来根据项目目录来生成侧边栏配置。

这里我们使用 nodejs fs 模块来实现目录读取,并根据文件的更新时间,使用归并排序来对同一个目录中的文件进行排序

我们将这个工具方法建立在 /src/.vuepress/utils/index.js

具体实现如下

const path = require("path")
const fs = require("fs")
// 生成侧边栏配置信息
function genSliderBar() {
  // 项目根目录
  const basePath = path.resolve(__dirname, "..", "..")
  // 需要排除的目录和文件
  const excludes = ["README.md", "img"]
  // 目录读取函数
  // path是当前目录的父目录路径
  // arr 用来存放读取到的目录和文件
  // depth 用来记录目录的深度,也是用作slide配置中的sidebarDepth值
  const _readdir = (path, arr, depth) => {
    // 读取目录
    const dirsOrFiles = fs.readdirSync(basePath + path)
    // 遍历目录中的每一项
    dirsOrFiles.forEach((name) => {
      // 以.开头和excludes中的文件都不做处理
      if (name.indexOf(".") === 0) return
      if (excludes.includes(name)) return
      // 拿到当前项的信息
      const stat = fs.statSync(basePath + path + name)
      // 如果是目录
      if (stat.isDirectory()) {
        // 读取这个目录中的信息
        const rawChildren = _readdir(path + name + "/", [], depth + 1)
        // 对目录中的文件,根据更新时间进行排序
        const sortChildren = mergeSort(rawChildren, (a, b) => {
          // 没有更新时间代表当前项是目录,目录都放在文件的上方
          if (!b.mtime) return true
          return a.mtime < b.mtime
        })
        // 将当前目录的配置信息放入arr中
        // children的操作是提取文件的path信息,真正的sidebar配置中是不需要mtime属性的
        arr.push({
          title: name,
          sidebarDepth: depth,
          children: sortChildren.map((item) => (item.path ? item.path : item)),
        })
      } else {
        // 如果不是目录,则放入arr中
        arr.push({
          path: path + name,
          mtime: stat.mtime,
        })
      }
    })
    return arr
  }
  return _readdir("/", [], 1)
}
// 归并排序
function mergeSort(array, fn) {
  // 函数安全检测
  if (!fn || typeof fn !== "function") fn = (a, b) => a > b
  // 分
  const divide = (arr) => {
    const len = arr.length
    if (len < 2) return arr
    const mid = (len / 2) | 0
    return merge(divide(arr.slice(0, mid)), divide(arr.slice(mid)))
  }
  // 合
  const merge = (a1, a2) => {
    const a = []
    while (a1.length && a2.length) {
      a.push(fn(a1[0], a2[0]) ? a2.shift() : a1.shift())
    }
    return a.concat(a1, a2)
  }
  return divide(array)
}

module.exports = {
  genSliderBar,
}

接着在我们的 config.js 中配置即可

themeConfig: {
  sidebar: utils.genSliderBar()
}

配置导航栏

接下来我们新增一下导航栏,将博客进行分类

添加评论功能:GitTalk

结束语

好的,到此我们的博客搭建就算告一段落了。

如果遇到问题,可以去 github 中问我。

如果觉得我写的还不错,欢迎去 github 中给个 star ~谢谢观看 🙏

觉得有帮助?给个 Star 支持一下吧!

Star on GitHub

On this page