前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >利用Babel自动生成“Attribute”文档

利用Babel自动生成“Attribute”文档

作者头像
前端小鑫同学
发布2022-12-26 13:07:56
5120
发布2022-12-26 13:07:56
举报

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

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

2.1 生成Babel插件模板:

  • 2.1.1 创建babel-plugin-auto-attr-doc文件夹;
  • 2.1.2 安装npm i -g yo generator-babel-plugin-x
  • 2.1.3 在新建目录下执行 yo babel-plugin-x:v7-ts

生成的插件模板如下:

代码语言:javascript
复制
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 转换思路详解:

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

2.3 单元测试用例:

  1. 准备插件待解析源码文件source-code.ts
  2. 准备实际生成MD后应该显示的内容文件actual.md
代码语言:javascript
复制
| 属性名 | 说明 | 类型 | 可选值	| 默认值 |
| ------ | ---- | ---- | ----- | ----- |
| type | 类型 | string |  |  |
| icon | 图标组件 | string |  |  |
| plain | 是否为朴素按钮 | boolean |  | false |

复制代码
  1. 调整单元测试文件读取:
代码语言:javascript
复制
it(`should ${caseName.split("-").join(" ")}`, () => {
  const actualPath = path.join(fixtureDir, "source-code.ts");

  // 对源码进行加载解析
  transformFileSync(actualPath);

  // 读取我们准备好的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分析详解:

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

将注释文本通过doctrine模块解析为对象后和属性名合并对转换Markdown所需要的所有数据~

image.png
image.png

2.5 插件开发过程:

2.5.1 定义Comment、ApiTable类型对象:
代码语言:javascript
复制
type Comment =
  | {
      describe: string;
      type: any;
      options?: any;
      default?: any;
    }
  | undefined;
复制代码
代码语言:javascript
复制
type ApiTable = {
  attributeName: any;
  attributeDescribe: any;
  attributeType: any;
  attributeOptions: any;
  attributeDefault: any;
};
复制代码
2.5.2 插件主逻辑分析:
  1. pre:初始化存放apidoc容器,避免在存放时找不到容器;
  2. visitor:解析源码并获取组织MD内容数据暂存到apidoc中;
  3. post:取出所有的apidoc内容解析并输出到本地文件中;
代码语言:javascript
复制
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 中未处理的数据后塞到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 主逻辑实现:
  1. leadingComments数组会在依次访问ExportNamedDeclaration时不停增加,我们在处理掉当前索引的对象后增加一个处理过的标记skip,下次循环直接跳过;
  2. 通过parseComment函数解析后的对象可以通过tags数组获取到所有的注释项目,通过对应的title得到对应description内容;
  3. 在往apidoc存放数据时需要处理属性名称符合一定的规则,并将apidoc对象存放到原容器中;
代码语言:javascript
复制
{
  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 注释解析函数:
代码语言:javascript
复制
const parseComment = (comment: string) => {
  if (!comment) {
    return;
  }
  return doctrine.parse(comment, {
    unwrap: true,
  });
};
复制代码
2.5.5 Markdown表格拼装:
代码语言:javascript
复制
const generateMD = (apidoc: Array<ApiTable>) => {
  let raw = `| 属性名 | 说明 | 类型 | 可选值	| 默认值 |\n| ------ | ---- | ---- | ----- | ----- |\n`;
  apidoc.forEach((item) => {
    raw += `| ${item.attributeName} | ${item.attributeDescribe} | ${item.attributeType} | ${item.attributeOptions} | ${item.attributeDefault} |\n`;
  });
  return raw;
};
复制代码
2.5.6生成结果展示~
image.png
image.png

3. 总结

插件生成目前基本功能完成,注释解析可以通过Babel的插件选项来定义作为一个扩展方向,MD文件的生成可以通过对应工具转换,更多的输出文件类型也可以作为扩展方向,欢迎喜欢玩转Babel的小伙伴一起交流交流~

已推送至GitHub,欢迎克隆演示:git clone https://github.com/OSpoon/awesome-examples.git

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 开发自动生成属性文档插件
    • 2.1 生成Babel插件模板:
      • 2.2 转换思路详解:
        • 2.3 单元测试用例:
          • 2.4 AST分析详解:
            • 2.5 插件开发过程:
              • 2.5.1 定义Comment、ApiTable类型对象:
              • 2.5.2 插件主逻辑分析:
              • 2.5.3 主逻辑实现:
              • 2.5.4 注释解析函数:
              • 2.5.5 Markdown表格拼装:
              • 2.5.6生成结果展示~
          • 3. 总结
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档