基于vuepress-theme-hope的官网开发小记

Posted by 施艳春 on Saturday, October 28, 2023

利用本科最后一个学生身份参加了开源之夏,有幸参与到 dromara 官网的开发工作。由于比较熟悉 Vue,选择了 Vuepress 开发,主题为 vuepress-theme-hope。 并使用 VuePress Theme Hope 主题的 V2 版本构建。V2 基于 VuePress2, 带有 Vite4 / Webpack5Vue3 的强大功能。以下为开发过程中的一些经验记录。

1.在 VuePress 项目中使用 Vue 组件与 Vue 语法。

由于组织对官网的美观有一定要求,官网开发之前就进行了很长时间的设计,然后发现 ai 设计功能还是不太行 hhh,最后去闲鱼找了一位设计师,效果就很好了。 拿到设计图就会发现 vuepress-theme-hope 是由 frontmatter 关键词渲染生成的样式,设计图完全无法由内置的模块生成,就思考怎么使用 Vue 语法自己写。查阅文档后,项目中主要使用了两种方式:

  1. 替换主题组件 主要参考hope 文档的替换主题组件 当在 hopeTheme() 的第二个参数中设置 { custom: true } 时,主题将通过 @theme-hope 别名来引入组件,然后在.vuepress/config.ts中通过 alias 替换主题中使用的组件别名,这样就可以替换主题的任何一个组件。 比如将主页组件改为自己组件 .vuepress/components 下的 HomePage.vue 首先设置 { custom: true },再在 alias 中映射就可以。

    // .vuepress/config.ts
    import { getDirname, path } from "@vuepress/utils";
    import { defineUserConfig } from "vuepress";
    import { hopeTheme } from "vuepress-theme-hope";
    
    const __dirname = getDirname(import.meta.url);
    
    export default defineUserConfig({
      theme: hopeTheme(
        {
          // 主题选项
          // ...
        },
        { custom: true }
      ),
    
      alias: {
        // 你可以在这里将别名定向到自己的组件
        // 比如这里我们将主题的主页组件改为用户 .vuepress/components 下的 HomePage.vue
        "@theme-hope/components/HomePage": path.resolve(
          __dirname,
          "./components/HomePage.vue"
        ),
      },
    });
    

    项目中替换了@theme-hope/components/HomePage主页与@theme-hope/components/PageFooter页脚

  2. 在 Markdown 中使用 Vue 语法与组件 参考官网在 Markdown 中使用 Vue 语法与组件

  • Markdown 中允许使用 HTML。
  • Vue 模板语法是和 HTML 兼容的。

因此在 Markdown 中允许直接使用 Vue 模板语法。

具体使用时,由于 Markdown 会被转换为缓存目录下的 Vue 单文件组件,任何相对路径的导入会在 Vue SFC 中失效。因此导入自己的组件,需要为它们创建别名,即通过 alias 选项

// .vuepress/config.ts

import { getDirname, path } from "@vuepress/utils";

const __dirname = getDirname(import.meta.url);

export default {
   alias: {
      "@ProjectsPage": path.resolve(__dirname, "components/ProjectsPage/ProjectsPage.vue"),
   };
};

访问到 src/projects/README.md 页面时,即可拿到 ProjectsPage.vue,并进行渲染。 projects 的 README 文档结构如下所示: 顶部是 md 的 frontmatter,把框架配置的结构为 false,只留空白页面。并在 script 中引入 ProjectsPage.vue。接着使用 ProjectsPage 这个组件。还可以在 style 标签中修改样式。

// src/projects/README.md
---
title: Projects
index: false
sidebar: false
breadcrumb: false
pageInfo: false
contributors: false
editLink: false
lastUpdated: false
prev: false
next: false
---

<script setup lang="ts">
import ProjectsPage from "@ProjectsPage";
</script>

<ProjectsPage />

<style scoped lang="scss">
.theme-hope-content {
  margin: 0;
  padding: 0;
  max-width: none;
  position: relative;
  z-index: 1;
  top: -161px;
  @media (min-width: 1440px) {
    background: #f9fbff;
  }
}
</style>

2. 多语言方案

由于上文把内置模块替换成 Vue 组件,会影响到多语言的实现。切换语言时,只有 md 的标题会变化,而 md 中使用的 Vue 组件需要找到方式联动。 一开始想用主题提供的 api 引入变量,将来改东西直接在 md 文档改,内部自动映射过来。结果看了提供的 api,发现首页还是只能拿到用不了的那些模块。 找到 jinchaolove 的 demo,发现可以拿到框架提供的多个 hooks,其中 useSiteLocaleData()lang 属性即为当前的语言。 所以考虑自己实现多语言,就是把文字常量放在 en.ts 和 zh.ts,引入到 vue 文件中,里面 watch 语言类型的变化,切换就把 homeOption 换掉。

// src\.vuepress\components\ProjectsPage\ProjectsPage.vue
<div class="project-container">
  <h1 class="title">{{ projectsOption.PROJECTS }}</h1>
  <p class="description">
    {{ projectsOption.DESCRIPTION }}
  </p>
</div>
import { useSiteLocaleData } from "@vuepress/client";
const siteLocaleData = useSiteLocaleData();

watch(
  () => siteLocaleData.value.lang,
  (newLang) => {
    lang.value = newLang;
    if (lang.value === "zh-CN" || lang.value === "/zh/") {
      projectsOption = zhProjectsOption;
    } else {
      projectsOption = enProjectsOption;
    }
  },
  {
    immediate: true
  }
);

询问阿超前辈后,认为可以这么实现,维护还好,大概后续不会特别关注底层,于是使用了该方案,后续转换也很顺畅。

3. 通过插件拿到所有 frontmatter 信息

有个场景,想当用户点击新闻标签时,显示所有新闻的博客信息,包括标题、链接、作者、日期等,也就是自动拿到 src\zh\news 这个文件夹下所有 md 文档的 frontmatter,然后内部进行处理,最终显示在新闻标签页。自己翻框架文档没找到可实现的 api,在思考使用 Node.js 自己写工具,还是换其他方式时,决定去请教阿超前辈。 阿超前辈通过 demo 展示了正确地使用框架内部插件的方式。我在项目里按照正确方式,运行很好。 前辈告诉我需要理解框架中配插件的方式与插件生命周期,然后可以打印出来看到底提供了哪些属性。 实现方式主要利用 vuepress2 提供的插件 API extendsPage 其类型为: (page: Page, app: App) => void | Promise 可以在 page 和 app 中拿到 frontmatter,并放在 app.siteData.frontmatter 中。

module.exports = {
  name: 'get-all-frontmatter',
  extendsPage: (page, app) => {
    app.siteData.frontmatter = app.siteData.frontmatter ?? []
    app.siteData.frontmatter.push(page.data.frontmatter)
  },
};

然后在新闻的 vue 文件中

import { siteData } from "@vuepress/client";
const allPagesFrontmatter = siteData.value.frontmatter;

就能获得 siteData.value.frontmatter,并做想要的转换。 这样,只要在所有新闻的 md 中,增加相应的 frontmatter,就可以在新闻页全部拿到对应信息。

4. hope 主题中使用 webpack

曾经想用 webpack 的 gzip 压缩下代码、使用 cdn,但发现我的网站慢主要是因为使用 gh-pages 部署的,还是应该在这上面下功夫,但翻到的前人在 hope 主题中使用 webpack 的方式还是很有用,可以用 webpack 的各种插件、loader,所以一并贴出来。 https://newzone.top/web/VuePress.html#%E6%97%B6%E9%97%B4%E5%8F%82%E6%95%B0

完整代码可参考仓库,欢迎大家指教。

「真诚赞赏,手留余香」

Cici Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付