笔记文档一把梭——MkDocs 快速上手指南

引言

在无纸化信息处理的流程中,信息输入、信息整理阶段的工具选择已经很多了,雨后春笋般不断浮现的笔记工具让人眼花缭乱。但到了信息输出和展示的环节——例如发表自己的读书心得、思考或是学习笔记,或是为自己的软件项目撰写文档——现有解决方案则总有明显的遗憾之处。

一方面,如果选择语雀、Notion 这样的第三方云端文档平台来存放,定制空间就很有限,且数据始终是寄人篱下。另一方面,如果选择 WordPress 这类 CMS 或近年流行的 JekyllHugo 或是 Hexo 等「静态网站生成器」,随之而来的技术门槛和复杂操作又容易打消人的创作和分享热情。

以我个人为例,我想通过搭建简单的静态网站来很好地展示我在学习编程过程中的所作的笔记、对问题的看法等,将它们作为一个 Cookbook 或系统化的参考文档来使用,同时也兼具美观和可读性。对此,上述主流框架都有些「杀鸡用牛刀」的感觉,那么,有没有更好的方式呢?

我遇见的答案是 MkDocs

什么是 MkDocs?

MkDocsMarkdown Documents)是一个快速、简单的静态网站生成器,用于将 Markdown 文档组织起来构建成有层次、美观的文档站点。

MkDocs 基于 Python 编写,也贯彻了 Python 里「简洁胜于复杂」的理念,与其他常见的静态网站生成器相比,无需繁琐的环境配置(如 Jekyll 涉及的 Bundler、Gems 等),所有配置都用只有简单的一个配置文件管理,并且当中配置项的内容也仅有一页文档。

MkDocs 见名知意就是为 Markdown 而生,根据 Markdown 格式将内容渲染成美观的文档,并通过目录层级关系对应文档的树状结构,使得当中所有的文档组织分明且层次递进。

此外,简洁不等同于简单,MkDocs 还为「动手能力强」的玩家们提供了扩展的空间:我们可以进一步让更多语法特性体现在文档内容中,如为 Markdown 增加列表语法等;我们也可以对 MkDocs 构建的站点的布局样式进行二次定制和编排,使得整个站点更加美观。

MkDocs 的上述优点,让它被很多知名开源项目选中,用于搭建和项目相关的文档网站。比如 Python 里知名的 Web 圈里的 django-rest-frameworkFastAPI 以及基于 Go 编写的云网关代理服务器 traefik 等项目的官方文档站点,都是通过 MkDocs 进行搭建。

当然,MkDocs 并不仅仅是开发者的玩物。由于文档的本质就是一组结构化、体系化的说明文本,任何具有这种特征的内容——例如就特定话题所做的笔记、Wiki 等——只要是使用了 Markdown 格式记录,那么用 MkDocs 来搭建一个自用的 Cookbook 或参考手册也完全不在话下。

因此,如果你和我一样是个 Markdown 爱好者并使用 Markdown 来写作或记录,并且不想花费过多时间和精力在琐碎的站点设计上,那么 MkDocs 是个不错的选择。

注:尽管 MkDocs 没有提供汉化翻译,但它的文档内容足够简洁明了,所以即使读者对自己的英语水平不太有信心,也可以在翻译软件的和示例项目的帮助下,快速上手并构建一个站点。

快速上手

下面,我们来尝试从无到有用 MkDocs 制作一个网站,顺便对它的轻量快捷建立一个直观认识。

首先,请确保自己安装了 Python3,然后通过命令 pip install mkdocs 安装 MkDocs。然后,按照下列步骤新建站点:

  1. 使用 mkdocs new <your-site-project-name> 新建站点项目,比如我这里叫 mysite,则是 mkdocs new mysite
  2. 切换到 mysite 目录下,并使用 mkdocs serve 在本地运行。

第三步……没有第三步了。现在打开浏览器,输入 localhost:8000,就能预览到 MkDocs 帮我们搭建好的文档站点了。

上面用到的是官方快速新建的示例进行演示,但假如我们已经有了项目目录,那么也无需从头再去创建站点。

对于这种情况,直接在原有目录下新建一个 docs 文件夹并包含一个名为 index.md 的 Markdown 文件;同时在原有目录下再新建一个名为 mkdocs.yml 的文件并填写基本信息之后,就能直接通过 mkdocs serve 看到效果了。例如:

my-exist-projct

├── docs

│   ├── index.md

│   ├── notes

│   │   ├── mkdocs.md

│   │   └── python.md

│   └── sspai

└── mkdocs.yml

大部分使用 MkDocs 搭建的文档站点,都有着和上述示例类似的目录结构:

  • docs 文件夹就是用于存放我们所有的 Markdown 格式的文档内容,当然我们也可根据自己的需要进一步用文件夹归类划分,这也是 MkDocs 运行时默认的根路径。我们也可以后续手动指定修改成其他目录。
  • mkdocs.yml 该文件主要用于 MkDocs 的定制化配置的设置与管理。
  • docs/index.md 该文档内容是必须要有的,它是整个 MkDocs 构建的首页入口

因为 MkDocs 只会对 Markdown 文件进行处理,因此我们除了需要将自己电子化的记录或者笔记放到 docs 这一目录之外,还需要确保这些文档文件的扩展名为 .md 的 Markdown 格式。

确保结构、格式没有问题后,我们就可以开始编写相关的配置文件了,因为只有编写了配置文件才能让 MkDocs 更好地符合我们的需要去构建站点。

插曲:YAML 配置文件格式速成

稍等!虽然我们准备好了相关 Markdown 的文档内容,但想要 MkDocs 运行起来还需要额外的一个步骤,那就是编写关于 MkDocs 的配置文件。

和前面提到的其他几个框架一样,MkDocs 也存在许多可选配置,需要通过 mkdocs.yml 这一个 YAML 文件来进行设置和管理。因此我们需要简单了解一下 YAML 以及如何编写 YAML 文件。

YAML 是 YAML Ain’t Markup Language 的递归缩略语,也是一种人类可读(human-readable) 的数据序列化语言,其文件扩展名为 .yml.yaml(官方推荐)。常被用作和 JSON 格式文件一样编写配置或存储相关数据内容。

YAML 语法的官方文档长达八十多页(丧心病狂),从一个侧面反映了其支持的语法特性有多么丰富。不过,虽然文档长度很唬人,但「二八定律」仍然适用:其中 80% 的语法都是比较简单也被经常使用,另外 20% 的语法并不常用,需要我们额外查阅文档。不仅如此,由于 MkDocs 本身配置项就不多,因此我们在编写 mkdocs.yml时,真正用到的 YAML 语法可能不到 10%。

这样一来,我们只需要谨记以下三个核心元素的用法,就能应付大多数 YAML 配置需求,包括本文的主角 MkDocs:

  1. 键值对(Key-Value Pair)
  2. 缩进(indent)
  3. 同一层级下不能有重名键

键值对

没有编程经验的朋友可能觉得键值是什么抽象难懂的概念,这其实理解为「标记与内容的对应形式」即可。

键值对在 YAML 中的形式如下:

name: 100gle

其中的标记(键)就是上面的 name,在现实生活中可以表现为我们很多属性的代称,比如性别、年龄、职位等等;而内容(值)就是这些代称下的具体详情,比如男、3 岁、少数派鸽子等等。这在 YAML 中通过一个 : 英文冒号将二者联系起来了,这等价于编程语言里的 = 等于号。

缩进

YAML 用 空格 缩进来表示层级关系,这区别于另一种流行配置格式 JSON 使用的花括号 {},可能是受到了 Python 的影响。

family:

  father:

    name: foo

    age: 100

  child:

    name: baz

    age: 3

需要注意的是,空格(ASCII 码值为 32)有的时候会和 Tab 键产生的制表符(ASCII 码值为 9)混在一起,二者单从外观上来看无法分辨,因此为避免 YAML 解析错误,我们在编写时要遵循 YAML 官方缩进的要求使用 两个半角空格

同一层级下不能有同名键

了解键值对和缩进值后,我们还需要注意的一点就是 YAML 不允许在同一层级下出现相同的键,例如:

father:

  name: foo

  age: 100



family:

  father:

      name: foo

      age: 100

  child:

      name: baz

      age: 3

  father: foo

在这个例子中 father 和两个 family/father 键(行文方便起见,我这里会以文件路径的形式表现层级关系)虽然同名,但因为它们并不在一个层级中,因此并不会产生冲突。

但是在 family 中,第一个 family/father 会和第三个 family/father 产生冲突,因为它们同级且同名

这就好比在同一个班级里有两个同名的张三,同学们如果只是看名字肯定都没办法分辨得出提到的是哪个张三,更别说程序和计算机了。

在 mkdocs.yml 中编写配置

了解了 YAML 的简单用法之后,我们就可以编写 mkdocs.yml 文件了。在编写的过程中我们会碰到字符串和列表的表达语法,但我并不打算在这里再继续展开,因为它们十分简单,一看便能知道。

关于 MkDocs 支持的所有配置你都能在 官方文档 中找到,但当中的配置我们只需要填写关键的几个即可。

添加站点信息

最开始在 mkdocs.yml 文件中填写的主要还是关于站点相关的信息。其中我们可以填写的有那么几个:

  • site_name:站点名称
  • site_url:站点 URL 链接
  • site_author:站点作者
  • site_description:站点描述
  • copyright:版权信息
  • repo_url:站点仓库 URL

其中,只有两个必须要填写的配置项就是 site_name 站点名称和 site_url 站点 URL 链接。例如:

site_name: "My Notes"

site_url: "http://<domain>:<port>/<project>"

但如果我们使用的是,mkdocs serve 来本地运行,那么无需填写 site_url,因为 MkDocs 会自动将其挂在本地的 http://127.0.0.1:8000/<your-site-project>

除此之外的选项都是选填,如果填写了则 MkDocs 会将其作为元数据或元素渲染最后的 HTML 中。

添加文档路径

既然我们想要搭建的是一个文档站点,那么文档内容才是关键,除了对内容进行必要的排版校对之外,如何组织文档或是让文档以清晰有层次的结构展示也是必不可少的。

MkDocs 提供了让使用者能够自由安排文档层级结构或目录编排的设置,这将决定最后文档在站点中的编排效果如何。这里主要是通过 nav 项来进行配置:

nav:

  - "index.md"

  - user-guide:

      - quickstart: "user-guide/quickstart.md"

      - usage: "user-guide/how-to-use-MkDocs.md"

  - "about.md"

  - "license.md"

在该设置项中,使用了 YAML 所支持的列表语法,这和 Markdown 的无序列表语法是完全一致。默认情况下如果不指定键名,MkDocs 会自动根据列举的 Markdown 文件名字符串来确定最终该页面的名称如何;如果我们手动指定了键名,那么会优先以我们的键名为主,比如这里的 user-guide/usage 这一部分。

需要注意的是,前面对于 MkDocs 的快速介绍中我就有提到,MkDocs 默认会将项目目录下的 docs 目录作为默认的根路径,所以在 nav 设置中我们如果指定的是该目录下的其他内容,那么就是只需要填写相对路径

这也是为什么在上述例子中的 user-guide 这一部分下的文档路径都不需要写成 docs/user-guide/XXXX.md 的原因。

同时,最终 MkDocs 也会根据 nav 配置的层级关系将内容的层级关系或包含关系一同渲染。

当然,如果你不想使用 MkDocs 的默认目录,那么你也可以通过修改 docs_dir 选项来指定对应的文件夹名称。但通常情况我们都不需要进行修改就已经能满足我们的基本需要了。

用扩展和主题武装 MkDocs

基本上我们只需要将站点信息和文档路径在 mkdocs.yml 文件中填写好就已经可以运行 MkDocs 了。不过 MkDocs 为使用者预留了可扩展的配置,方便使用者去添加其他开发者所开发的主题、扩展等功能。

Markdown 扩展

MkDocs 使用 Python-Markdown 库(Markdown 规范的 Python 实现)来渲染 Markdown 内容,因此 MkDocs 中对于 Markdown 内容渲染的扩展也是来自于此。我们可以在 Python-Markdown 的 官方文档 中浏览到目前所支持的 Markdown 扩展有哪些。

比如我在当中开启对于 Markdown 内容标题的固定标识符、脚注以及表格:

markdown_extensions:

  - toc:

      permalink: True

  - footnotes

  - tables

除此之外,Python-Markdown 还支持一些来自于第三方的 Markdown 扩展,你能在 Python-Markdown 的 Github 仓库中的 Wiki 页面找到符合你需要的扩展,比如支持数学公式、emoji 表情、增强 Markdown 语法等。

但这些第三方扩展都还是需要通过 pip工具(安装 Python 解释器时会自带)来进行安装,安装之后在 mkdocs.yml中的markdown_extension部分继续追加。

这里我下载了一个名为 markdown-checklist的扩展,使我的 Markdown 文档内容支持清单列表的预览样式。

pip install markdown-checklist

安装完成后将其添加到 markdown_extension 中,注意,这里是需要安装插件对应文档的名称来填写包含相应的 makeExtension 属性的模块或继承了 Markdown 库中 Extension 的类,所以我就填入了 markdown_checklist.extension 这一模块:

markdown_extensions:

  - toc:

      permalink: True

  - footnotes

  - tables

  - markdown_checklist.extension

最后运行 MkDocs 后就可以看到其对应的效果了。

主题样式美化

如果不追求惊艳的视觉效果,MkDocs 默认的主题已经相当简约美观了。

不过,如果你有一定的 HTML、CSS 以及 JavaScript 基础,对 MkDocs 默认的主题样式存在某些想要定制化的部分,那么你可以通过设置 extra_templatesextra_cssextra_javascript 三个选项来进行局部调整。哪怕你是想从头到尾自己定制一套主题样式,那么 MkDocs 也能如你所愿,还有 专门的文档 来指导你如何去操作。

当然,更多时候我们会希望直接套用别人定制好的主题。对此,你同样可以在 MkDocs 的 Github 仓库中的 Wiki 页面中找到你心仪的主题样式。

无论是自己定制还是使用第三方的主题样式,想要主题样式生效,最后都需要通过配置文件中的 theme 项来进行配置。

比如我这里使用了目前使用度最广泛的 MkDocs 第三方主题样式 Material for MkDocs,使得 MkDocs 能够以 Google 推行的 Material Design 风格呈现。

但因为该主题属于第三方主题样式,我们需要使用前面安装 MkDocs 使用的 pip 工具进行安装:

pip install mkdocs-material

安装完成后,我们只需要在 mkdocs.yml 文件中进行配置即可:

theme:

  name: material

之后我们就能得到一个 Material Design 风格的 MkDocs 站点了,这个站点甚至还有自适应的暗黑模式(Dark Mode)。

站点生成与部署

如果你只是临时用于离线浏览文档,那么使用 mkdocs serve 命令其实足矣;但如果你是打算作为个人站点或主页部署到云服务器,那么你就需要将整个文档项目编译打包,最后放到对应的服务器上部署。

这里 MkDocs 给我们提供了两个选项:一个是使用 mkdocs build 命令将项目打包之后,再由个人将打包文件上传至服务器对应的目录下进行部署;另外一个就是如果你有打算使用 Github Pages 来作为展示的站点(参见 GitHub Pages 搭建教程),那么我们就只需要使用 mkdocs gh-deploy 这一条命令就可以帮我们完成部署任务。

该命令的过程主要是先将编译打包好的相关资源文件(如 HTML 页面、样式、静态资源图片、用于交互的 JavaScript 等)上传至对应个人 Github 用户名的 Github Pages 仓库中,然后再利用 GitHub Actions 来保持站点活跃。

这样我们每次只需要在本地更新内容之后,通过该命令进行提交、部署,就可以实现文档内容的更新。

注:由于 Github Actions 运作需要一定时间(大约在几分钟到十分钟不等),因此部署完成后你需要等待一会儿才能看到 Github Pages 生效,否则会出现 404 错误。

结语

对程序员来说,大部分时间都花在了写文档和 Debug 上。Bug 可以通过相应工具来慢慢排查并通过少量代码去解决,但除了自动生成的文档之外,大部分情况下都必须要人为撰写。可内容写出来是一回事,展示得是否妥当又是另外一回事。

使用 MkDocs 就像 Markdown 一样,一定程度上能够减少使用者内容排版或展示的样式细节的顾虑,而让使用者能更加专注于记录或写作的内容本身。

关于 MkDocs 的更多内容本文并没有进一步展开,但如果你有兴趣或有需要,可以到 MkDocs 的 Github Wiki 页面进一步探索 MkDocs 生态。