前端文檔站點搭建方案
本文首發(fā)于政采云前端團隊博客:前端文檔站點搭建方案
https://www.zoo.team/article/document-site

前言
在《自動化 Web 性能優(yōu)化分析方案》一文中說到,百策系統(tǒng)性能檢測的原理,以及對于檢測頁面我們最終會生成一份檢測報告,如下圖所示:
前言通過檢測報告,我們可以清楚地了解到我們的頁面在性能方面有哪些不足和有待提高的地方,并且針對每一個扣分項,我們都提供了詳細的扣分原因,以及解決方案。
我們的系統(tǒng)是面向前端團隊所有的開發(fā)同學,因此我們需要將我們的解決方案沉淀下來,群策群力,共同豐富優(yōu)化性能的知識庫,所以我們需要文檔站點,一個可以實時編輯,自動部署的文檔站點。
目前現有的文檔站點方案有 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 的時候觸發(fā),一個完整的 push event 返回的數據字段很多,對于我們來說,下面兩個字段就足夠了
{
??ref:?''????//?分支名
??commits:?[???//?提交內容
????{
??????added:?[],??//?新增的文件路徑
??????modified:?[],??//?修改的文件路徑
??????removed:?[]?????//?刪除的文件路徑
????}
??],
??...
}
整個流程大致如下:
63CCF325-2676-420C-B793-0803B226F8DB.png模板文件即除了 Markdown 外的文件,譬如布局、樣式、腳本等,這些公用文件有變動后需要將整個站點重新構建一遍。
在這個流程中,服務端需要開發(fā)兩個接口,一個用來響應 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 的時候,才會觸發(fā) Hook,為了保證文檔的規(guī)范,只對 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 類似的方案,細節(jié)方面的設計著實比不上開源的那些產品,所以我們決定再折騰一次,遷移到了方案二:node.js + docsify。服務端負責處理 Webhooks 來拉取 GitLab 上最新的文檔文件,docsify 負責實時編譯文檔。
docsify 是一個動態(tài)生成文檔網站的工具。不同于 GitBook、Hexo 的是,它不會在服務端編譯時將 md 文件轉成 Html 文件,所有轉換工作都是在瀏覽器端執(zhí)行的。docsify 已經提供了實施編譯 md 文件的功能,剩下我們需要實現的部分就是在 GitLab 上的文件有更新時,自動觸發(fā)服務重新拉取最新的 md 文件。
改造后的流程:
- 文檔貢獻者在 GitLab 上編輯源文件
- 編輯完成保存后觸發(fā) 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()?{
????//?執(zhí)行命令?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,其它的工作服務器都會幫我們完成。
推薦閱讀
我的公眾號能帶來什么價值?(文末有送書規(guī)則,一定要看)
每個前端工程師都應該了解的圖片知識(長文建議收藏)
為什么現在面試總是面試造火箭?
