为 VuePress 实现一个有序目录插件~

该插件已经发布至:GitHub | npm

如有帮助可以点个 star 支持一下 ~。

VuePress 默认不支持生成有序目录,比如想要下面这种效果:

markdown 复制代码
# 标题 1

## 标题 1-1

### 标题 1-1-1

## 标题 1-2

# 标题 2

目录将显示为:

css 复制代码
1 header 1

1.1 header 1-1

1.1.1 header 1-1-1

1.2 header 1-2

2 header 2

于是花了一点时间研究了 VuePress 的插件系统。

VuePress 架构

以下内容来自 VuePress - 架构

概览

VuePress 的简要架构:

  • Node App 会生成临时文件,包括页面、路由等。
  • Bundler 会将 Client App 和临时文件一起进行打包,就像处理一个普通的 Vue App 一样。

VuePress 分为两个主要部分: Node App 和 Client App:

  • 插件或者主题的入口文件会在 Node App 中被加载。
  • 客户端文件会在 Client App 中被加载,也就是会被 Bundler 处理。比如组件、客户端配置文件等。

核心流程和 Hooks

VuePress 的核心流程以及 插件 API 的 Hooks :

  • init 阶段:
    • 主题和插件会被加载。这意味着插件需要在初始化之前使用。
    • 由于我们要使用 markdown-it 来解析 Markdown 文件,因此需要在加载页面文件之前创建 markdown-it 实例:
      • extendsMarkdownOptions Hook 会被调用,用以创建 markdown-it 实例。
      • extendsMarkdown Hook 会被调用,用以扩展 markdown-it 实例。
    • 页面文件会被加载:
      • extendsPageOptions Hook 会被调用,用以创建页面。
      • extendsPage Hook 会被调用,用以扩展页面对象。
  • prepare 阶段:
    • 临时文件会被生成,因此所有和客户端文件相关的 Hooks 会在此处调用。
  • dev / build 阶段:
    • Bundler 会被加载:
    • extendsBundlerOptions Hook 会被调用,用以生成 Bundler 的配置。
    • alias Hook 和 define Hook 会被用在 Bundler 的配置中,所以它们会在此处调用。

开发插件

开发插件只需要导出一个插件对象,并在对应 Hooks 中实现自己想要的逻辑。

比如要实现有序标题,那么可以在 extendsPage Hooks 中扩展 Page 实例的 headers 属性。

extendsPage

  • 类型: (page: Page, app: App) => void | Promise<void>
  • 详情: 页面扩展。 该 Hook 接收一个函数,在参数中会收到一个 Page 实例。 该 Hook 可以用来在 Page 对象上添加额外的属性,或修改现有的属性等。 值得一提的是,针对 page.datapage.routeMeta 的改动可以在客户端代码中使用。
js 复制代码
module.exports = () => {
  return {
    name: 'vuepress-plugin-ordered-header',
    extendsPage: (page) => {
      // 实现扩展逻辑
    }
  }
}

实现功能

headers: PageHeader[] 数据结构如下:

ts 复制代码
interface PageHeader {
  level: number
  title: string
  slug: string
  children: PageHeader[]
}

每个标题下的子标题会被放入 children 数组,因此需要利用递归实现。

  1. 首先初始化一个计数数组 counter,代表 h1 ~ h6 6 个标题层级。
  2. 在每个标题层级上,首先将对应的层级计数器加一(即 counter[level] = (counter[level] || 0) + 1)。
  3. 然后对于比当前层级更深的计数器(即该标题的子标题)进行重置(即 counteri = undefined)。
  4. 再利用 counter 数组给标题增加序号。
  5. 最后返回处理好的 page 对象。
js 复制代码
module.exports = () => {
  return {
    name: 'vuepress-plugin-ordered-header',
    extendsPage: (page) => {
      const counter = Array(6).fill(undefined)

      const reorderHeaders = (headers) => {
        headers.forEach((header) => {
          const level = header.level - 1

          counter[level] = (counter[level] || 0) + 1

          // reset level
          for (let i = level + 1; i < counter.length; i++) {
            counter[i] = undefined
          }

          header.title = `${counter.filter(Number).join('.')} ${header.title}`

          if (header.children) reorderHeaders(header.children)
        })
      }

      reorderHeaders(page.headers)

      return page
    }
  }
}
相关推荐
肆仲冬4 分钟前
不用框架,用 TypeScript 从零搭一个 Agent 框架
前端
林语琛12 分钟前
我写的 switch…break 被 Babel 偷偷吞了
前端·javascript·babel
烈风逍遥16 分钟前
第六篇:RAG 知识库构建与检索全链路
前端·人工智能·后端
爱丶不疚18 分钟前
什么是 Jev 决策模型?它适合干什么?
前端·agent
江畔柳前堤36 分钟前
字节跳动·大模型应用知识手册
前端·人工智能·深度学习·opencv·目标检测·重构·transformer
星核0penstarry1 小时前
从“一次生成“到“持续进化“:自进化社媒 Agent 的工作流拆解
人工智能·开源·agent
LEE1 小时前
前端转型全栈 03:接口失败也返回 200,OpenAPI 契约与错误码怎么定
前端·后端·全栈
我命由我123452 小时前
CSS - CSS 媒体查询 orientation
前端·javascript·css·html·css3·html5·js
lichenyang4532 小时前
让 VK 小程序调用 HarmonyOS 原生能力:一次跨端 Bridge SDK 的设计与实践
前端
阳光宅男@李光熠3 小时前
【电子通识】一起学习TDK的EMC基础——电池兼容设计方法概述
java·前端·数据库