ETJava Beta | Java    注册   登录
  • 搜索:
  • 适用于 VitePress 的公告插件开发实记

    发表于      阅读(2)     博客类别:Crawler     转自:https://www.cnblogs.com/roseAT/p/18424345
    如有侵权 请联系我们删除  (页面底部联系我们)  

    前言

    笔者维护的 VitePress 博客主题在近1年多的时间里集成了非常多功能,不少用户希望将里面的部分功能分离出来,方便在其它 VitePress 站点也可以独立使用。

    其中分离的第一个组件类型的就是呼声较高的 公告插件

    最终效果如下:

    接下来将介绍一下用法,讲解一下实现原理,提供一个插件模板供大家快速开发同类型插件。

    如何使用

    只需要 2 步:

    1. 安装插件

    pnpm/npm/yarn 均可,笔者比较偏好 pnpm

    pnpm add vitepress-plugin-announcement
    
    1. 配置插件

    引入插件在 .vitepress/config.ts VitePress 配置文件中

    import { defineConfig } from 'vitepress'
    import { AnnouncementPlugin } from 'vitepress-plugin-announcement'
    
    export default defineConfig({
      vite: {
        // ↓↓↓↓↓
        plugins: [
          AnnouncementPlugin({
            title: '标题',
            body: [
              { type: 'text', content: '文本内容' },
              {
                type: 'image',
                src: '图片'
              }
            ],
            footer: [
              {
                type: 'button',
                content: '按钮',
                link: 'https://sugarat.top'
              },
            ],
          })
        ]
        // ↑↑↑↑↑
      }
    })
    

    目前支持 文本/图片/按钮 三种类型。

    实现原理

    这里只阐述关键点,详细部分将计划单独拆一篇文章进行讲解(完整的搭配插件模板 0 - 1 实现)

    注入自定义组件

    VitePress 官方文档自定义板块 中可以了解到,

    VitePress 入口的组件是在 .vitepress/theme/index 中导出的 Layout.vue 组件。

    同时 VitePress 默认主题的 Layout 已经提供了许多可以直接使用的插槽,于是乎,咱们可以把组件直接放入这些位置即可。

    VitePress 是由 Vite 驱动,所以按照 Vite 插件的思路去拓展即可。

    这里利用插件的 transform 钩子 去处理。

    const pluginOps = {
      name: 'vitepress-plugin-announcement',
      transform(code, id) {
        // 具体处理逻辑
      }
    }
    

    将我们的组件插入到默认主题的 Layout.vue 里即可。

    期望插入后的代码如下

    <script setup lang="ts">
    import Announcement from './Announcement.vue' // [!code ++]
    </script>
    
    <template>
      <div>
        <slot name="layout-top" />
        <Announcement /> // [!code ++]
      </div>
    </template>
    

    下面是具体的处理逻辑,简单字符串替换插入我们需要的内容即可

    const pluginOps = {
      name: 'vitepress-plugin-announcement',
      enforce: 'pre',
      transform(code, id) {
        // 筛选出 Layout 文件
        if (id.endsWith('vitepress/dist/client/theme-default/Layout.vue')) {
          // 插入自定义组件调用代码
          const slotPosition = '<slot name="layout-top" />'
          let transformResult = code.replace(slotPosition, `${slotPosition}<Announcement/>`)
    
          // 导入自定义组件导入代码
          const setupPosition = '<script setup lang="ts">'
          transformResult = transformResult.replace(setupPosition, `${setupPosition}\nimport Announcement from './Announcement.vue'`)
          return transformResult
        }
      }
    }
    

    同时通过 enforce 参数控制插件顺序,这里需要尽可能的提前执行,处理源代码。

    接下来就是处理 import 的导入,如果不做处理,Layout.vue 里是找不到这些组件,就一定会报错。

    import Announcement from './Announcement.vue' // [!code ++]
    

    于是这里,再利用 config 钩子,传入 resolve.alias 配置 即可。

    // 构造插件里实际组件的路径
    const aliasComponentFile = `${getDirname()}/components/Announcement.vue`
    
    const pluginOps = {
      config() {
        return {
          resolve: {
            alias: {
              './Announcement.vue': aliasComponentFile
            }
          }
        }
      }
    }
    

    至此自定义组件的注入就算是完成了,接下来介绍如何将外部的参数传入到组件里。

    插件配置传递

    方式有很多种,笔者这里采用改动最小,使用较为广泛的一种 虚拟模块

    官方示例里已经介绍了如何使用,这里不做赘述。

    组件中导入配置代码

    <script lang="ts" setup>
    import announcementOptions from 'virtual:announcement-options'
    </script>
    

    插件中处理虚拟模块逻辑。

    import { stringify } from 'javascript-stringify'
    
    function AnnouncementPlugin(options) {
      const virtualModuleId = 'virtual:announcement-options'
      const resolvedVirtualModuleId = `\0${virtualModuleId}`
    
      const pluginOps = {
        name: 'vitepress-plugin-announcement',
        enforce: 'pre',
        resolveId(id) {
          if (id === virtualModuleId) {
            return resolvedVirtualModuleId
          }
        },
        load(this, id) {
          if (id === resolvedVirtualModuleId) {
            // 虚拟模块处理
            return `export default ${stringify(options)}`
          }
        },
      }
      return pluginOps
    }
    

    tips:由于 JSON.stringify 默认不会处理函数,这里就偷懒用一下社区的库 javascript-stringify 进行处理。

    如果要用 JSON.stringify 处理,需要传入第二个 replacer 参数,对函数做特殊处理。

    至此,关键的两步就算搞定了,剩余的工作就是具体组件的样式和交互实现开发了,按常规 Vue 组件开发即可。

    插件模板介绍

    在开发插件的过程中,笔者把此类基于 slot 位置注入的插件分离了一个模板 vitepress-plugin-slot-inject-template

    目录结构

    ├── scripts # 构建相关脚本,无特殊需求可以不用修改
    |  ├── copyComponents.mjs
    |  └── watchAndCopy.mjs
    ├── src
    |  ├── components # 组件实现
    |  ├── index.ts # 插件入口
    |  ├── type.ts # 插件配置参数类型定义
    |  └── util.ts # 插件使用的工具函数
    ├── README.md
    ├── package.json
    └── tsconfig.json
    

    参考了 VitePress 默认主题中部分实现。

    使用步骤

    1. 插件入口 src/index.ts,修改一下插件&组件命名信息和组件插入位置。
    2. 按照实际需求编写组件实现。
    3. 完善 README.md 使用文档后发布 npm 包即可。

    最后

    样式实现参考了 reco-1.x 主题 中的 @vuepress-reco/vuepress-plugin-bulletin-popover 插件

    后续继续按照 vitepress 插件开发计划 推进,将开发过程内容整理成教程与插件模板。

    插件完整源码 vitepress-plugin-announcement