入坑 Docusaurus,看这一篇就够了

2,472 阅读37分钟

头图

文章的 TOC 太多了,看起来不方便?PC 端可以看看我的 《两个 TamperMonkey 脚本,解决掘金阅读体验的三点不爽》,可以解决一些掘金阅读体验问题。

我写的「入坑」系列文章:

  1. 《入坑 Mac,看这一篇就够了》
  2. 《入坑 iTerm + OMZ,看这一篇就够了》
  3. 《入坑 Firefox Developer Edition 及 Mobile 版,看这一篇就够了》
  4. 《入坑 WebStorm,看这一篇就够了》
  5. 《入坑 VSCode,看这一篇就够了》
  6. 《入坑 Vim,看这一篇就够了》
  7. 《入坑 Git,看这一篇就够了》
  8. 《入坑 Docusaurus,看这一篇就够了》← 本文
  9. 《入坑 Nginx,看这一篇就够了》
  10. 《入坑 UniApp 写小程序,一些感受》

🎙️ 前言

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

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

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

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

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

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

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

文档框架往往用 Content-Driven、Jamstack Framework、Static Site Generator (SSG) 等词来描述自己,其中以 SSG 居多。

TL;DR

使用官方 V3 Demo,根据本文实战生成的模板代码 template-docusaurus。

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

选择 Docusaurus 的 n 个理由:

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

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

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

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

主要内容

适合读者

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

你将获得

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

编辑历史

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

🤔 为何需要 SSG

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

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

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

🧐 如何挑选 SSG

0 - 候选列表

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

名称自述TSMDXReactVue
astroThe web framework for content-driven websites✅✅✅✅
docsifyA magical documentation site generator✅
docusaurusBuild optimized websites quickly, focus on your content✅✅✅
doczIt's never been easier to document your things!✅✅✅
dumi为组件研发而生的静态站点框架✅✅✅✅
eleventyEleventy is a simpler static site generator✅✅✅
gatsbyGatsby is a React-based open source framework for creating websites.✅✅✅
gitbookBuild docs as a product, not an afterthought✅
gridsomeA Jamstack framework for Vue.js✅
hugoThe world’s fastest framework for building websitesGo
jekyllTransform your plain text into static websites and blogsRuby
nextThe React Framework for the Web✅✅✅
nuxtThe Intuitive Vue Framework✅✅
pelicanStatic site generator powered by PythonPython
rspressLightning Fast Static Site Generator✅✅✅
vitdocA new way to write Component Usage✅✅
vitepressVite & Vue Powered Static Site Generator✅✅✅
vuepressVue-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数量
astroastro.build/showcase1200+
docsifydocsify.js.org/#/awesome?i…130+,但相当一部分已经无效
docusaurusdocusaurus.io/showcase270+
gatsbywww.gatsbyjs.com/showcase600+
gitbookwww.gitbook.com/customer-sh…100+
gridsome收集中...
jekylljekyllrb.com/showcase/47+
nextnextjs.org/showcase120+
nuxtnuxt.com/showcase100+

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:非必需

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

名称TSMarkdownTOCFrontMatter代码高亮自动外链MDX框架黑/白搜索国际化多版本RTL
astro✅✅✅✅✅❌✅React✅✅✅❓
docsify❌✅左侧✅✅✅VueVue✅✅✅❌
docusaurus✅✅✅✅✅✅✅React✅✅✅✅
docz✅✅左侧✅✅✅✅React❌❌❌❌
dumi✅✅左侧✅✅❌✅React✅✅✅✅✅
eleventy✅✅顶部✅✅❌✅React❌✅✅✅
gatsby✅✅✅✅✅❌✅React❌✅✅✅
gitbook✅✅✅✅✅❌❌❓✅✅ 很慢❌❌
gridsome❌✅✅✅✅✅❓Vue❌✅❌❌
hugoGo✅✅✅✅❌❌❓✅✅✅❌
jekyllRuby✅❌✅✅❌❌❓❌✅❌❌
next✅✅✅✅✅✅✅React✅ 底部✅✅❌
nuxt✅✅✅✅✅❌VueVue✅✅✅❌
pelicanPython✅✅❌✅❌❌❓✅✅✅✅
rspress✅✅✅✅✅✅✅React✅✅✅❌
vitdoc✅✅✅✅✅✅❓React✅✅✅❌
vitepress✅✅✅✅✅✅VueVue✅✅✅✅
vuepress✅✅✅✅✅✅VueVue✅✅✅✅

4 - 客观数据

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

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

名称NPM 周下载Github StarOpen Issue版本数第一版上次更新
astro github npm365k49.7k14811702021/032025/03
docsify github npm43.7k29k1431932016/112023/06
docusaurus github npm387.6k58.5k28120272017/062025/01
docz github npm3.5k23.7k1082682018/042022/02 💥 官宣停更
dumi github npm40.2k3.7k1303662019/122025/02
eleventy github npm70k17.7k3961932018/012024/10
gatsby github npm219.5k55.8k21029792015/052024/12
gitbook github npm3.4k27.6k42212015/022017/07
gridsome github npm1.4k8.6k540752018/092020/11
hugo github npm-78.7k4543162013/062025/02
next github npm7559.5k130k2.6k30292011/072025/03
nuxt github npm666.3k56.5k8693492016/?2025/03
rspress github npm6.4k1.6k736752022/102025/03
vitdoc github npm408311402022/082025/03
vitepress github npm114.6k14.1k3362512020/052025/01
vuepress github npm36.4k22.7k5412492018/042023/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
    • 虽然我是 husky、lint-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 I:准备

创建项目

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

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

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

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

初始化项目

如果你的项目还不是 Monorepo,推荐使用 pnpm,在项目根目录下新增 pnpm-workspace.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. react、react-dom 的版本是 19,视情况看要不要降级,我目前的代码尚未适配 19,于是先降级成 18
    • "react": "^19.0.0" → "react": "^18.3.1"
    • "react-dom": "^19.0.0" → "react-dom": "^18.3.1"
  4. 安装其他基础依赖
    • pnpm add -D @types/react@18.3.18 @types/react-dom@18.3.5 估计是他们漏了
    • pnpm add styled-components 样式解决方案
  5. 使用 ncu 更新版本,执行 ncu -ui 进行升级(忽略 react)

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

启动项目

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

- "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 目录的作用
名称、SloganuseDocusaurusContext().siteConfig 能够在组件中访问 docusaurus.config.ts 的内容
GitHub 等相关链接配置项 organizationName、projectName 的作用
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。

pnpm add -D docusaurus-plugin-sass sass

更新 docusaurus.config.ts:

plugins: [
  ...
+  'docusaurus-plugin-sass'
]

自定义 CSS Var

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

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

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 样式自定义,去掉 code 边框背景
        './src/css/custom-live-editor.scss' // live editor 插件样式自定义
      ]
    }],
    // ...
  ]
};

对应文件结构:

配置顶栏

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

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

顶栏左侧导航不必太多,实在可能很多的话,可以配置 下拉菜单。

配置侧边栏

侧边栏的配置在 sidebars.ts,默认的核心内容如下:

tutorialSidebar: [{
  type: 'autogenerated',
  dirName: '.'  
}]

生成的模板中,文件与文档的对应关系如下图:

可以想见,凡是丢在 docs 目录下的合法文件,都会自动在 Sidebar 展示,若文档越来越多,读者可能会迷失在一个很大的侧边栏里。

我们可能希望文档可以分「栏目」,每个栏目有各自的侧边栏,比如需要「开发指南」、「组件」、「API」,而这些栏目,可以入顶栏。可以调整 sidebars.ts 的内容(其中的 type: 'autogenerated' 很关键):

import {
  SidebarsConfig
} from '@docusaurus/plugin-content-docs';

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

以上,我故意没有删除官方模板生成的教程部分,而是将它们移入了 docs/tutor 下。

需要注意的是,配置中的 key 和 dirName 并不需要保持一致,但 key 有可能被配置项 themeConfig.navbar.items 的 sidebarId 引用。

docs 文件目录组织如下:

docs/
├── api/
├── component/
├── component/
└── tutor/

从上面这个图,我们可以看到文档的顺序和与文件的顺序无法从物理上进行对应,生成的模板里,都是在 Front Matter 定义 sidebar_position 的,但我更喜欢通过 文件名数字前缀 的方式,保证文件顺序和视图顺序一致,并且这些前缀数字并不会体现在 URL 中,完美。

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

调整 docs/turor 下所有的文件命名:

扩展 Markdown / MDX

Markdown 是我们写文档的首选方式。Docusaurus 除了 CommonMark 和 GFM 外,还内置了文档必不可少的扩展,并且支持 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 的样式,一如既往地不够精致,你可以看到前面我加了自定义样式,代码也很简单:

.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 指令:

npm run swizzle @docusaurus/theme-live-codeblock ReactLiveScope -- --eject

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

import React from 'react';
+import {
+  InputSwitch
+} from 'kcuf-ui';

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

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

## Live Demo

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

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

Mermaid 图表

文档中除了截图之外,更多的可能是结构化的图表。Docusaurus 官方支持了大名鼎鼎的 Mermaid,按 官方文档 - Diagrams 设置即可。

目前支持的 Mermaid 版本为最新的 11,支持包含 流程图、类图、状态图、思维导图、甘特图、时序图 等 20 多种图表类型。

作为一个有节操的程序员,能用代码解决的问题,千万不要用图,所以,不管你对 Mermaid 熟悉与否,都建议配上。

添加自定义组件

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

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

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 II:写

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

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

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

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

何时开始

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

你可能会认为「写代码的时间都不够,哪有时间写文档」,这就跟「哪有时间写测试」一样。你应该听说过 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 并不会被生成到文档中。

API 自动生成

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

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

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

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

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

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

安装依赖

在 documentation 下安装:

pnpm add -D react-docgen-typescript commander ts-node-dev

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

写脚本

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

  1. 项目结构仅符合我的项目,若要符合你自己的项目,需调整路径
  2. 我加了自定义组件 TagRequired、TagDefault,需要在 src/theme/MDXComponents.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

npm pkg set scripts.generate:md-api="ts-node-dev script/generate-md-api-ref.ts -p"

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

{
  ...
  "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。

安装依赖

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

配置项

更新 docusaurus.config.ts:

{
  plugins: [
+    ['docusaurus-lunr-search', {
+      languages: ['en', 'zh']
+    }]
  ]
}

本地开发不工作

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

构建

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

效果

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

🍇 实战 Part III:部署

你有实在太多种部署方式,见官文 Deployment,package.json 里,Docusaurus 生成了 deploy 命令,但我没用过。

验证构建

首先,你需要验证构建结果:

pnpm build # 打 build 包
pnpm serve # 本地验证

临时分享

最开始,我以最最最原始的方式进行了部署——部署在自己的开发电脑上,然后把自己的 IP 加端口发给别人,这适合在同一个局域网内临时分享。

久而久之,发现这不行,必须得有个域名(或者一个 IP 稳定的服务器)。

部署到 Nginx

以下是我目前的部署方式。

假设以下条件:

  1. 恰好有台服务器
  2. 服务器上恰好有 Nginx
  3. 恰好申请了一个域名,比如 doc.my-site.net

那么可以这样(这里假设你已经 SSH 登录到了你的服务器)。

第一步,在 Nginx 配置项(一般目录为 /etc/nginx/conf.d),添加新的文件 documentation.conf,内容如下:

server {
    listen 80;
    server_name doc.my-site.net;
    root /var/www/documentation; # Docusaurus 构建目录
    
    # 处理根路径请求
    location / {
        try_files $uri $uri/ /index.html; # 支持单页面应用路由
    }
    
    # 静态资源缓存配置(可选但推荐)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|txt|map|json)$ {
        expires 30d;
        add_header Cache-Control "public";
        access_log off;
    }
    
    # 错误页面配置
    error_page 404 /404.html;
    location = /404.html {
        internal;
    }
}

第二步,将构建结果 build 目录下的文件,上传到目标服务器的 /var/www/documentation 目录下(这里,推荐 Transmit 这个颜值与性能并存的 SFTP 上传工具)。

第三步,重启 Nginx,后续更新只需要上传即可。

🌻 实战总结

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

  1. SEO
  2. 国际化
  3. 多版本

🎯 最佳实践

  1. 使用 Husky + LintStaged + MarkdownLint 规范 Markdown 格式
  2. 尽可能每个目录有个 index,或者在 _category_.yaml|json 中设置 link.type 为 generated-index
  3. _category_.yaml|json,JSON 或 YAML 仅取其一,并设置 link.slug
  4. 文档 FrontMatter 填写 title 和 description,以保证在 generated-index 的展示良好
  5. 文档 TOC 从二级标题 ## 开始
  6. 为每个 TOC 添加英文锚点,如 ## 项目名称 {#project-name}
  7. 图片尽可能就近放在当前目录的 img 子目录下,只有全局通用的放在 static/img 下

😳 CSS-in-JS 的问题

一切看起来都还不错,直到构建之后,首页的样式在初次打开或刷新后会丢失,但点到别的页面再回来就会有,像这样:

看了一下是 styled-components 的样式在 SSR 生成的 HTML 中找不到。

这个问题目前我只解决了一半,能够在初次打开时显示没有问题,但会先无样式,界面有抖动,体验不太好。

添加或编辑 babel.config.js,相关内容如下(需要先 pnpm add -D babel-plugin-styled-components):

export default {
  presets: ['@docusaurus/babel/preset'],
  plugins: [
    ['babel-plugin-styled-components', {
      ssr: true,
      displayName: true,
      fileName: false // 避免开发环境类名带文件名、生产环境不带的问题,确保 SSR 样式匹配
    }]
  ]
};

要做到刷新后无抖动,应该还需要修改 docusaurus.config.ts,但我试了无果(有成功的望不吝赐教)。关于这个问题,Docusaurus 的开发人员明确说了他们并未支持,而且极大可能不作为...

📌️ 附录

有用的资源和网站

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

  • favicon.io 快速简单地生成 FavIcon
  • SVG Stack 可免费下载 SVG 图标,还能编辑后下载
  • SVG Repo 免费的装饰性多色 SVG 图标
  • unDraw 免费的装饰性 SVG 插图
  • Web SVG 很多好看的 SVG 装饰图
  • Flat Icon 图标和装饰图,SVG 要收费,但 PNG 免费
  • SVGOMG SVG 优化

SSG 标识

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

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

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

以下基于 Docusaurus:

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

以下基于其他框架

站点分类使用框架
atlassian design设计系统Gatsby
biome代码质量Astro
commitlintGit 工作流Vitepress
cypresse2e 测试Next
eslint代码质量?
highlight.js代码高亮Next
huskyGit 工作流Vitepress
mermaid文字转图表Vitepress
npm docsNPMGatsby
parcel构建?
radashi工具集Astro
reactLibNext
rollup构建Vitepress
shiki代码高亮Vitepress
styled-componentsCss-in-JSNext
tauri前端 App 化Astro
typescript编译Gatsby
use-hooksReact HookAstro
vite构建Vitepress
vitest单元测试Vitepress
vueLibVitepress
webpack构建?
zustand状态管理Next

关于技术文档的一些文章

🙋 FAQ

❓ 为什么不选 Storybook?

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

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

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

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

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

❓ 如何显示代码行号?

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

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

\`\`\`type showLineNumbers
...code...
\`\`\`

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

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

在配置项中,增加 prism.additionalLanguages:

export default {
  // ...
  themeConfig: {
    // ...
    prism: {
      // ...
      additionalLanguages: ['bash']
    }
  }
};

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

❓ 如何 include Markdown/MDX 代码片段?

比你想象的简单,但仅支持在 MDX 中引别处的 Markdown 或 MDX,只需要 import,然后把它当成 React 组件进行渲染即可:

---
sidebar_position: 100
title: 变更历史
---

import Changelog from '../../../CHANGELOG.md';

<Changelog />

被 import 的文件,甚至不必须在文档项目目录下,比如以上的 CHANGELOG.md,其实位于 Monorepo 根目录下。

❓ TOC 及章节标题的锚点 ID 是否可自定义?

Docusaurus 会根据章节标题自动生成锚点 ID,点击后会在浏览器地址栏看到,这在中文场景下会比较丑,如下:

另外,自动生成的锚点 ID 会根据标题变化,如果你引用的锚点的标题改了,就可能导致 Broken Anchor 的问题。

如果你对以上两个问题都比较在意的话,可以为标题加上自定义锚点 ID,像这样:

-### Pnpm - 包管理工具
+### Pnpm - 包管理工具 {#pnpm}

以上 {#pnpm} 前的空格不是必需,但为了好看,建议加上。

❓ TOC 只展示到 H3 级别?

Docusaurus 默认支持 H2-H3 的标题入 TOC,有两个方法可以调整:

修改 docusaurus.config.ts 全局设置,见文档 Themes - Configuration - Table of Contents:

export default {
  themeConfig: {
+    tableOfContents: {
+      maxHeadingLevel: 4
+    }
  }
};

一般不建议太深的 TOC,H2-H4 三层差不多够了。

如果只是想临时提升一下,可以在文档或 Blog 的 FrontMatter 中设置 toc_max_heading_level,见 plugin-content-docs 和 plugin-content-blog。

---  
title: 文档标题
toc_max_heading_level: 5
---

❓ TOC 为什么不从 H1 开始,而是 H2?

Markdown 的规范 MD025 - Single H1,建议全局只一个 H1,即文档标题,而所有的子标题都有 H2 开始。

❓ 构建后 Live Editor 不工作?

开发阶段好好的,但 pnpm build && pnpm serve 就发现,所有的 Live Editor 都不工作了,浏览器 Console 下也没有任何报错信息,只有渲染区域说「SyntaxError」,像这样:

问题的原因在 src/theme/ReactLiveScope/index.ts,把 import * as React from 'react';,改成 import React from 'react';。

如果改完后,ESLint 报错「No default export found in imported module "react".」,可以忽略掉(Swizzle 生成的代码是 import React from 'react';,估计我也是看到了这个报错才改成 * as 的吧)。