翼度科技»论坛 编程开发 JavaScript 查看内容

Babel自动生成Attribute文档实现详解

3

主题

3

帖子

9

积分

新手上路

Rank: 1

积分
9
1. 前言

利用Babel自动解析源码属性上的注释生成对应Markdown文档,这个场景的应用主要包括在组件库文档对组件属性的介绍中,这一篇就通过编写一个Babel插件来实现这个功能~

2. 开发自动生成属性文档插件


2.1 生成Babel插件模板:


  • 2.1.1 创建
    1. babel-plugin-auto-attr-doc
    复制代码
    文件夹;
  • 2.1.2 安装
    1. npm i -g yo generator-babel-plugin-x
    复制代码

  • 2.1.3 在新建目录下执行
    1. yo babel-plugin-x:v7-ts
    复制代码

生成的插件模板如下:
  1. babel-plugin-auto-attr-doc  
  2. ├─ lib                     
  3. │  └─ index.js              
  4. ├─ src                     
  5. │  └─ index.ts              
  6. ├─ __tests__               
  7. │  ├─ fixtures              
  8. │  │  └─ example            
  9. │  │     ├─ actual.ts      
  10. │  │     └─ expected.ts     
  11. │  └─ index.js              
  12. ├─ package-lock.json        
  13. ├─ package.json            
  14. ├─ README.md               
  15. └─ tsconfig.json            
复制代码
2.2 转换思路详解:


转换过程:利用Babel将Typescript脚本解析为AST,通过对AST结构分析抽离对应的注释部分,再拼接Markdown表格风格的语法;
源码要求:**我们应该将组件涉及到对外提供的属性统一到对应的
  1. types.ts
复制代码
文件管理,分别导出对应的
  1. type
复制代码
字段;
注释要求:**分别定义字段描述、类型、可选项、默认值4项,由于解析器关键词冲突原因,我们应该尽量避免;
  1. /**
  2.   * @cDescribe 类型
  3.   * @cType string
  4.   * @cOptions
  5.   * @cDefault
  6.   */
  7. export type IType = "primary" | "success" | "warning" | "danger" | "info";
  8. /**
  9.   * @cDescribe 图标组件
  10.   * @cType string
  11.   * @cOptions
  12.   * @cDefault
  13.   */
  14. export type IIcon = string;
  15. /**
  16.   * @cDescribe 是否为朴素按钮
  17.   * @cType boolean
  18.   * @cOptions
  19.   * @cDefault false
  20.   */
  21. export type IPlain = boolean;
复制代码
Markdown表格:**展示组件的属性、描述、类型、可选值和默认值这几项;


2.3 单元测试用例:


  • 准备插件待解析源码文件
    1. source-code.ts
    复制代码

  • 准备实际生成MD后应该显示的内容文件
    1. actual.md
    复制代码

  1. | 属性名 | 说明 | 类型 | 可选值        | 默认值 |
  2. | ------ | ---- | ---- | ----- | ----- |
  3. | type | 类型 | string |  |  |
  4. | icon | 图标组件 | string |  |  |
  5. | plain | 是否为朴素按钮 | boolean |  | false |
复制代码

  • 调整单元测试文件读取:
  1. it(`should ${caseName.split("-").join(" ")}`, () => {
  2.   const actualPath = path.join(fixtureDir, "source-code.ts");
  3.   // 对源码进行加载解析
  4.   transformFileSync(actualPath);
  5.   // 读取我们准备好的md文件
  6.   const actual = fs
  7.     .readFileSync(path.join(fixtureDir, "actual.md"))
  8.     .toString();
  9.   // 读取插件解析生成的md文件
  10.   const expected = fs
  11.     .readFileSync(path.join(fixtureDir, "api-doc.md"))
  12.     .toString();
  13.   // diff
  14.   const diff = diffChars(actual, expected);
  15.   diff.length > 1 && _print(diff);
  16.   expect(diff.length).toBe(1);
  17. });
复制代码
2.4 AST分析详解:


  • 通过在AST explorer的源码分析,我们在Babel中可以通过遍历
    1. ExportNamedDeclaration
    复制代码
    (命名导出声明);
    1. leadingComments
    复制代码
    数组中可以取出所有注释文本的集合,在Babel处理时我们需要依次处理每一块注释后增加标记来避免重复处理;
    1. (path.node.declaration as t.TypeAlias).id.name
    复制代码
    中取属性名称;
将注释文本通过doctrine模块解析为对象后和属性名合并对转换Markdown所需要的所有数据~


2.5 插件开发过程:


2.5.1 定义Comment、ApiTable类型对象:
  1. type Comment =
  2.   | {
  3.       describe: string;
  4.       type: any;
  5.       options?: any;
  6.       default?: any;
  7.     }
  8.   | undefined;
复制代码
  1. type ApiTable = {
  2.   attributeName: any;
  3.   attributeDescribe: any;
  4.   attributeType: any;
  5.   attributeOptions: any;
  6.   attributeDefault: any;
  7. };
复制代码
2.5.2 插件主逻辑分析:


  • pre:初始化存放apidoc容器,避免在存放时找不到容器;
  • visitor:解析源码并获取组织MD内容数据暂存到apidoc中;
  • post:取出所有的apidoc内容解析并输出到本地文件中;
  1. export default declare(
  2.   (api: BabelAPI, options: Record<string, any>, dirname: string) => {
  3.     api.assertVersion(7);
  4.     return {
  5.       name: "auto-attr-doc",
  6.       pre(this: PluginPass, file: BabelFile) {
  7.         this.set("api-doc", []);
  8.       },
  9.       visitor: {
  10.         ExportNamedDeclaration(
  11.           path: NodePath<t.ExportNamedDeclaration>,
  12.           state: PluginPass
  13.         ) {
  14.           const apidoc = state.get("api-doc");
  15.           // 处理 path.node.leadingComments 中未处理的数据后塞到apidoc中
  16.           state.set("api-doc", apidoc);
  17.         },
  18.       },
  19.       post(this: PluginPass, file: BabelFile) {
  20.         const apidoc = this.get("api-doc");
  21.         const output = generateMD(apidoc);
  22.         const root = path.parse(file.opts.filename || "./").dir;
  23.         fs.writeFileSync(path.join(root, "api-doc.md"), output, {
  24.           encoding: "utf-8",
  25.         });
  26.       },
  27.     } as PluginObj<PluginPass>;
  28.   }
  29. );
复制代码
2.5.3 主逻辑实现:
  1. leadingComments
复制代码
数组会在依次访问
  1. ExportNamedDeclaration
复制代码
时不停增加,我们在处理掉当前索引的对象后增加一个处理过的标记
  1. skip
复制代码
,下次循环直接跳过;
通过
  1. parseComment
复制代码
函数解析后的对象可以通过
  1. tags
复制代码
数组获取到所有的注释项目,通过对应的
  1. title
复制代码
得到对应
  1. description
复制代码
内容;
在往apidoc存放数据时需要处理属性名称符合一定的规则,并将
  1. apidoc
复制代码
对象存放到原容器中;
  1. {
  2.   ExportNamedDeclaration(
  3.     path: NodePath<t.ExportNamedDeclaration>,
  4.     state: PluginPass
  5.   ) {
  6.     const apidoc = state.get("api-doc");
  7.     let _comment: Comment = undefined;
  8.     path.node.leadingComments?.forEach((comment) => {
  9.       if (!Reflect.has(comment, "skip")) {
  10.         const tags = parseComment(comment.value)?.tags;
  11.         _comment = {
  12.           describe:
  13.             tags?.find((v) => v.title === "cDescribe")?.description || "",
  14.           type: tags?.find((v) => v.title === "cType")?.description || "",
  15.           options:
  16.             tags?.find((v) => v.title === "cOptions")?.description || "",
  17.           default:
  18.             tags?.find((v) => v.title === "cDefault")?.description || "",
  19.         };
  20.         Reflect.set(comment, "skip", true);
  21.       }
  22.     });
  23.     apidoc.push({
  24.       attributeName: (path.node.declaration as t.TypeAlias).id.name.substr(1).toLocaleLowerCase(),
  25.       attributeDescribe: _comment!.describe,
  26.       attributeType: _comment!.type,
  27.       attributeOptions: _comment!.options,
  28.       attributeDefault: _comment!.default,
  29.     } as ApiTable);
  30.     state.set("api-doc", apidoc);
  31.   },
  32. }
复制代码
2.5.4 注释解析函数:
  1. const parseComment = (comment: string) => {
  2.   if (!comment) {
  3.     return;
  4.   }
  5.   return doctrine.parse(comment, {
  6.     unwrap: true,
  7.   });
  8. };
复制代码
2.5.5 Markdown表格拼装:
  1. const generateMD = (apidoc: Array<ApiTable>) => {
  2.   let raw = `| 属性名 | 说明 | 类型 | 可选值        | 默认值 |\n| ------ | ---- | ---- | ----- | ----- |\n`;
  3.   apidoc.forEach((item) => {
  4.     raw += `| ${item.attributeName} | ${item.attributeDescribe} | ${item.attributeType} | ${item.attributeOptions} | ${item.attributeDefault} |\n`;
  5.   });
  6.   return raw;
  7. };
复制代码
2.5.6生成结果展示~



3. 总结

插件生成目前基本功能完成,注释解析可以通过Babel的插件选项来定义作为一个扩展方向,MD文件的生成可以通过对应工具转换,更多的输出文件类型也可以作为扩展方向,欢迎喜欢玩转Babel的小伙伴一起交流交流~
已推送至GitHub https://github.com/OSpoon/awesome-examples
以上就是Babel自动生成Attribute文档实现详解的详细内容,更多关于Babel生成Attribute文档的资料请关注脚本之家其它相关文章!

来源:https://www.jb51.net/article/266664.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具