Babel自動(dòng)生成Attribute文檔實(shí)現(xiàn)詳解
1. 前言
利用Babel自動(dòng)解析源碼屬性上的注釋生成對(duì)應(yīng)Markdown文檔,這個(gè)場(chǎng)景的應(yīng)用主要包括在組件庫(kù)文檔對(duì)組件屬性的介紹中,這一篇就通過(guò)編寫(xiě)一個(gè)Babel插件來(lái)實(shí)現(xiàn)這個(gè)功能~
2. 開(kāi)發(fā)自動(dòng)生成屬性文檔插件
2.1 生成Babel插件模板:
- 2.1.1 創(chuàng)建
babel-plugin-auto-attr-doc文件夾; - 2.1.2 安裝
npm i -g yo generator-babel-plugin-x; - 2.1.3 在新建目錄下執(zhí)行
yo babel-plugin-x:v7-ts;
生成的插件模板如下:
babel-plugin-auto-attr-doc ├─ lib │ └─ index.js ├─ src │ └─ index.ts ├─ __tests__ │ ├─ fixtures │ │ └─ example │ │ ├─ actual.ts │ │ └─ expected.ts │ └─ index.js ├─ package-lock.json ├─ package.json ├─ README.md └─ tsconfig.json
2.2 轉(zhuǎn)換思路詳解:

轉(zhuǎn)換過(guò)程:利用Babel將Typescript腳本解析為AST,通過(guò)對(duì)AST結(jié)構(gòu)分析抽離對(duì)應(yīng)的注釋部分,再拼接Markdown表格風(fēng)格的語(yǔ)法;
源碼要求:**我們應(yīng)該將組件涉及到對(duì)外提供的屬性統(tǒng)一到對(duì)應(yīng)的types.ts文件管理,分別導(dǎo)出對(duì)應(yīng)的type字段;
注釋要求:**分別定義字段描述、類(lèi)型、可選項(xiàng)、默認(rèn)值4項(xiàng),由于解析器關(guān)鍵詞沖突原因,我們應(yīng)該盡量避免;
/** * @cDescribe 類(lèi)型 * @cType string * @cOptions * @cDefault */ export type IType = "primary" | "success" | "warning" | "danger" | "info"; /** * @cDescribe 圖標(biāo)組件 * @cType string * @cOptions * @cDefault */ export type IIcon = string; /** * @cDescribe 是否為樸素按鈕 * @cType boolean * @cOptions * @cDefault false */ export type IPlain = boolean;
Markdown表格:**展示組件的屬性、描述、類(lèi)型、可選值和默認(rèn)值這幾項(xiàng);

2.3 單元測(cè)試用例:
- 準(zhǔn)備插件待解析源碼文件
source-code.ts; - 準(zhǔn)備實(shí)際生成MD后應(yīng)該顯示的內(nèi)容文件
actual.md;
| 屬性名 | 說(shuō)明 | 類(lèi)型 | 可選值 | 默認(rèn)值 | | ------ | ---- | ---- | ----- | ----- | | type | 類(lèi)型 | string | | | | icon | 圖標(biāo)組件 | string | | | | plain | 是否為樸素按鈕 | boolean | | false |
- 調(diào)整單元測(cè)試文件讀?。?/li>
it(`should ${caseName.split("-").join(" ")}`, () => {
const actualPath = path.join(fixtureDir, "source-code.ts");
// 對(duì)源碼進(jìn)行加載解析
transformFileSync(actualPath);
// 讀取我們準(zhǔn)備好的md文件
const actual = fs
.readFileSync(path.join(fixtureDir, "actual.md"))
.toString();
// 讀取插件解析生成的md文件
const expected = fs
.readFileSync(path.join(fixtureDir, "api-doc.md"))
.toString();
// diff
const diff = diffChars(actual, expected);
diff.length > 1 && _print(diff);
expect(diff.length).toBe(1);
});
2.4 AST分析詳解:
- 通過(guò)在AST explorer的源碼分析,我們?cè)贐abel中可以通過(guò)遍歷
ExportNamedDeclaration(命名導(dǎo)出聲明); - 在
leadingComments數(shù)組中可以取出所有注釋文本的集合,在Babel處理時(shí)我們需要依次處理每一塊注釋后增加標(biāo)記來(lái)避免重復(fù)處理; - 在
(path.node.declaration as t.TypeAlias).id.name中取屬性名稱(chēng);
將注釋文本通過(guò)doctrine模塊解析為對(duì)象后和屬性名合并對(duì)轉(zhuǎn)換Markdown所需要的所有數(shù)據(jù)~

2.5 插件開(kāi)發(fā)過(guò)程:
2.5.1 定義Comment、ApiTable類(lèi)型對(duì)象:
type Comment =
| {
describe: string;
type: any;
options?: any;
default?: any;
}
| undefined;
type ApiTable = {
attributeName: any;
attributeDescribe: any;
attributeType: any;
attributeOptions: any;
attributeDefault: any;
};
2.5.2 插件主邏輯分析:
- pre:初始化存放apidoc容器,避免在存放時(shí)找不到容器;
- visitor:解析源碼并獲取組織MD內(nèi)容數(shù)據(jù)暫存到apidoc中;
- post:取出所有的apidoc內(nèi)容解析并輸出到本地文件中;
export default declare(
(api: BabelAPI, options: Record<string, any>, dirname: string) => {
api.assertVersion(7);
return {
name: "auto-attr-doc",
pre(this: PluginPass, file: BabelFile) {
this.set("api-doc", []);
},
visitor: {
ExportNamedDeclaration(
path: NodePath<t.ExportNamedDeclaration>,
state: PluginPass
) {
const apidoc = state.get("api-doc");
// 處理 path.node.leadingComments 中未處理的數(shù)據(jù)后塞到apidoc中
state.set("api-doc", apidoc);
},
},
post(this: PluginPass, file: BabelFile) {
const apidoc = this.get("api-doc");
const output = generateMD(apidoc);
const root = path.parse(file.opts.filename || "./").dir;
fs.writeFileSync(path.join(root, "api-doc.md"), output, {
encoding: "utf-8",
});
},
} as PluginObj<PluginPass>;
}
);
2.5.3 主邏輯實(shí)現(xiàn):
leadingComments數(shù)組會(huì)在依次訪(fǎng)問(wèn)ExportNamedDeclaration時(shí)不停增加,我們?cè)谔幚淼舢?dāng)前索引的對(duì)象后增加一個(gè)處理過(guò)的標(biāo)記skip,下次循環(huán)直接跳過(guò);
通過(guò)parseComment函數(shù)解析后的對(duì)象可以通過(guò)tags數(shù)組獲取到所有的注釋項(xiàng)目,通過(guò)對(duì)應(yīng)的title得到對(duì)應(yīng)description內(nèi)容;
在往apidoc存放數(shù)據(jù)時(shí)需要處理屬性名稱(chēng)符合一定的規(guī)則,并將apidoc對(duì)象存放到原容器中;
{
ExportNamedDeclaration(
path: NodePath<t.ExportNamedDeclaration>,
state: PluginPass
) {
const apidoc = state.get("api-doc");
let _comment: Comment = undefined;
path.node.leadingComments?.forEach((comment) => {
if (!Reflect.has(comment, "skip")) {
const tags = parseComment(comment.value)?.tags;
_comment = {
describe:
tags?.find((v) => v.title === "cDescribe")?.description || "",
type: tags?.find((v) => v.title === "cType")?.description || "",
options:
tags?.find((v) => v.title === "cOptions")?.description || "",
default:
tags?.find((v) => v.title === "cDefault")?.description || "",
};
Reflect.set(comment, "skip", true);
}
});
apidoc.push({
attributeName: (path.node.declaration as t.TypeAlias).id.name.substr(1).toLocaleLowerCase(),
attributeDescribe: _comment!.describe,
attributeType: _comment!.type,
attributeOptions: _comment!.options,
attributeDefault: _comment!.default,
} as ApiTable);
state.set("api-doc", apidoc);
},
}
2.5.4 注釋解析函數(shù):
const parseComment = (comment: string) => {
if (!comment) {
return;
}
return doctrine.parse(comment, {
unwrap: true,
});
};
2.5.5 Markdown表格拼裝:
const generateMD = (apidoc: Array<ApiTable>) => {
let raw = `| 屬性名 | 說(shuō)明 | 類(lèi)型 | 可選值 | 默認(rèn)值 |\n| ------ | ---- | ---- | ----- | ----- |\n`;
apidoc.forEach((item) => {
raw += `| ${item.attributeName} | ${item.attributeDescribe} | ${item.attributeType} | ${item.attributeOptions} | ${item.attributeDefault} |\n`;
});
return raw;
};
2.5.6生成結(jié)果展示~

3. 總結(jié)
插件生成目前基本功能完成,注釋解析可以通過(guò)Babel的插件選項(xiàng)來(lái)定義作為一個(gè)擴(kuò)展方向,MD文件的生成可以通過(guò)對(duì)應(yīng)工具轉(zhuǎn)換,更多的輸出文件類(lèi)型也可以作為擴(kuò)展方向,歡迎喜歡玩轉(zhuǎn)Babel的小伙伴一起交流交流~
已推送至GitHub https://github.com/OSpoon/awesome-examples
以上就是Babel自動(dòng)生成Attribute文檔實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Babel生成Attribute文檔的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue使用axios進(jìn)行數(shù)據(jù)異步交互的方法
大家都知道在Vue里面有兩種出名的插件能夠支持發(fā)起異步數(shù)據(jù)傳輸和接口交互,分別是axios和vue-resource,同時(shí)vue更新到2.0之后,宣告不再對(duì)vue-resource更新,而是推薦的axios,今天就講一下怎么引入axios,需要的朋友可以參考下2024-01-01
在瀏覽器console中如何調(diào)用vue內(nèi)部方法
這篇文章主要介紹了在瀏覽器console中如何調(diào)用vue內(nèi)部方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Vuex 單狀態(tài)庫(kù)與多模塊狀態(tài)庫(kù)詳解
這篇文章主要介紹了Vuex 單狀態(tài)庫(kù)與多模塊狀態(tài)庫(kù)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
解決vue watch數(shù)據(jù)的方法被調(diào)用了兩次的問(wèn)題
這篇文章主要介紹了解決vue watch數(shù)據(jù)的方法被調(diào)用了兩次的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
基于Vue封裝實(shí)現(xiàn)全屏功能工具類(lèi)
在?Web?應(yīng)用程序中,有時(shí)需要為某些內(nèi)容提供全屏顯示的功能,本文將介紹如何使用?Vue.js?3?的?Composition?API?創(chuàng)建一個(gè)全屏功能的工具類(lèi),希望對(duì)大家有所幫助2024-03-03
對(duì)vue下點(diǎn)擊事件傳參和不傳參的區(qū)別詳解
今天小編就為大家分享一篇對(duì)vue下點(diǎn)擊事件傳參和不傳參的區(qū)別詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
vue中移動(dòng)端調(diào)取本地的復(fù)制的文本方式
這篇文章主要介紹了vue中移動(dòng)端調(diào)取本地的復(fù)制的文本方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07

