likes
comments
collection
share

如何快速为 VitePress 添加 RSS 订阅支持

作者站长头像
站长
· 阅读数 34

省流:使用 vitepress-plugin-rss 这个插件

前言

在看许多个人博客站点的时候,右上角总会有个RSS订阅的标志

如何快速为 VitePress 添加 RSS 订阅支持

恰好我的博客也是基于 VitePress 搭建的,就想看看能不能也实现这个功能呢?

动手前先搜了一下,先是看到了vitepress-blog-zaun上有这个RSS的实现支持,再搜了一下发现Vue的官方博客 vuejs/blog 也是用的这样的实现

大概就是自定义 VitePress 的 buildEnd 钩子,在里面实现逻辑获取 md 文件列表,然后通过 feed 生成 RSS 文件,整个逻辑就 50+ 行代码

由于我的博客还分离了独立的主题包 @sugarat/theme,我想把这个功能加到我的主题包里,这样使用这个主题的就可以简单的配置一下就能使用了,当然也为了方便广大 VitePress 用户更加简便的使用,我将这段逻辑单独分离封装到了 vitepress-plugin-rss 这个插件里。

接下来我将会先介绍一下如何食用这个插件,再介绍它的核心实现原理

插件使用

通过 pnpm/npm/yarn 安装插件

pnpm add vitepress-plugin-rss

.vitepress/config.ts 配置文件中添加配置使用

下面是最基础的使用配置

import { RssPlugin, RSSOptions } from 'vitepress-plugin-rss'
const baseUrl = 'https://sugarat.top'
const RSS: RSSOptions = {
  title: '粥里有勺糖',
  baseUrl,
  copyright: 'Copyright (c) 2018-present, 粥里有勺糖',
}

export default defineConfig({
  vite: {
    // ↓↓↓↓↓
    plugins: [RssPlugin(RSS)]
    // ↑↑↑↑↑
  }
})

然后运行 build 命令,你可以看到在rendering pages...后打印了生成 feed.rss 日志...

pnpm run build

如何快速为 VitePress 添加 RSS 订阅支持

同时会在导航栏的 socialLinks 中添加 rss 图标链接

如何快速为 VitePress 添加 RSS 订阅支持

使用是不是非常简单,只需要 10 行代码。

如果你对插件的实现原理感兴趣,请接着往下看 🎉 🎉 🎉。

核心实现原理解析

VitePress 的拓展在官方文档 Use Cases 部分有提到

如何快速为 VitePress 添加 RSS 订阅支持

其是基于 Vite 的,因此可以使用 Vite 的插件机制来实现主题内容的拓展。

buildEnd 修改

从官方的demo种可以看到,RSS 的生成逻辑是放在 buildEnd 中的,因此咱们插件也需要实现间接修改 buildEnd 方法

这个非常的简单,利用 Vite 的插件提供的 configResolved 钩子就行

下面是简单的demo

import { SiteConfig } from 'vitepress'

let resolveConfig: any = null

function configResolved(config: any) {
  // 避免多次执行
  if (resolveConfig) {
    return
  }
  resolveConfig = config

  const VPConfig: SiteConfig = config.vitepress
  if (!VPConfig) {
    return
  }
  const selfBuildEnd = VPConfig.buildEnd
  // 自定义 buildEnd 方法,添加 rss 生成支持
  VPConfig.buildEnd = async (siteConfig: any) => {
    // 调用自己的
    await selfBuildEnd?.(siteConfig)
    console.log('buildEnd', '生成 rss 文件');
  }
}

通过config.vitepress即可拿到vitepress的配置,然后重新定义 buildEnd 方法即可

这里可以直接快速的验证一下

如何快速为 VitePress 添加 RSS 订阅支持

运行后可以看到打印了 buildEnd 生成 rss 文件,说明我们的插件的修改已经生效了

如何快速为 VitePress 添加 RSS 订阅支持

icon 添加

这个也非常的简单,VitePress 在官方文档里有介绍 socialLinks

如何快速为 VitePress 添加 RSS 订阅支持

我们只需要在配置修改中添加一个 socialLinks 的配置即可

接着上述的demo,添加如下代码

VPConfig.site.themeConfig.socialLinks = [
  {
    icon: {
      svg: 'svg icon'
    },
    link: 'rss url'
  },
  ...VPConfig.site.themeConfig.socialLinks
]

svg的图标可以通过 xicons 这个网站查找

比如我这里找了一个 sun 的图标配上

如何快速为 VitePress 添加 RSS 订阅支持

启动博客后就能看见右上角这个小太阳了

如何快速为 VitePress 添加 RSS 订阅支持

MD文件获取与解析

这个是最核心的逻辑了,① 需要获取所有的 md 文件,② 解析里面的 frontmatter ③ 渲染HTML

这个在 vuejs/blog 中可以看到使用的是 VitePress 内置的 createContentLoader 方法(里面包含了上述3部分逻辑)

这里把其核心实现拆出来,方便大家理解和更好的自定义(笔者在插件里也没直接使用 createContentLoader 这个方法)

① 通过 fast-glob 获取所有的 md 文件

import glob from 'fast-glob'

const files = glob.sync(`${srcDir}/**/*.md`, { ignore: ['node_modules'] })

其中 srcDir 即文章所在的目录,可以通过如下方式获取到相对路径

// config 即 SiteConfig
const srcDir =
    config.srcDir.replace(config.root, '').replace(/^\//, '') ||
    process.argv.slice(2)?.[1] ||
    '.'

② 通过 gray-matter 解析 frontmatter

这里frontmatter就是文章开头里两个---之间的内容

例如

---
title: 示例标题
description: 文章介绍
---

利用 gray-matter 解析

import matter from 'gray-matter'
import fs from 'fs'

for (const file of files) {
  const fileContent = fs.readFileSync(file, 'utf-8')
  const { data: frontmatter, excerpt } = matter(fileContent, {
    excerpt: true
  })
}

其中 excerpt 即为文章的摘要信息(description)

③ MD 渲染为 HTML

这个使用 VitePress 提供的 createMarkdownRenderer 即可

// 由于插件里最后构建成 CJS/ESM 两种格式,VitePress 最新的版本支持 ESM,因此需要动态引入
const { createMarkdownRenderer } = await import('vitepress')

const mdRender = await createMarkdownRenderer(
  config.srcDir,
  config.markdown,
  config.site.base,
  config.logger
)
for (const file of files) {
  const fileContent = fs.readFileSync(file, 'utf-8')
  // 生成html
  const html = mdRender.render(fileContent)
}

RSS文件生成

通过上面的 markdown 文件的解析,我们已经拿到了所有的文章信息,接下来就是通过 feed 这个库生成 RSS 文件了

import { Feed } from 'feed'
const feedOptions = {
  // ...
}
const feed = new Feed(feedOptions)

for (const file of files){
  // 通过前面解析的信息,生成 feed item 
  feed.addItem({
    title,
    id: link,
    link,
    description,
    content: html,
    author: [
      {
        name: author,
        ...authorInfo
      }
    ],
    image: frontmatter?.cover,
    date: new Date(date)
  })
}

const RSSFilename = 'feed.rss'
const RSSFilepath = path.join(config.outDir, RSSFilename)

// 生成 rss 文件
writeFileSync(RSSFilepath, feed.rss2())

最后

插件的完整源码见 GitHub,欢迎大家试用和反馈

参考

转载自:https://juejin.cn/post/7270046196642005049
评论
请登录