前端文档站点搭建方案
本文首发于政采云前端团队博客:前端文档站点搭建方案
https://www.zoo.team/article/document-site
前言
在《自动化 Web 性能优化分析方案》一文中说到,百策系统性能检测的原理,以及对于检测页面我们最终会生成一份检测报告,如下图所示:前言通过检测报告,我们可以清楚地了解到我们的页面在性能方面有哪些不足和有待提高的地方,并且针对每一个扣分项,我们都提供了详细的扣分原因,以及解决方案。
我们的系统是面向前端团队所有的开发同学,因此我们需要将我们的解决方案沉淀下来,群策群力,共同丰富优化性能的知识库,所以我们需要文档站点,一个可以实时编辑,自动部署的文档站点。
目前现有的文档站点方案有 docsify (https://docsify.js.org/#/?id=docsify)、gitbook (http://gitbook.hushuang.me/)、vuepress (https://vuepress.vuejs.org/zh/guide/) 等,也有着相对丰富的插件和主题,但是布局较为单一,灵活性不足。
需求
- 文档用 Markdown 编写,最终生成 Html
- 文档可以实时编辑,而不是修改 Html 代码
- Markdown 文件修改后,文档站点自动更新
方法一:EggJS + marked + highlight.js
- 将文档以 Markdown 的形式放在 GitLab 中,以便文档的维护
- 熟悉 Git 的小伙伴知道 GitLab 是可以设置钩子 (Hook) 的,通过设置钩子可以实现当我们提交代码后,服务端知道在哪个分支修改了哪些文件,然后把更新后的文档重新转化成 Html 文件
在设置 GitLab Webhook 时,只需选择 push event 就好,这样 Hook 就只会在 push 的时候触发,一个完整的 push event 返回的数据字段很多,对于我们来说,下面两个字段就足够了
{
ref: '' // 分支名
commits: [ // 提交内容
{
added: [], // 新增的文件路径
modified: [], // 修改的文件路径
removed: [] // 删除的文件路径
}
],
...
}
整个流程大致如下:63CCF325-2676-420C-B793-0803B226F8DB.png模板文件即除了 Markdown 外的文件,譬如布局、样式、脚本等,这些公用文件有变动后需要将整个站点重新构建一遍。
在这个流程中,服务端需要开发两个接口,一个用来响应 GitLab 的 Webhook,另一个是用来进行手动批量生成。// document.router.ts
import { Application } from "egg";
export default (app: Application): void => {
const { controller, router } = app;
// gitlab webhook
router.post("/api/hook", controller.document.hook);
router.get("/api/batchUpdate", controller.document.batchUpdate);
};
Webhook
在设置 Webhook 的时候,我们只需选择 push event,这样就会在 push 的时候,才会触发 Hook,为了保证文档的规范,只对 Master 分支的 push event 进行操作。
Webhooks 配置:image-20191130105149305代码示例:const { commits, ref }: { commits: any[]; ref: string } = pushEvent;
// 过滤非 master 分支的 push
if (!isMaster(ref)) {
return false;
}
// 修改(新增)文档列表
const updateList: string[] = [];
// 删除文档列表
const removeList: string[] = [];
commits.forEach((item: Commits) => {
const { added, modified, removed } = item;
updateList.push(...added, ...modified);
removeList.push(...removed);
});
// 过滤重复文件
const uniqueUpdateList: string[] = [...new Set(updateList)];
const uniqueRemoveList: string[] = [...new Set(removeList)];
在得到要更新的集合 uniqueUpdateList
后,就要将对应的 Markdown 文件内容转为 Html,如何获取到 Git 上的单个文件呢,我们可以从 uniqueUpdateList
得知新增或修改的文件路径,然后我们需要借助 GitLab Open Api 中的 ${gitLabhost}/api/v3/projects/${projectId}/repository/files?file_path=${filePath}&ref=master&private_token=${accessToken}
获取对应文件的内容,完整的返回如下:
{
file_name: "", // 文件名称
file_path: "", // 文件路径
size: 700, // 文件大小
encoding: "base64", // 编码方式
content: "", // 文件内容
ref: "master", // 分支名
...
}
通过这个接口可拿到 base64
编码的文件内容,转换成 uft-8
后就是我们需要的 Markdown 文档了。
new Buffer(content, "base64").toString("utf8");
接下来需要将 Markdown 转换成 Html,Markdown 转 Html 使用的是 marked (https://yarnpkg.com/zh-Hant/package/marked),代码高亮使用的是 highlight.js (https://yarnpkg.com/zh-Hant/package/highlight.js)
import * as marked from "marked";
import * as hljs from "highlight.js";
marked.setOptions({
// 设置高亮
highlight(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(lang, code, true).value;
} else {
return hljs.highlightAuto(code).value;
}
}
});
const customRender = new marked.Renderer();
const htmlStr: string = marked(markdownStr, { renderer: customRender });
全量生成
为了保证使用最新的模板和文档生成 html,每次全量生成之前,都需要从 GitLab 拉取完整的项目,拉取代码使用的是 git-clone (https://yarnpkg.com/zh-Hant/package/git-clone)gitClone(repo, targetPath, {}, () => {
...
});
接下来就是读取临时文件夹中 Markdown 文件内容,结合页面模板转化为 Html。页面模板即除了文档内容 Markdown 外的文件,譬如布局、公用头部、左侧菜单、样式、脚本等。
然后将前面生成的正文内容注入到准备好的模板中,这里使用的模板引擎是 Ejs,就可以得到如下页面:image-20191125211921359方法二:NestJS + docsify
本着折腾的精神,上述方法是我们组的小伙伴自己实现了 docsify 类似的方案,细节方面的设计着实比不上开源的那些产品,所以我们决定再折腾一次,迁移到了方案二:node.js + docsify。服务端负责处理 Webhooks 来拉取 GitLab 上最新的文档文件,docsify 负责实时编译文档。
docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo 的是,它不会在服务端编译时将 md 文件转成 Html 文件,所有转换工作都是在浏览器端执行的。docsify 已经提供了实施编译 md 文件的功能,剩下我们需要实现的部分就是在 GitLab 上的文件有更新时,自动触发服务重新拉取最新的 md 文件。
改造后的流程:
- 文档贡献者在 GitLab 上编辑源文件
- 编辑完成保存后触发 GitLab 的 Webhooks
- 文档服务接收到 Webhooks 请求后拉取最新的文档
- 用户刷新页面后 docsify 实时把最新的 md 文件转化为 Html
服务端核心代码
// app.controller.ts
import { Controller, Post } from "@nestjs/common";
import * as execa from "execa";
@Controller()
export class AppController {
@Post("hook")
async hook() {
// 执行命令 git pull,拉取最新代码
const { stdout } = await execa("git", ["pull"]);
return stdout;
}
}
// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { join } from "path";
import { Logger } from "@nestjs/common";
const port = parseInt(process.env.PORT, 10) || 3001;
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix("api");
app.useStaticAssets(join(__dirname, "..", "docs"));
await app.listen(port);
Logger.log(`服务已启动,请访问 http://localhost:${port}`);
}
bootstrap();
效果图
效果图点击编辑文档即可进入文档对应的 GitLab 页面进行编辑。GitLab 编辑方案对比
方案二和方案一不同的地方就是把 md 文件渲染成 Html 的一步从服务端改到了浏览器端,服务器端只承担接收 Webhook 拉取最新的代码的工作。fD4H17kx4AkM4dG5.png总结
以上提供了一个不用 GitLab CI 实现文档站点内容修改后自动更新的思路。一个顺手的文档站点搭好之后,接下来我们就只需要关心如何把 Markdown 写好推送到 GitLab,其它的工作服务器都会帮我们完成。
推荐阅读
我的公众号能带来什么价值?(文末有送书规则,一定要看)
每个前端工程师都应该了解的图片知识(长文建议收藏)
为什么现在面试总是面试造火箭?