使用 Docsify、Lefthook 等工具维护开源软件文档
“程序的好坏取决于其文档的质量。”——乔·阿姆斯特朗,Erlang编程语言的作者
优秀的开源项目应该具备哪些要素?如果你熟悉 Ruby,可以访问Gem Check(由我本人创建)查看所有最佳实践。但无论使用何种语言或技术栈,对于所有开源项目而言,有一点至关重要:文档。
所有开源项目,无论规模大小,都必须编写文档(即使是现在已被弃用的、臭名昭著的left-pad也需要文档)。在大多数情况下,一份精心编写的 README 文件就足够了,但你还可以更进一步,创建一个“超棒的 README”。
然而,随着项目规模的扩大,README 文件支持的文档不再适用。我们可能会说“
铁路README 文件无法扩展!我们说得没错。
这就引出了一个问题:“还有什么比单个 Markdown 页面更具扩展性呢?”
本文将分享我对这个问题的回答。
起初,一片混沌。
在我们开始讨论docsify之前,让我先向您展示一些我在最终选择 docsify 之前使用过的其他工具。
GitHub Wiki
我第一个超出 README 文件容量的项目是AnyCable。我没怎么考虑就把一些文档移到了 GitHub 内置的 wiki 里(旧版本的 gem 文档仍然保留在那里)。既然工具已经存在了——这样做当然没问题,对吧?嗯,也不一定。
事实证明,GitHub wiki 内置是唯一的优点,而缺点却数不胜数:
- 独立更新代码和文档很可能导致不一致。
- Web 编辑器还有很多不足之处;例如,您无法轻松上传图像(从技术上讲是可以的,但这需要克隆 wiki 存储库)。
- 只需更改标题,交叉引用就很容易失效;没有办法设置永久 URL(或者我遗漏了什么)。
docs文件夹
解决 Wiki 所有缺陷(明白我的意思吗?)的一个方法是docs在仓库中创建一个文件夹,并将 Markdown 文件放入其中。GitHub 会.md在浏览器中打开文件时自动显示格式。而且你还可以直接在网页界面编辑内容!既然如此,为什么还要使用 Wiki 功能呢?
所以,我们找到了一种存储文档内容的好方法。但是用户界面/用户体验呢?我们难道不想让文档更易于使用吗(例如,添加搜索功能)?是的,我们想。
让我们把GitHub上的文档转换成Web格式。
Jekyll 和 GitHub Pages
GitHub只需点击几下即可帮助您设置由Jekylldocs支持的文档网站:转到“设置”->“GitHub Pages”,选择网站的来源(我们使用文件夹),然后选择您喜欢的 Jekyll 主题。
现在访问设置页面中的 URL,即可找到您的全新文档网站!
遗憾的是,GitHub Pages 与 Jekyll 的集成功能有限,尤其是在插件方面。它能实现的功能非常有限。而且,在我看来,如果你想自定义页面外观或添加一些交互功能,Jekyll 就显得过于复杂了。
我们来看看现代工具。
恐龙纪录片
我尝试过的第一个现代文档生成器是Docusaurus。我们用它构建了Clowne的 gem 文档。
但我不得不承认,这段经历并不愉快:
- 鉴于该库是用 React 构建的,我原本期望它能提供更直接的自定义选项。然而,你实际上无法访问其内部机制,而且库的作者也不希望你进行深入的调整。
- 我第一次使用它的时候,它还不支持实时重新加载,这对于本地开发至关重要(现在看来这个问题已经解决了)。
- 它需要一个单独的构建步骤(
yarn run build)。
所以我开始寻找其他选择,然后发现了docsify。
文档化
与 Jekyll 或 Docusaurus 相比,Docsify 使用不同的方法来“生成”网站:它动态渲染 markdown 文件,不需要构建阶段。
要将您的文档“文档化” docs,您需要执行以下操作:
- 添加
docs/.nojekyll文件以禁用 Jekyll。 - 添加
index.html加载和配置 docsify:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="//unpkg.com/docsify/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
loadSidebar: true,
subMaxLevel: 2,
repo: 'palkan/docs-example',
basePath: '/docs-example/',
auto2top: true,
homepage: 'https://raw.githubusercontent.com/palkan/docs-example/master/README.md'
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-ruby.min.js"></script>
</body>
</html>
就这样!现在你得到类似这样的结果:
请注意,我添加了一些特定的配置选项:
basePath: '/docs-example/定义您网站的根路径(对于 GitHub Pages 上的个人项目,它就是仓库名称);homepage: '...'设置为仓库的 README 文件(默认情况下 docsify 使用该docs/README.md文件);这使我们能够保持两个主页(GitHub 和 web)同步。
而这仅仅是个开始!Docsify 的主要优势之一就是可以通过插件轻松添加实用功能。
我们来添加搜索功能。
我们只需要添加这两行代码:
window.$docsify = {
loadSidebar: true,
subMaxLevel: 2,
+ search: 'auto',
repo: 'palkan/docs-example',
basePath: '/docs-example/',
auto2top: true,
homepage: 'https://raw.githubusercontent.com/palkan/docs-example/master/README.md'
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-ruby.min.js"></script>
瞧!我们可以搜索文档了!搜索功能在客户端实现,并由保存在 . 中的索引提供支持localStorage。
我喜欢 docsify 的另一个原因是它易于自定义样式:该库使用CSS 属性,因此无需编写自己的 CSS 即可更改颜色和布局!
您可以直接在以下位置更改颜色和字体大小index.html:
<style>
:root {
--theme-color: #ff5e5e;
--theme-color-light: #fd7373;
--theme-color-dark: #f64242;
--theme-color-secondary: #ff5e5e;
--theme-color-secondary-dark: #f64242;
--theme-color-secondary-light: #fd7373;
--text-color-base: #363636;
--text-color-secondary: #646473;
}
</style>
<body>
...
</body>
是不是很棒?
上述示例的代码可以在 GitHub 上找到:palkan/docs-example。
请访问AnyCable 文档网站和相应的代码库,查看更高级的示例!
额外福利*:您可以在此 gist中找到“在 GitHub 上编辑”功能的浮动操作按钮的实现。
使用代码检查器保持文档健康
在本教程的第二部分,我想分享一下我维护文档良好状态的方法。所谓“良好状态”,指的是:
- 源文件(Markdown)和代码示例(Ruby)的风格一致性。
- 拼写正确。
- 有效的代码示例(从语法角度来看)。
- 有效链接(任何链接都不应指向 4xx/5xx)。
上述所有情况都有相应的开源工具(有时甚至有很多),这并不奇怪。
Markdownlint帮助我强制执行 Markdown 文件样式(它还有NodeJS 版本和VS Code 插件)。
我通常还会禁用几条规则:
- 行长度(
MD013)—现代编辑器(如 VS Code)可以通过换行来处理长行。 - HTML 片段——有时 Markdown 还不够用。
为此,我.mdlrc在项目根目录下放置了一个文件,内容如下:
rules "~MD013", "~MD033"
为了处理 Ruby 语法,我使用RuboCop以及我专门为此编写的rubocop-md插件。作为默认样式配置,我最近开始使用standard .
要使此设置生效,您需要:
- 安装
standardgemsrubocop-md(gem install standard和gem install rubocop-md) - 添加一个
.rubocop.yml包含以下内容的列表:
require:
- standard/cop/semantic_blocks
- rubocop-md
inherit_gem:
standard: config/base.yml
Standard/SemanticBlocks:
Enabled: false
- 快跑,鲁博科普!
对于拼写检查,还有另一个 Ruby 工具——Forspell。它是对著名的Hunspell包的封装。
由于涉及大量技术术语,您在第一次运行 Forspell 时可能会看到很多警告:
$ forspell docs/
docs/development/lefthook.md:5: lefthook (suggestions: left hook, left-hook, leftmost)
docs/development/lefthook.md:9: lefthook (suggestions: left hook, left-hook, leftmost)
docs/development/lefthook.md:11: Hombrew (suggestions: Hombre, Hombres, Hombre w)
docs/development/lefthook.md:17: Golang (suggestions: Golan, Golan g, Angolan)
这个问题很容易解决,只需运行带有相应--gen-dictionary标志的 Forspell 命令即可:它会生成一个forspell.dict包含所有未知单词的文件。别忘了仔细检查这个文件,并删除实际的拼写错误。
最后,为了确保我们的文档没有任何死链接,我使用了liche——一个用 Go 语言编写的 Markdown 和 HTML 链接检查器:
$ liche -r docs/
ERROR https://githb.com/palkan/anyway_config
Dialing to the given TCP address timed out
Liche缺少一些我希望它具备的功能:例如,它不会对返回404错误的URL发出警告。尽管如此,我发现它比其他现有工具略好一些。
为了管理所有这些代码检查工具,我使用Lefthook进行本地开发,使用 CircleCI 进行拉取请求。
以下是我的配置文件的内容lefthook.yml,该文件存储了 Lefthook 的配置:
pre-commit:
commands:
mdl:
glob: "**/*.md"
run: mdl {staged_files}
liche:
glob: "**/*.md"
run: liche -r docs
forspell:
glob: "**/*.md"
run: forspell {staged_files}
rubocop:
glob: "**/*.md"
run: rubocop {staged_files}
CirclCI 的配置语句稍微冗长一些,但功能基本相同:
version: 2.1
workflows:
version: 2
build_and_test:
jobs:
- checkout
- md_lint:
requires:
- checkout
- links_lint:
requires:
- checkout
- spelling:
requires:
- checkout
- rubocop:
requires:
- checkout
executors:
golang:
docker:
- image: circleci/golang:1.12.4-stretch
ruby:
docker:
- image: circleci/ruby:2.5-stretch
jobs:
checkout:
executor: ruby
steps:
- restore_cache:
keys:
- project-source-v1-{{ .Branch }}-{{ .Revision }}
- project-source-v1-{{ .Branch }}
- project-source-v1
- checkout
- save_cache:
key: project-source-v1-{{ .Branch }}-{{ .Revision }}
paths:
- .git
- persist_to_workspace:
root: .
paths: .
md_lint:
executor: ruby
steps:
- attach_workspace:
at: .
- run:
name: Install mdl
command: gem install mdl
- run:
name: Markdown lint
command: mdl docs
links_lint:
executor: golang
steps:
- attach_workspace:
at: .
- run:
name: Install liche
command: go get -u github.com/raviqqe/liche
- run:
name: Check links
command: liche -r docs
spelling:
executor: ruby
steps:
- attach_workspace:
at: .
- run:
name: Install hunspell
command: sudo apt-get install hunspell
- run:
name: Install forspell
command: gem install forspell
- run:
name: Check spelling
command: forspell docs/
rubocop:
executor: ruby
steps:
- attach_workspace:
at: .
- run:
name: Install standard
command: gem install standard
- run:
name: Install rubocop-md
command: gem install rubocop-md
- run:
name: Check Ruby style
command: rubocop
完整的示例可以在docs.anycable.io仓库中找到(参见 PR #14和#15)。
我花了很长时间(真的,好几年)才搭建出这套方案。现在我几分钟就能启动一个新的文档网站。希望这篇文章对您有所帮助,下次您需要为项目创建基于 Web 的文档时,可以考虑采用类似的方法。
文档万岁!
请访问https://evilmartians.com/chronicles阅读更多开发者文章!
文章来源:https://dev.to/evilmartians/keeping-oss-documentation-with-docsify-lefthook-and-friends-11e5






