博客搭建 | 五 使用 vitepress 搭建自己的网站

1,379 阅读9分钟

简述

最初,我的博客使用的vuepress搭建的,使用的是vuepress-theme-reco,功能齐全,布局简洁美观,也花了一些心思在搭建上面,后来reco主题进入2.x的开发中,1.x版本进入稳定版本,作为博客,内容才是关键,vuepress和它的主题都是载体,而在今年开始(也好像是去年),博客在actions中一直报错,大概是旧版本依赖被弃用了,导致博客无法正常更新至线上,所以便考虑升级博客。

reco2.x仍然采用vuepress作为驱动,而尤雨溪推出了vitepress之后,我决定拥抱新技术,在动手中了解新框架。虽然官方说vuepress和vitepress定位不同,没有替代一说,但vite带来的开发体验确实更好,随着文章的增多,vuepress的启动速度与打包速度都不及vitepress。

在这里提示大家,vitepress作为静态文档生成框架,在工作中接触到的无非是生产相关文档,不需要深入了解,所以也提示大家,如果非兴趣所致,不需要在这上面花太长时间。

本篇文章主要是在实践博客搭建中介绍vitepress的一些特性,目标是对reco主题的部分功能进行复刻。

初始化vitepress项目

pnpm add -D vitepress
pnpm vitepress init

安装和初始化后,我们会得到一个基本的项目框架,运行后会看到一个类似vitepress官方文档的页面布局。

项目目录

为了方便实现博客的一些功能,我们将自己的文章内容分为两类,一类是内容页,一类是功能页(文章列表,时间线等),所以在目录中新建 blogspage两个文件夹,那么整体的目录结构为:

.
├─ .vitepress
│  ├─ theme // 主题相关
│  └─ config.mts // 项目配置
├─ blogs // 文章文件夹  // [!code ++]
├─ page // 功能页 // [!code ++]
├─ api-examples.md // 示例md
├─ markdown-examples.md // 示例md
└─ index.md // 示例md
└─ package.json

进行基础配置

.vitepress>config.mts, 这是整个博客的配置文件,要显示什么,怎么显示都需要再其中设置,现在要对其进行基础设置

1. 汉化

// config.mts
export default {
  // 应用级配置选项
  lang: "zh", //中文配置
  markdown: { // vitepress 对md进行了扩展,有些写法有特殊的样式
    container: {
      tipLabel: '提示',
      warningLabel: '警告',
      dangerLabel: '危险',
      infoLabel: '简述',
      detailsLabel: '详细信息'
    }
  },
  themeConfig: { // 主题配置
    lastUpdated: { // 更新时间的显示
      text: '更新于',
      formatOptions: {
        dateStyle: 'medium',
        timeStyle: 'medium'
      }
    },
    search: { // 搜索
      provider: "local",
      options: {
        translations: {
          button: {
            buttonText: "搜索文档",
            buttonAriaLabel: "搜索文档",
          },
          modal: {
            noResultsText: "无法找到相关结果",
            resetButtonTitle: "清除查询条件",
            footer: {
              selectText: "选择",
              navigateText: "切换",
            },
          },
        },
      },
    },
    notFound:{ // 404页
      title: '未找到页面,迷路了~',
      quote: '请检查地址是否正确,或当前页面未开通,点击下方按钮返回首页',
      linkText: '返回首页'
    },
  }
}

汉化内容可以完全按照自己的想法去设置。

2. 导航

在vitepress中,如果项目不设置根目录和源目录,使用默认设置,那么与.vitepress同级的目录为/

// config.mts
export default {
  themeConfig: { // 主题配置
    nav: [
      { text: "主页", link: "/" },
      { text: "分类", link: "/category.md" },
      { text: "标签", link: "/tag.md" },
      { text: "时间线", link: "/timeline.md" },
      { text: "留言板", link: "/messageBoard.md" },
    ],
  }
}

自定义页面

一般在博客首页都会展示文章列表,为了展示相关内容,我们需要自定义一些页面

vitepress在编译时将md文档转成html,也允许我们在像正常的vue项目中使用组件。

这里以博客主页为例

1. 依赖安装

首先我们需要安装vue,其实我们不安装也可以直接使用,只不过编辑器会不断的报错,特别是在ts环境下,很多vue语法会报错,所以需要安装vue,如果在我们自定义中想要使用其他框架也是可以的,这个就见仁见智了。

2. 创建相关文件

.
├─ .vitepress
│  ├─ theme
│  |  ├─ components
|  |  |  ├─ Blog.vue  // [!code ++]
|  |  |  ├─ More... // [!code ++]
|  |  ├─ index.ts // 主题入口
|  |  ├─ posts.data.ts // [!code ++]

Blog.vue 是博客主页的vue文件,可以任意添加一些内容,后面再讲如何获取博客列表。

3. 引入组件

// .vitepress>theme>index.ts
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import Blog from "./components/Blog.vue"; // [!code ++]
import './style.css'

export default {
  extends: DefaultTheme,
  Layout: () => {
    return h(DefaultTheme.Layout, null, {
      // https://vitepress.dev/guide/extending-default-theme#layout-slots
    })
  },
  enhanceApp({ app, router, siteData }) {
    // ...
    app.component("blog", Blog); // [!code ++]
  }
} satisfies Theme

引入代码没什么需要说明的,回头看一下默认初始化出来的index.md中的内容,头部的配置了一些属性,vitepress会读取它们并执行相关操作,比如 layout:home,该属性表明该页面是使用home组件渲染页面,因此,当我们将layout:home替换成layout:blog,博客首页的渲染内容便成了你的组件内容。

官方提供了三种页面布局:doc | home | page ,你可以在 这里 查看他们的详情。

4. 文章列表组件

获取文章列表的方式vitepress也提供了,在第二步创建的posts.data.ts,其内容可直接复制官方代码,这里是说明,大致了解一下就可以。

需要注意的有以下两点:

// posts.data.ts
export default createContentLoader('posts/*.md', { // [!code --]
export default createContentLoader('blogs/**/*.md', { // [!code ++]
  excerpt: true,
  transform(raw): Post[] {
    return raw
      .map(({ url, frontmatter, excerpt }) => ({
        title: frontmatter.title,
        url,
        excerpt,
        date: formatDate(frontmatter.date)
      }))
      .sort((a, b) => b.date.time - a.date.time)
  }
})

在初始化时创建了两个目录,区分了文章和功能页,也是为了这里在获取文章列表时减少不必要的操作

考虑到后期要增加的标签、分类、时间线等功能,所以需魔改一下createContentLoader,这里贴一下代码仅供参考。 ::: details

export default createContentLoader("blogs/**/*.md", {
  transform(raw): BlogData {
    const blogData: BlogData = {
      posts: [],
      tags: [],
      categories: [],
      everyCategoryCount: {},
    };
    raw.forEach(({ url, frontmatter, excerpt }) => {
      if (frontmatter.publish == undefined || frontmatter.publish) {
        if (frontmatter.tags) blogData.tags.push(...frontmatter.tags);
        if (frontmatter.categories) {
          blogData.categories.push(...frontmatter.categories);
          frontmatter.categories.forEach((category: string) => {
            if (blogData.everyCategoryCount[category]) {
              blogData.everyCategoryCount[category]++;
            } else {
              blogData.everyCategoryCount[category] = 1;
            }
          });
        }
        blogData.posts.push({
          title: frontmatter.title,
          url,
          excerpt,
          date: formatDate(frontmatter.date),
          frontmatter,
        });
      }
    });
    blogData.tags = removeDuplicates(blogData.tags) as string[];
    blogData.categories = removeDuplicates(blogData.categories) as string[];
    blogData.posts.sort((a, b) => b.date.time - a.date.time);
    return blogData;
  },
});

::: createContentLoader 将处理并返回文章数组,但使用时,并不需要调用该方法,直接使用导出的data

import { data } from '../posts.data'
// 文章数组
console.log(data.posts)
// 省略

基于此流程,便可以创建更多的自定义页面

5. 重写路由

当把所有的功能页(包括主页index.md)放进page文件夹后,我们在导航配置的路由需要加上前缀/page/,如果点击左上交标题,默认回到路由/,以及在默认打开博客时,都是空白页,vitepress提供了路由的重写,这里我们需要重写page的路由

// config.mts
export default {
  rewrites: {
    'page/(.*)': '(.*)',
  },
}

需要注意的是,路由重写后,我们在nav配置的地址应是重写后的

主题

vitepress定义了一些css变量,并且默认分为深色和浅色两个主题,如果想要自定义主题样式,可以在styles.css中覆盖这些样式,官方css变量,

运行时API

VitePress 提供了几个内置的 API 来让你访问应用程序数据。VitePress 还附带了一些可以在全局范围内使用的内置组件。

useData

可访问到页面相关数据和config.mts中自定义的属性,比如:

export default {
  themeConfig:{
    docCount:10, // [!code ++]
  }
}

上述代码在主题配置中新增定义docCount,这个属性是我用来配置文章列表分页的,然后这样使用:

import { useData } from 'vitepress'
const { theme } = useData()
const docCount: number = theme.value.docCount; // 每页展示文章数量

useRouter

路由示例,进行路由跳转

import { useRouter } from 'vitepress'
const { go } = useRouter()
<div @click="go('/somePageRoute')"></div>

<Content />

<Content /> 组件显示渲染的 md 内容。

<!-- blog.vue -->
<template>
  <h1>Custom Layout!</h1>
  <content />
</template>

添加评论

目前我所接触到的评论有valine和giscus,旧的博客用的便是valine,这次尝试一下giscus

这里讲略掉如何配置giscus,只将在vitepress中如何应用。

pnpm i @giscus/vue

新建一个Comment.vue,内容如下

<script setup lang="ts">
import Giscus from '@giscus/vue'
import { useData } from 'vitepress';
const { theme, frontmatter,isDark } = useData();
const show = frontmatter.value.comment === undefined || frontmatter.value.comment;

</script>

<template>
  <giscus v-if="theme.comment && show" id="comments"
  :repo="theme.comment.repo"
  :repo-id="theme.comment.repoId"
  category="Announcements"
  :category-id="theme.comment.categoryId"
  mapping="title" strict="0"
  :reactions-enabled="theme.comment.reactionsEnabled" emit-metadata="0" input-position="top"
  :theme="isDark?'dark':'light'" lang="zh-CN"
  crossorigin="anonymous" />
</template>

<style scoped>
</style>

这里我将giscus的参数放到了config.mts中,通过使用useData获取,你可以将获取的giscus参数直接填写进去。 评论系统一般在文章下面,默认的vitepress主题提供了很多插槽,我们只需要将评论插入既可。


// https://vitepress.dev/guide/custom-theme
import { h, defineAsyncComponent, defineComponent } from "vue";
import type { Theme, DefaultTheme } from "vitepress";
import theme from "vitepress/theme";
import Comment from "./components/Comment.vue"; // [!code ++]
import "./style.css";
export default {
  extends: theme,
  Layout: () => {
    return h(theme.Layout, null, {
      // https://vitepress.dev/guide/extending-default-theme#layout-slots
      "doc-after": () => h(Comment), // [!code ++]
    });
  },
  enhanceApp({ app, router, siteData }) {
    app.component("blog", Blog);
  },
} satisfies Theme;

站点统计

站点统计可以了解博客的被访问情况,这对自己的发展还是有帮助的,在了解一系列站点统计的工具,最终选择了开源的umami,他可以部署到自己的服务器上,保证数据隐私,工具功能足够自己去使用了,不过安装它让我折腾了许久,这里建议如果有问题,去仓库issues翻一翻,会少走很多弯路,其实部署过程很简单。本文主要讲vitepress相关,umami仅提醒几个需要注意的点。

  1. umami支持node环境安装或docker一键安装,并且依赖mysql(8.0以上)或pgsql(12.14以上),二选一,数据库需提前安装。
  2. 一定要注意环境变量DATABASE_URL的配置。
  3. 部署时报错多去仓库issues翻一翻。
  4. 在vercel部署也是一个不错的选择。
  5. 建议部署到https网址上,我的博客除了自己的服务器,也会在github page 中运行,而github是https,如果我们部署在http上,会导致接口访问错误。 部署好后,会在平台上获取一个跟踪代码:
<script defer src="xxxxxx/script.js" data-website-id="xxxxxx"></script>

接下来是将这个代码在vitepress上使用:

// config.mts
export default {
    head: [
    [
      "script",
      {
        defer: "",
        src:   "xxxxxx/script.js",
        "data-website-id": "xxxxxx",
      },
    ],
  ],
}

umami有个优势就在于我们这样配置以后,即便只是路由跳转,它依然能统计到,在之前调研过百度统计在vitepress的使用,还需要在router.onBeforeRouteChange中处理(掘金有个小伙伴是这么做的),umami还可以通过接口的方式获取统计数据,以进行其他功能的实现(因为数据完全私有化,也可以直接访问数据库进行操作)

总结

通过上述流程,基本将vitepress可能用到的功能使用了一遍,当然还有很多没有讲(目前我还没用到..),感兴趣的可以用它创建自己的主题博客或知识笔记,目前我的博客正在搭建中,也欢迎访问~ 个人博客