入坑 Docusaurus,看这一篇就够了

🎙️ 前言

作为一个造轮惯犯,我喜欢在项目中使用 Monorepo,然后造一堆项目专属组件库和工具库,幸运的话,还会孵化一些普适的能够开源的库。

为了其他人能够快速了解如何使用,每个 package 都会配上详尽的 Demo 或单测。

我认为已经够开发友好了,队友们只要看类型定义,简单运行一下 Demo 或跑一下测试,就能够对如何使用相关功能有足够的认知。

但终究,还是我想太简单了,依然会有人不断来问这是什么,那怎么用,又或者类似的东西又写一遍(往往写得还不好),他们根本不看那些玩意儿(或者看不懂)。

我反思了一下,没有直观的文档的情况下,一是可发现性差,二来上手成本高。

另外,我写的另外一个项目,当时对色彩等方面做了蛮多的研究记录,最终却只能成为散落在各个角落的 Markdown,无法形成系统性的参考,想找的时候,只能靠记忆全局搜素。

痛定思痛,我开始着手研究如何搭建文档站,嗯,需要一个文档框架。

文档框架往往用 Content-DrivenJamstack FrameworkStatic Site Generator (SSG) 等词来描述自己,其中以 SSG 居多。

TL;DR

在众多的 SSG 框架中,我基于第一印象、支持特性、用户数据、上手难易、界面设计等客观主观方面的比较,最终选择了 Meta 家的基于 React 的 Docusaurus。

选择 Docusaurus 的 n 个理由:

  1. 天然支持 TypeScript
  2. 基于 React(符合我目前的技术栈)
  3. 支持 Markdown、MDX
  4. 支持搜素、国际化等文档站必要因素
  5. 拥有大量的用户,处于积极维护的状态
  6. 上手简单,对新手友好

但真要找毛病,也不是没有:

  1. 名字是虚构的单词,难写难读(-saurus 为表示爬行动物的后缀,多为恐龙,从图标可以看出)
  2. 本地启动项目,用的是 Webpack,很明显的慢(但我认为这不是什么大不了的事情,晚些他们底层悄咪咪改了也有可能)
  3. 部分界面元素偏大,不够精致
  4. TOC 及书签点击后的定位,没有考虑到顶栏的高度,部分内容在跳转后被遮挡

总之,无论从 UX 还是 DX 来讲,Docusaurus 都是一个非常不错的 SSG 框架,虽然有些小瑕疵,但瑕不掩瑜。

主要内容

适合读者

  • 想写文档站,不知道如何技术选型的同学
  • 想入坑 Docusaurus 的同学

你将获得

  • 了解有哪些有名的 SSG 框架
  • 选择 Docusaurus 的理由
  • 如何使用 Docusaurus 从 0 开始搭建文档站

编辑历史

日期 版本说明
2025/03/17 V1 拖延症陆陆续续写了有三个月多

🤔 为何需要 SSG

README 是最接近文档的存在,大家应该都会用 Markdown 来写 README。

但作为技术文档,读者可能需要直观的代码运行效果,最好可交互,并且能够一键复制相关代码以便快速验证。这些就不是 README 这样的纯 Markdown 的工作职责了。

即便如此,Markdown 仍然是我们写文档的不二选择,只不过,我们想要的更多------这就需要一个文档框架。

🧐 如何挑选 SSG

0 - 候选列表

Jamstack Generators 这里提到了 300 多个 SSG 的框架。但对 Language 和 Templates 的分析并不全面,而且有一部分也没有包含进来。这么多,也没有必要都去研究,以下是我略研究过的(字母序):

名称 自述 TS MDX React Vue
astro The web framework for content-driven websites
docsify A magical documentation site generator
docusaurus Build optimized websites quickly , focus on your content
docz It's never been easier to document your things!
dumi 为组件研发而生的静态站点框架
eleventy Eleventy is a simpler static site generator
gatsby Gatsby is a React-based open source framework for creating websites.
gitbook Build docs as a product, not an afterthought
gridsome A Jamstack framework for Vue.js
hugo The world's fastest framework for building websites Go
jekyll Transform your plain text into static websites and blogs Ruby
next The React Framework for the Web
nuxt The Intuitive Vue Framework
pelican Static site generator powered by Python Python
rspress Lightning Fast Static Site Generator
vitdoc A new way to write Component Usage
vitepress Vite & Vue Powered Static Site Generator
vuepress Vue-powered Static Site Generator

SSG 这个赛道也是很卷,相互间会暗自较劲,要么做比较,要么提供迁移方案(这些也是了解有哪些 SSG 的途径),比如:

1 - 第一印象

找一个趁手的工具,很多时候跟相亲一样,第一印象,眼缘很重要。

眼缘首先看官网。作为 SSG 来说,官网必须是自己的第一个用户,是其展现自身能力的第一演练场。从官网,我们可以看其整体设计感,以及是否有我们需要的相关功能。

名称 第一眼好感 个人评价
astro ★★★★☆ 很有设计感,文档排版及色调也不错,该有的功能都有;但顶栏上的下拉用了原生的 select,略拉低档次,且默认不是 SPA
docsify ★★★ 相对简陋,没有顶栏,没有暗色,没有独立的 TOC
docusaurus ★★★★☆ 整体样式偏重,但不难看,文档全面;但部分设计偏大(比如侧边栏的图标),不够精致
dumi ★★★☆ 首页略简陋,文档树略丑
eleventy ★★☆ 内容区域偏窄;有搜索但交互不方便;没有暗色;非 SPA,点击后,左侧文档树会刷新,导致丢失位置;没有 TOC 是最致命的
gatsby ★★★ 广义的应用框架,上手略难
gitbook ★★★ 老牌 SSG,整体样式比较清爽;但要账号,且没有暗色切换
gridsome ★★★★☆ 布局、排版等细节非常棒,非常契合写文档
next ★★★☆ 更广义的应用框架,基于 React,上手略难;虽然也有人用它来做文档站,但更多的是商业站
nuxt ★★★☆ 从名字上看,它和 Next 的定位是一样的,也是广义上的应用框架,基于 Vue
rspress ★★★☆ 让人很有尝试欲的 SSG
vitdoc 文档内容很少,可能烂尾了
vitepress ★★★★ 内容全面,结构清晰,整体样式也不错;Vue 技术栈可以看看
vuepress ★★ 被 vitepress 代替了

总结:论排版的清爽而言,个人认为 Gridsome 最符合文档站的审美需求。从功能的全面性来讲,Astro、Docusaurus、Next、Vitepress 等都不错。

2 - Showcase

其次看它的用户,一来能够判断其受欢迎程度,二来可以作为借鉴学习的参考。用户量大的,自然会很骄傲地秀出来。

名称 Showcase 数量
astro astro.build/showcase 1200+
docsify docsify.js.org/#/awesome?i... 130+,但相当一部分已经无效
docusaurus docusaurus.io/showcase 270+
gatsby www.gatsbyjs.com/showcase 600+
gitbook www.gitbook.com/customer-sh... 100+
gridsome 收集中...
jekyll jekyllrb.com/showcase/ 47+
next nextjs.org/showcase 120+
nuxt nuxt.com/showcase 100+

3 - 文档基本需求

从官网还可以简单看它是否符合文档站的基本需求。每个人的需求可能都不大一样,我的如下:

  1. 支持 TS,必需,无 TS 不 Coding
  2. 支持标准 Markdown:必需,并支持 FrontMatter、右侧独立 TOC、代码高亮、外链默认 target="_blank"
  3. 支持 MDX:必需,以支持更多动态特性,比如可交互 Demo
  4. 支持熟悉的 UI 框架:必需,否则不会需要 MDX
  5. 支持黑白主题:必需
  6. 支持全文搜索:必需,最好是本地搜素
  7. 支持国际化:非必需,有则善
  8. 支持多版本:非必需,有则善
  9. 支持 RTL:非必需
  10. SEO:非必需

以下判断,时间有限,不一定正确。

名称 TS Markdown TOC FrontMatter 代码高亮 自动外链 MDX 框架 黑/白 搜索 国际化 多版本 RTL
astro React
docsify 左侧 Vue Vue
docusaurus React
docz 左侧 React
dumi 左侧 React
eleventy 顶部 React
gatsby React
gitbook ✅ 很慢
gridsome Vue
hugo Go
jekyll Ruby
next React ✅ 底部
nuxt Vue Vue
pelican Python
rspress React
vitdoc React
vitepress Vue Vue
vuepress Vue Vue

4 - 客观数据

受欢迎程度(下载量、Star 数、Issue 数)和更新频次(版本数、更新时间),属于比较可观的信息,能进一步帮我们确定选择。

以下数据,收集时间为 2025/03/14,纯手工,非 AI。

名称 NPM 周下载 Github Star Open Issue 版本数 第一版 上次更新
astro github npm 365k 49.7k 148 1170 2021/03 2025/03
docsify github npm 43.7k 29k 143 193 2016/11 2023/06
docusaurus github npm 387.6k 58.5k 281 2027 2017/06 2025/01
docz github npm 3.5k 23.7k 108 268 2018/04 2022/02 💥 官宣停更
dumi github npm 40.2k 3.7k 130 366 2019/12 2025/02
eleventy github npm 70k 17.7k 396 193 2018/01 2024/10
gatsby github npm 219.5k 55.8k 210 2979 2015/05 2024/12
gitbook github npm 3.4k 27.6k 42 21 2015/02 2017/07
gridsome github npm 1.4k 8.6k 540 75 2018/09 2020/11
hugo github npm - 78.7k 454 316 2013/06 2025/02
next github npm 7559.5k 130k 2.6k 3029 2011/07 2025/03
nuxt github npm 666.3k 56.5k 869 349 2016/? 2025/03
rspress github npm 6.4k 1.6k 73 675 2022/10 2025/03
vitdoc github npm 40 83 1 140 2022/08 2025/03
vitepress github npm 114.6k 14.1k 336 251 2020/05 2025/01
vuepress github npm 36.4k 22.7k 541 249 2018/04 2023/08 💥 基本停更

选择工具,跟买车一样,最怕停产了,根据上面的数据,可以放弃停产 2 年以上的那些。

5 - 上手

以上框架,不论是否已停产,在我研究 SSG 框架的最初,应该也有试过(但当时未做记录)。出于篇幅的考虑,以下仅对持续更新中的进行上手演练,以便读者根据自己的喜好进行选择。

Astro

Getting Started - Installation

Astro 内置了 TS 支持,不需要加参数,这点我非常喜欢。

pnpx create-astro 创建项目,首次使用会比较慢,约莫 2min,再次运行耗时在 20-50s 左右,整体体验很棒。

跑起来:

  1. 本地服务
    • 秒开,得益于底层的 Vite
  2. 初始效果
    • 界面太简单,对于想简单做一个文档站的需求并不友好
    • 底部那个应该是开发工具栏,看上去很不错
  3. 文件结构
    • 内容比较少,对于新手来说,尚需要琢磨很久
    • Astro 提供了自己的 .astro 模板,虽然不难,但也增加了一定的学习成本
    • 个人不甚喜欢 IDE 有关的东西入库,那个 .vscode 比较刺眼(可以推荐,不要硬塞)
    • package.json 未设置 private: true

到这里,你可能想放弃 Astro 作为 SSG 框架的想法了?别急。有很多模版,你可以在 这里 找找看,使用命令 npm create astro@latest --template <example-name> 创建项目。

模板是 Astro 的强大的体现,但我认为在 SSG,尤其是技术文档这个赛道,反而可能是个弊端,很容易让潜在用户止步在了「该选哪个模板」这一步。

总结:Astro 是一个超级强大的 SSG 框架,文档站、商业站、博客等各个领域都适用,非常推荐。但作为罹患「选择困难症」的病友,我先撤了 🤪。

Docusaurus

Getting Started - Installation

pnpx create-docusaurus 创建项目,设计感不如 Astro 强,耗时在 20s 左右。

跑起来:

  1. 本地服务
    • 有明显的构建过程,耗时 5s 多(猜是 Webpack?对的)
  2. 初始效果
    • 作为 Demo 来讲,可以说是五脏俱全了,对新手入坑十分友好
    • 整体的风格样式对于有设计洁癖的人来讲达不到满分
  3. 文件结构
    • 结构清晰,没什么学习曲线
    • 静态文件、React、Markdown、MDX 等都有例子
    • package.json 设置了 private: true

习惯了 Vite 的秒开,是否看到 5s 的构建时间就难受了?一开始可能会有那么点,但我认为秒开并不是一个 SSG 最大的亮点。而且,构建这个事情,有可能下个版本他们偷偷换一下底层就好了。

总结:Docusaurus 对于想要快速入坑 SSG 写文档,并且期望基于 React 技术栈的,提供了十分有效的起始项目。

P.S. 由于这个起始项目太有效了,甚至有些人懒到图片都懒得换 😂,比如 turf.js 的文档:

Dumi

初始化

pnpx create-dumi 创建项目,但它不会创建项目目录,需要先建目录,但后边又来问项目名称,可以说这里的体验非常不好。首次运行耗时 1.5min 左右,后续运行在 35s 左右。

跑起来:

  1. 本地服务
    • 虽然也是 Webpack,也还算快,可能是因为内容比较少的缘故
  2. 初始效果
    • 内容太少,达不到「改巴改巴就能用」的标准
  3. 文件结构
    • 只有 Markdown
    • 虽然我是 huskylint-staged 的忠实用户,但我并不希望在一个文档站的默认模板中看到有这些(说不定我会用 monorepo 的方式呢)
    • package.json 未设置 private: true

总结:新手入坑做的不够,它只起到了「我能跑起来」的说明,不够吸引让新用户直接上手开干。

Eleventy

Getting Started

11ty 的上手比较原始,没有一键生成的能力,纯手撸,问题不大,但懒人第一时间会拒绝。

总结:我不试了,但 11ty 还是一个很不错的 SSG。

Gridsome

不试了,直接总结。

总结:样式清新,我很喜欢。但用它的话,需要全局安装其 CLI,不太喜欢,它又没有提供 create-gridsome,于是没有试它。

Gatsby

Quick Start

pnpx create-gatsby 创建项目,引导做的很好,1min 左右。

跑起来:

  1. 本地服务
    • 抛错了 💥
  2. 初始效果
    • 看不到 👻
  3. 文件结构
    • 太简单了,满足不了直接上手改巴改巴的诉求
    • package.json 设置了 private: true

总结:弃坑(隔了三个月再次尝试,依然如此)。

Next

Getting started - Installation

pnpx create-next-app 创建项目,引导做的很好,30s 左右。

跑起来:

  1. 本地服务
    • 起服务很快
    • 构建时间平摊到查看页面的时候
  2. 初始效果
    • 只有一个主页
  3. 文件结构
    • 静态文件和 TSX
    • 没看到 Markdown 和 MDX
    • package.json 设置了 private: true

Next 也提供了大量的 模板,其中也不乏文档站,还是一样,「选择困难症」患者会心生抗拒。

总结:Next 可能是最受欢迎的应用框架,但 SSG 的话,要斟酌一下,毕竟上手略难。

Nuxt

Getting started - Installation

pnpx create-nuxt 创建项目。

跑起来:

  1. 本地服务
    • 抛了个错,但有页面
  2. 初始效果
    • 就那么简单
  3. 文件结构
    • 太少内容了
    • package.json 设置了 private: true

结论:不太有继续下去的勇气 😳。

Rspress

听不少字节的同学提到过这个。

Getting started

pnpx create-rspress 创建项目,几乎没有废话。

跑起来:

  1. 本地服务
    • 速度快
  2. 初始效果
    • 不错
  3. 文件结构
    • 虽然缺少 MDX 的例子,但也算不错
    • package.json 设置了 private: true

总结:除 Docusaurus 外,Rspress 的表现算不错了。

Vitepress

Getting started

pnpx create-vitepress 创建项目,没有半句废话,简单干脆。

跑起来:

  1. 本地服务
    • 跑起来了
  2. 初始效果
    • 只是跑起来了..
  3. 文件结构
    • 少到跟手撸没啥区别
    • package.json 设置了 private: true

总结:毫无食欲。

结论

  • 第一印象:Astro、Docusaurus、Gridsome、Next、Vitepress 胜出
  • Showcase:Astro、Docusaurus、Next 等都有很大的用户群体
  • 基本需求:React 可以选 Docusaurus、Next、Rspress;Vue 可以选 Vitepress、Vuepress
  • 客观数据:Astro、Docusaurus、Gatsby、Next 胜出
  • 上手:Docusaurus 完胜

综上,我选择 Docusaurus。

呼~,总算可以到正文了,开始实操。

🌱 实战 Part 1:准备

创建项目

推荐 Monorepo 的方式,实在想要单独拎一个 Git 仓库作文档站,...虽然不赞成,但也不推荐。

先取个目录名,建议从下面几个中挑一个(当然也可以换作别的,只要你喜欢):

  • documentation ← 本文所选
  • docs
  • website ← Docusaurus 默认
  • web

建议切一个新的分支。执行 pnpx create-docusaurus 在项目根目录下创建文档项目:

如果你的项目还不是 Monorepo,推荐使用 pnpm,在项目根目录下新增 pnpm-workspace.yaml,内容如下:

yaml 复制代码
packages:
  - "documentation"

内容形式

在新生成的 Docusaurus 项目中,可以看到有三种形式的内容,分别是:

  1. Page 独立页面,没有侧边栏,支持 React、Markdown 和 MDX,一般用来写主页等单独页面
  2. Doc 文档,有侧边栏、TOC,支持 Markdown、MDX
  3. Blog 博客文章,文件名以日期打头,侧边栏按年份分组,有 TOC,支持 Markdown、MDX

修改 package.json

洁癖 节操的同学可能需要先改一下 package.json 的依赖项:

  1. docusaurus 的所有版本写死了,加上修饰符 ^
  2. typescript 的版本修饰符是 ~,改成 ^
  3. reactreact-dom 的版本是 19,视情况看要不要降级,我目前的代码尚未适配 19,于是先降级成 18
    • "react": "^19.0.0""react": "^18.3.1"
    • "react-dom": "^19.0.0""react-dom": "^18.3.1"
  4. 安装其他基础依赖
  5. 使用 ncu 更新版本,执行 ncu -ui 进行升级(忽略 react)

更新后,记得重新安装依赖。

启动项目

如果你像我一样,喜欢 127.0.0.1 胜过 localhost 的,你会发现 pnpm start 之后访问 http://127.0.0.1:3000 不通。但 Docusaurus 不像 Webpack 或 Vite 那样有 host 配置项(至少我看了类型里没有),可以改 package.jsonstart 命令:

diff 复制代码
- "start": "docusaurus start",
+ "start": "docusaurus start --host 0.0.0.0",

以上,0.0.0.0 也可以换成 127.0.0.1,但前者可以让你的本地文档服务可以在局域网可见。参考 docusaurus start - siteDir

初次尝试 - 修改品牌元素

给新手的建议:在未正式开始写文档之前,可以根据官方生成的文件改巴改巴,看看效果。有一定的了解后,再正式着手才会事半功倍。

从最简单的开始,建立自己的品牌形象,也就是「改巴改巴」的一些事情:

修改 知识点
Logo、FavIcon,简单一点可以去 favicon.io 自己生一个 static 目录的作用
名称、Slogan useDocusaurusContext().siteConfig 能够在组件中访问 docusaurus.config.ts 的内容
GitHub 等相关链接 配置项 organizationNameprojectName 的作用
Footer 配置项 themeConfig.footer 的内容和作用
品牌色 如何使用 CSS Var 自定义样式
首页 pages 目录下的 React 或 Markdown 能够成为独立页面(没有侧边栏)
docs 下的文档 docs 目录结构与 Sidebar 之间的关系、文档的 FrontMatter 作用、文档在侧边栏如何排序等等
Blog 中的作者信息(记得用全局替换) blogs/authors.yml 的格式及作用

注意:favicon 的浏览器缓存可能会比较顽固,常常很难刷掉。

补充一点,在修改首页 src/pages/index.tsx 的时候,你会发现 Docusaurus 给的 CSS 方案是 CSS module,加上 clsx 这个辅助工具库。不知道你怎么想,反正这不是我喜欢的方式,个人更推崇 styled-components。所以,我顺便把首页的样式重写并去掉了 clsx 的依赖。

调校样式

初始样式的问题

差不多的 Docusaurus 站点都基于 @docusaurus/preset-classic,它的优点就是用起来简单。然而在有设计洁癖的人看来,很多地方并不能令人满意。

相较于 Astro、Gridsome、Next 等,Docusaurus 的初始样式给人不够精致的感觉,部分设计元素一看就让人感觉尴尬(当然,有部分纯粹是个人的吹毛求疵),比如:

  • 侧边栏的子项箭头图标:偏大
  • 侧边栏底部的收起按钮图标:太大,收起后左侧有一竖宽条,奇丑无比(实际上很多站点都把这个功能关了,估计就是因为样式实在太难看)
  • 面包屑:有背景色,间接使其必须有额外的高度
  • Tab:太高,占空间
  • 代码块:灰底,在白色主题下,层次不够分明
  • 表格:线框太多太重,斑马条纹影响观感,单元格 padding 太大
  • code:边框过重

启用 SCSS

以上多数问题,覆盖 CSS Var 即可解决,少数可以用样式覆盖的方式。

Docusaurus 默认仅支持 CSS,你可能更希望用 LESS 或 SCSS。个人而言,LESS 是首选,但 docusaurus-plugin-less 已经很久不更新了,没说支持 V3,只好退而求其次,用 docusaurus-plugin-sass

shell 复制代码
pnpm add -D docusaurus-plugin-sass sass

更新 docusaurus.config.ts

diff 复制代码
plugins: [
  ...
+  'docusaurus-plugin-sass'
]

自定义 CSS Var

小技巧:使用 Developer Tool 查看样式,拷贝相关的 CSS Var 代码片段到 src/css/__var-ref.scss,不需要在代码中引它,但 IDE 会很高兴地帮你作代码提示。

自定义入口在配置文件 presetsclassic theme.customCss,初始值是 './src/css/custom.css',它也可以是数组,能让我们方便地根据功能切分样式代码,以下是我切的:

ts 复制代码
export default {
  // ...
  presets: [
    ['classic', {
      customCss: [  
        './src/css/var.scss', // 按需覆盖 CSS var
        './src/css/custom-common.scss', // 一些通用 class 的覆盖
        './src/css/custom-markdown.scss', // markdown 下的 tag 选择器样式
        './src/css/custom-site-header.scss', // 站点头样式自定义,比如一些图标等
        './src/css/custom-site-footer.scss', // 站点底部样式自定义
        './src/css/custom-sidebar.scss', // 侧边栏样式自定义
        './src/css/custom-toc.scss', // 右侧 TOC 样式自定义
        './src/css/custom-live-editor.scss' // live editor 插件样式自定义
      ]
    }],
    // ...
  ]
};

对应文件结构:

配置顶栏

顶栏是文档站最重要的导航,在 docusarurus.config.tsthemeConfig.navbar 配置,分左右,左侧主要是文字导航,右侧为功能导航(版本、语言、主题、Git 地址、搜索等)。

具体填哪些,视你的需求而定。

左侧导航不必太多,一般指向 docs 下的一级子目录,再加一个 blog(如果有的话),因此,docs 下的一级目录不应太多。

当然,内容多的时候,也可以配置 下拉菜单

扩展 Markdown / MDX

Markdown 是我们写文档的首选方式。Docusaurus 除了 CommonMarkGFM 外,还内置了文档必不可少的扩展,并且支持 MDX(使得我们写文档有了无限的可能)。

内置 Markdown 扩展

Live Editor

经常看文档的同学应该知道,那种既可以看到效果,又可以看到代码的文档看起来有多么爽,如果还可以动手就更要不得了。

Docusaurus 官方提供了支持,需要安装插件 @docusaurus/theme-live-codeblock,详见官方说明 Interactive code editor

题外话,Docusaurus 似乎管 Plugin 和 Theme 都叫「插件」,这一点让人很琢磨不透。这是它的原话:「You can create an interactive coding editor with the @docusaurus/theme-live-codeblock plugin.」对两个概念,它的 架构文档 有简单的说明,

但 Live Editor 的样式,一如既往地不够精致,你可以看到前面我加了自定义样式,代码也很简单:

scss 复制代码
.markdown {
  [class^=playgroundContainer_] {
    box-shadow: 0 0 4px 0 hsl(0 0% 0% / 17%);
  }
  
  [class^=playgroundHeader_] {
    display: none;
  }
}

其实就是把两个标题块给隐藏了。

同时,官方文档也明确指出,在 Live Editor 里是不能 import 的,而是需要我们事先把要用的组件注册好。我认为这个很好理解,Live Editor 毕竟运行在 MDX 下,而在 MDX 里是可以 import 的,Live Editor 必然不能被 MDX 的 import 污染,Live Editor 因此开了一个类似沙箱的环境,它与 MDX 上下文是隔离的。

注册组件需要用到 Docusaurus 提供的 swizzle 指令

shell 复制代码
npm run swizzle @docusaurus/theme-live-codeblock ReactLiveScope -- --eject

它会提问几个问题,确定后会新建一个文件 src/theme/ReactLiveScope/index.tsx(不打算在里边写 JSX 可以把它改成 .ts),按需改这个文件:

diff 复制代码
import React from 'react';
+import {
+  InputSwitch
+} from 'kcuf-ui';

export default {
  React,
  ...React,
+  InputSwitch
};

使用也非常方便,只需要在常规的 Code Block 的类型后加上 live 即可。这样,我们可以为每个组件加上 Live Demo 章节了,给用户最快的直观感受:

md 复制代码
## Live Demo

\`\`\`tsx live
<InputSwitch defaultValue={true} />
\`\`\`

效果(我隐藏了两个标题):

添加自定义组件

在 MDX 中可以像常规 ES 一样直接 import 任何 React 组件,但如果很多页面都需要某个组件,而要一次又一次地手动引入未免就过于麻烦了,Docusaurus 支持我们把常用的组件 注册到全局 scope。这样还有一个好处,常规的 Markdown 也能用,而不仅仅局限于 MDX。

手动建文件 src/theme/MDXComponents.ts(不支持 swizzle),内容视你的具体情况,比如我的:

ts 复制代码
import MDXComponents from '@theme-original/MDXComponents';

import {
  Colored,  
  Highlight,
  TagRequired,
  TagReadonly,
  TagTodo,
  TagOverride,
  TagDefault,
  ComponentBrief
} from '../../rc';

export default {
  ...MDXComponents,
  C: Colored,
  Highlight,
  TagRequired,
  TagReadonly,
  TagTodo,
  TagOverride,
  TagDefault,
  ComponentBrief
};

这里,我加了组件文档中常用的一些小组件,比如 ComponentBrief,只需要最简单的代码便可展示组件的基本信息:

再比如各种 Tag 展示:

我其实不太明白为什么 Docusaurus 不把它加到 swizzle 里,他们实际上完全可以这么做。

🍒 实战 Part 2:写

搞了这么久,准备工作和基础知识差不多了,总算可以开始真正着手写文档了。这其实才是最难的,因为这很玄学,你可能不知道该写哪些内容,内容该如何组织,等等。

这很可能是多数人即使入坑了某个文档框架,又无法坚持的最大原因。

其实,你回过头来去看看你学过的各项技能,一旦你对这项技能有一定的自信后,你会发现,你可以总结出来的学习方法最合适的,可能就是「无他,唯手熟尔」这句话了。

所以,开始写,多写,多总结,多改,周而复始,一定能达到「无他,唯手熟尔」的境界。而如何开始呢,那就是模仿------所有的学习都是从模仿开始的。

何时开始

写文档须趁早,甚至可以先于代码,总之,不要有「空了再补」的想法。

你可能会认为「写代码的时间都不够,哪有时间写文档」,这就跟「哪有时间写测试」一样。你应该听说过 TDD,即「测试驱动开发(Test Driven Development)」,TDD 就是提倡测试与开发并行,甚至测试先行。熟悉 Jest、Viteset 等单测框架,或者 Storybook 等 UI 测试框架的同学,对这种开发方式带来的好处一定可以深有体会。

同样的,我们是否能够也做到「文档驱动开发」呢?

理论上可行,实际上倒也没有那么绝对,但两者并行,起到文档辅助开发的效果,一定是可以的。

另外,你可能会有「晚些空了再补文档」的想法,都是拖延症在作祟。即使真的空了,你可能也已经过了要为自己的 NB 代码或项目写文档的激情期了,即使还想写,也可能已经没有了最新鲜的一手资料,要么写错,要么想很久才能记起当时为什么那么设计。

书写规范

个人对一致性有着近乎痴狂的偏执,写代码如此,写文档亦如此。以下是我一贯坚持的书写规范:

  1. 中文段落中不用英文标点,反之依然(99% 的人都会犯的错误)
  2. 中文和英文及数字之间加空格
  3. 中文句子中出现的英文单词,尽可能保持大写字母打头
  4. 链接、粗体字、代码等与一般段落文字之间加空格
  5. 能不用就不用叹号
  6. 少用「您」,用「你」或「我们」

最佳实践

一些最佳实践建议:

  1. 使用简练且结构合理的标题组织文档结构,清晰的 TOC 能帮助读者快速找到所需的内容
  2. 图优于表格,表格优于列表,列表优于自然段落(是不是很像 PPT 的建议?)
  3. 制定术语表,不要出现近义词,相同的英文术语大小写保持一致
  4. 保持精简段落和句子,段落最好不要超过 7 行
  5. 用正向句式,少用反向句式
  6. 克制你的幽默细胞

模板

模板的作用,除了提效,更重要的是保证文档的一致性。另外,当模板有更新的时候,同步更新已有的文档,也可以促进文档的整体进步。

模板的形式,自动(用脚本)或手动(拷贝)都不要紧,比如我为「组件」写了一个模板,就近放在了 docs/component 目录下:

以上,利用了 Docusaurus 忽略下划线打头的文件 的规则,_template.mdx 并不会被生成到文档中。

组织与排序

文档会越写越多,需要将相关的文档有序地组织起来,以帮助读者即便不用搜素也能快速定位想找的内容。

我们利用顶栏(在 docusaurus.config.tsthemeConfig.navbar.items 进行配置)增加一级入口,假设我们会写这些:

  1. 开发指南
  2. 组件
  3. 数据层
ts 复制代码
themeConfig: {
  // ...
  navbar: {
    // ...
    items: [{
      type: 'docSidebar',
      sidebarId: 'guide',
      position: 'left',
      label: '开发指南'
    }, {
      type: 'docSidebar',
      sidebarId: 'component',
      position: 'left',
      label: '组件'
    }, {
      type: 'docSidebar',
      sidebarId: 'data',
      position: 'left',
      label: '数据层'
    }]
  }
}

sidebar.ts 中:

ts 复制代码
import {
  SidebarsConfig
} from '@docusaurus/plugin-content-docs';

export default {
  guide: [{
    type: 'autogenerated',
    dirName: 'guide'
  }],
  component: [{
    type: 'autogenerated',
    dirName: 'component'
  }],
  data: [{
    type: 'autogenerated',
    dirName: 'data'
  }]
} satisfies SidebarsConfig;

以上 type: 'autogenerated' 很关键。

文件目录:

text 复制代码
docs/
├── component/
├── data/
└── guide

文档在 Sidebar 中的顺序,也是文档作者非常在意的。在 Docusaurus 中,可以在 Front Matter 定义 sidebar_position,但我更喜欢通过 文件名数字前缀 的方式,保证文件顺序和视图顺序一致,并且这些前缀数字并不会体现在 URL 中,完美。

虽然 Docusaurus 官方更推荐使用 Front Matter,但我绝对更推荐数字前缀。

API 自动生成

写组件的 Props 描述是一件非常枯燥的体力活,一般就是从代码中不厌其烦地拷贝相关的代码及注释,然后组织成 Markdown 格式的表格。

你可以希望有自动化的能力,至于是「自动生成」,亦或是「自动更新」(即直接改对应的文档),我更倾向于前者------工具自动提取相关的信息拼装成文档代码片段,至于这个代码片段要不要放,放哪里,由我来决定。

但我看了这么多框架,鲜少在其首页提到这个能力的,也就 Vitdoc 提了一下,但也只是仅仅提了一下,之前我说了,Vitdoc 看上去就像个烂尾的半成品。

Rspress 提到有相关的插件 @rspress/plugin-typedoc@rspress/plugin-api-docgen

Docusaurus 也有一个非亲生的插件 docusaurus-plugin-typedoc。我没有试,因为粗看不像我需要的。我其实只需要一个脚本,能帮我免去乏味枯燥的体力劳动就可以按照我想要的格式生成相应的 Markdown 片段即可。

上面的插件给了一些启发,于是动手写了一个命令脚本。

安装依赖

documentation 下安装:

shell 复制代码
pnpm add -D react-docgen-typescript commander ts-node-dev

ts-node-dev 代替 ts-node,后者会运行出错。

写脚本

新建 script/generate-md-api-ref.ts,内容我直接贴了,需要注意几点:

  1. 项目结构仅符合我的项目,若要符合你自己的项目,需调整路径
  2. 我加了自定义组件 TagRequiredTagDefault,需要在 src/theme/MDXComponents.ts 中注入(看前面章节)
ts 复制代码
import path from 'node:path';
import {
  existsSync
} from 'node:fs';
import process from 'node:process';

import {
  program
} from 'commander';
import {
  ComponentDoc,
  PropItem,
  withCustomConfig
} from 'react-docgen-typescript';

interface ICommandArgs {
  pkg: string;
}

const NO_AUTO_DEFAULT_NAMES = [
  'value',
  'checked'
];

const parser = withCustomConfig('./tsconfig.json', {
  propFilter: (prop: PropItem) => {
    if (prop.parent) {
      return !prop.parent.fileName.includes('node_modules');
    }
    
    return true;
  }
});

function codify(content: string): string {
  return `\`${content}\``;
}

function safeCellContent(content?: string): string {
  return content ? content.replaceAll('|', '\\|').replaceAll('\n', '<br />') : '';
}

function printPropName(prop: PropItem): string {
  const parts: string[] = [codify(prop.name)];
  
  // JSDoc 中添加 `@default`,默认对类型为 `boolean` 的使用 `false` 做默认值
  if (prop.required) {
    parts.push('<TagRequired />');
  } else {
    if (prop.defaultValue) {
      parts.push(`<TagDefault>${prop.defaultValue.value}</TagDefault>`);
    } else if (prop.type.name === 'boolean' && !NO_AUTO_DEFAULT_NAMES.includes(prop.name)) {
      parts.push('<TagDefault>false</TagDefault>');
    }
  }
  
  return parts.join(' ');
}

function printPropType(prop: PropItem): string {
  return codify(safeCellContent(prop.type.name.replaceAll('ReactElement<any, string | JSXElementConstructor<any>>', 'ReactElement')));
}

function printPropDescription(prop: PropItem): string { 
  return safeCellContent(prop.description);
}

function generateMarkdownTable(component: ComponentDoc): string {
  const markdownPropsLines = [
    '| 属性 | 类型 | 说明 |',
    '| --- | --- | --- |'
  ];
  
  for (const [, prop] of Object.entries(component.props)) {
    markdownPropsLines.push(`| ${[
      printPropName(prop),
      printPropType(prop),
      printPropDescription(prop)
    ].join(' | ')} |`);
  }
  
  return markdownPropsLines.join('\n');
}

function generateOnFilePath(filePath: string): void {
  const component = parser.parse(filePath)[0];
  
  if (component) {
    console.info(generateMarkdownTable(component)); // eslint-disable-line no-console
  } else {
    console.warn(`No component found at ${filePath}`); // eslint-disable-line no-console
  }
}

function readAndGenerate(options: ICommandArgs): void {  
  const entryFilePathTs = path.join(process.cwd(), '..', options.pkg, 'src/index.ts');
  const entryFilePathTsx = path.join(process.cwd(), '..', options.pkg, 'src/index.tsx');
  
  if (existsSync(entryFilePathTs)) {
    generateOnFilePath(entryFilePathTs);
    
    return;
  }
  
  if (existsSync(entryFilePathTsx)) {
    generateOnFilePath(entryFilePathTsx);
    
    return;
  }
  
  console.warn(`File ${entryFilePathTsx}? not found.`); // eslint-disable-line no-console  
}

/**
 * How to use (where `pkg` is the package directory): 
 * > ts-node-dev ./script/generate-md-api-pref.ts -p <pkg>
 */
program.requiredOption('-p, --pkg <pkg>').parse();

readAndGenerate(program.opts<ICommandArgs>());

加 script

shell 复制代码
npm pkg set scripts.generate:md-api="ts-node-dev script/generate-md-api-ref.ts -p"

以上命令等价于在 package.jsonscripts 下添加如下代码:

diff 复制代码
{
  ...
  "scripts": {
    ...
+    "generate:md-api": "ts-node-dev script/generate-md-api-ref.ts -p"
  }
}

生成表格

以下是利用上面的 script 命令生成对应 Markdown 表格代码片段:

拷贝到文档对应的地方,渲染效果如下:

搜索(本地搜索)

当有了内容,即使内容还不是很多,搜索就很有必要了。Docusaurus 提供了 4 种搜索选项

  1. 使用免费的 Algolia DocSearch 服务(Docusaurus 官方支持)
  2. 使用 Typesense,跟前者类似
  3. 使用本地搜素,有好些个 搜索插件
  4. 自己写 SearchBar 组件(难,不建议)

作为嫡系,Algolia 应该是最受欢迎的搜索方案,你可能已经在很多地方看到「Search by Algolia」 ,它能跟站点主题很好地融合。

但 Algolia 也好,Typesense 也好,由于它们是 SaaS,都要求文档站必须已经发布到线上,并且全球可见,详见 Algolia Checklist

对于小型项目,我更倾向于本地/离线搜索。经过试验,最终选择了 docusaurus-lunr-search

安装依赖

shell 复制代码
pnpm add docusaurus-lunr-search lunr lunr-languages
pnpm add -D @node-rs/jieba # 中文搜索需要

配置项

更新 docusaurus.config.ts

diff 复制代码
{
  plugins: [
+    ['docusaurus-lunr-search', {
+      languages: ['en', 'zh']
+    }]
  ]
}

本地开发不工作

你会发现本地开发的时候,搜索框一直处于「Loading」状态,点击也无效,这是因为这个插件无法在开发模式下使用,有个 Issue 记录 Lunr Search stuck on "Loading..." on front end,但是文档中没有相关的提示,这个 Issue 也没有关掉,不知道作者是不是有意向要修复它。

构建

执行 pnpm build 进行构建(首次运行时间会慢一些):

效果

执行 pnpm serve 后可以看效果,可以看到中英文都可以搜索:

🌻 实战总结

以上,我们以一个新手的视角,从零开始,用 Docusaurus 搭建了一个文档站的基本内容,并配设了文档站十分重要的搜索功能。但这并不是 Docusaurus 的全部,也不是文档站的全部,本文尚未覆盖的相关部分有:

  1. 部署
  2. SEO
  3. 国际化
  4. 多版本

📌️ 附录

有用的资源

文档,除了文字之外,图片装饰必不可少,这些网站可以收藏一下:

SSG 标识

自从开始研究文档框架,每打开一个文档站,我都会预判其 SSG 选型(同源的基因很容易看出来),然后在开发工具中找 HTML 上的标识以确定预判是否正确。以下是一些比较明显的标记:

  • body.astro-...
  • div#___gatsby
  • div#__docusaurus
  • div#__next
  • next-route-announcer
  • div#VPContent Vitepress

前端常用工具的文档站选型

以下基于 Docusaurus:

站点 分类 可参考
babel 编译
electron 前端 App 化 顶栏下拉菜单
format.js 国际化
gulp 构建
hooks-ts React Hook
immer immutable 辅助
jest 单元测试 多版本
lerna monorepo 工作流
npmpackagejsonlint 代码质量
playwright e2e 测试
pnpm 包管理
prettier 代码格式
react-live React Playground
redux-saga 状态管理 自定义主题切换按钮
redux-toolkit 状态管理 不一样的搜索
remirror 在线编辑
stylelint 代码质量
tauri@v1 前端 App 化 顶栏下拉菜单
testing-library 单元测试
verdaccio NPM 仓库 多版本、多语言

以下基于其他框架

站点 分类 使用框架
atlassian design 设计系统 Gatsby
biome 代码质量 Astro
commitlint Git 工作流 Vitepress
cypress e2e 测试 Next
eslint 代码质量
highlight.js 代码高亮 Next
husky Git 工作流 Vitepress
mermaid 文字转图表 Vitepress
parcel 构建
radashi 工具集 Astro
react Lib Next
rollup 构建 Vitepress
shiki 代码高亮 Vitepress
styled-components Css-in-JS Next
tauri 前端 App 化 Astro
typescript 编译 Gatsby
use-hooks React Hook Astro
vite 构建 Vitepress
vitest 单元测试 Vitepress
vue Lib Vitepress
webpack 构建
zustand 状态管理 Next

关于技术文档的一些文章

🙋 FAQ

❓ 为什么不选 Storybook?

首先要说明的是,个人非常喜欢 storybook,在我的项目中少不了它。

Storybook 非常受欢迎,它不仅仅是一个 UI 测试框架,甚至可以用它写文档,甚至写文档非常方便(支持 MDX,并且只需要一行配置就可以自动生成相关的文档)。

而且已经有不少团队已经在用 Storybook 辅助生成文档,一些例子:

我不选择它作为文档基座,有以下原因:

  1. Storybook 自己没用它来写文档
  2. Storybook 自动生成的文档比较生硬,不像自己写那样自然
  3. 没有全局搜索
  4. 左侧树状导航很紧凑,没有层次感,影响阅读
  5. 生成的文档默认没有 TOC,但应该可以加上,比如 circuit.sumup.com 有个比较丑的 TOC

❓ 如何显示代码行号?

Docusaurus 并没有提供全局开启代码行号的配置,但你可以针对特定的一块代码显示行号,给代码块加上 showLineNumbers 即可。

我认为这样的按需策略是正确的,毕竟多数情况下,两三行的代码,并不需要行号。

md 复制代码
\`\`\`type showLineNumbers
...code...
\`\`\`

❓ 如何增加代码高亮的语言种类?

Docusaurus 使用 Prism 对代码块进行高亮处理,并且内置了一些常用语言,但可能漏了你需要的,比如我们经常会在文档中写一些命令行,就没有高亮。

在配置项中,增加 prism.additionalLanguages

ts 复制代码
export default {
  // ...
  themeConfig: {
    // ...
    prism: {
      // ...
      additionalLanguages: ['bash']
    }
  }
};

注意,填 Prism 不支持的语言会导致运行时报错,从而白屏。更多内容可以看 Code Blocks - Supported Languages

相关推荐
潘小安2 小时前
『算法』图解数组排序全家桶 - 堆排序
算法·react.js·面试
fly啊7 小时前
app Router VS pages Router(Next.js学习笔记)
前端·javascript·react.js
吴楷鹏8 小时前
【Next.js】路由跳转显示进度条
前端·react.js·next.js
杨进军8 小时前
React 的 diff 策略是啥?
react.js
Dream耀9 小时前
前端实战:构建用户体验优秀的图片识别应用
前端·react.js·node.js
一只不会编程的猫10 小时前
Could not find a declaration file for module ‘..XX‘.
linux·前端·vue.js·前端框架·vue·es6
德育处主任Pro1 天前
Ant Design Charts入门教程
前端框架
鹏多多.1 天前
详解vue渲染函数render的使用
前端·javascript·vue.js·前端框架
德育处主任Pro1 天前
AntV L7入门教程
前端框架