「你说vscode插件」-自动生成组件API文档

2,105 阅读4分钟

一、背景

我们在开发组件时,写组件文档是一件很头疼,但又是必不可少的事情,所以有没有一种自动生成组件属性API文档的工具呢?答案是肯定的,Dumi、StoryBook都是专门为组件开发场景而生的文档工具。例如 我们在使用TypeScript开发React组件时,定义好组件的属性类型和参数注解,就可以自动生成一份API文档。基于这样的场景,我们打算通过vscode插件的方式来实现类似的需求。

二、方案探讨

启初,我查看了babel文档,打算通过bebel来解析我们的代码,从中提取出我们想要的信息,然后再进行数据的整合。随后我看又翻阅了下Dumi的文档,想参考下Dumi是如何进行代码解析的。

image.png

好吧,是我想多了,原来已经存在现成的解析工具react-docgen-typescript。那么接下来我们的任务就简单很多了。

三、vscode插件实战

3.1 项目搭建

vscode官方提供了插件开发脚手架,可以快速生成一个项目。

# 安装依赖
npm install -g yo generator-code
# 运行工具
yo code

执行yo code命令之后,会出现以下选择。因为是第一次接触vscode插件,并不想提高学习成本,使用JavaScript来进行开发。剩下的环节就自由发挥好了。

image.png

完成上述操作后,就会在本地生成一个api-generate的文件夹。

3.2 需求开发

3.2.1 注册命令

我们想在目标文件,或者侧边栏对应的文件名下右击,选择对应的选项,从而执行命令对应的回调函数。命令的注册是在package.json文件中,设置contributesactivationEvents关键字完成的。

  // 入口文件 
  "main": "./extension.js",
  "activationEvents": [
    "onCommand:start"
  ],
  "contributes": {
     // 命令
    "commands": [
      {
        "command": "start",
        "title": "组件API文档生成"
      }
    ],
    "menus": {
       // 编辑上下文菜单
      "editor/context": [
        {
          "when": "editorFocus",
          "command": "start",
          "group": "navigation"
        }
      ],
      // 资源管理器上下文菜单
      "explorer/context": [
        {
          "command": "start",
          "group": "navigation"
        }
      ]
    }
  }

3.2.2 代码解析

完成命令的注册之后,就需要写具体的需求逻辑了。首先是组件代码的解析,这里主要依赖react-docgen-typescript

我们先来看下项目的入口文件,默认为根目录下extension.js文件。删除一些注解代码后,我们来看下剩下的内容。

const vscode = require("vscode");

function activate(context) {
  let disposable = vscode.commands.registerCommand("start", function () {
    // 逻辑主体
  });

  context.subscriptions.push(disposable);
}

function deactivate() {}

module.exports = {
  activate,
  deactivate,
};

vscode.commands.registerComman方法的第一个参数为start,即我们刚才注册的命令名。第二个参数就是命令对应的回调函数,我们的核心逻辑就写在这个函数中。我们拿BaseComponent举例。

import * as React from "react";
import { Component } from "react";

export interface IBaseComponentProps {
  /** 属性一*/
  prop1?: string;
  /** 属性二 */
  prop2: number;
  /**属性三*/
  prop3: () => void;
  /** 属性四 */
  prop4: "option1" | "option2" | "option3";
}

export class BaseComponent extends Component<IBaseComponentProps, {}> {
  render() {
    return <div>BaseComponent</div>;
  }
}

使用react-docgen-typescript解析以上代码。

const path = require("path");

const options = {
  savePropValueAsString: true,
};

const res = docgen.parse("./index.tsx", options);

以下是代码输出。

[
  {
    tags: {},
    filePath: "index.tsx",
    description: "Form column.",
    displayName: "Column",
    methods: [],
    props: {
      prop1: {
        defaultValue: null,
        description: "prop1 description",
        name: "prop1",
        parent: { fileName: "index.tsx", name: "IColumnProps" },
        declarations: [{ fileName: "index.tsx", name: "IColumnProps" }],
        required: false,
        type: { name: "string" },
      },
      prop2: {
        defaultValue: null,
        description: "prop2 description",
        name: "prop2",
        parent: { fileName: "index.tsx", name: "IColumnProps" },
        declarations: [{ fileName: "index.tsx", name: "IColumnProps" }],
        required: true,
        type: { name: "number" },
      },
      prop3: {
        defaultValue: null,
        description: "prop3 description",
        name: "prop3",
        parent: { fileName: "index.tsx", name: "IColumnProps" },
        declarations: [{ fileName: "index.tsx", name: "IColumnProps" }],
        required: true,
        type: { name: "() => void" },
      },
      prop4: {
        defaultValue: null,
        description: "prop4 description",
        name: "prop4",
        parent: { fileName: "index.tsx", name: "IColumnProps" },
        declarations: [{ fileName: "index.tsx", name: "IColumnProps" }],
        required: true,
        type: { name: '"option1" | "option2" | "option3"' },
      },
    },
  },
];

3.2.3 代码整合

组件代码解析完毕后,接下来就是对这个List进行遍历处理,然后生成markdown中的表格,具体代码如下。

function commentToMarkDown(componentInfo) {
  let { props } = componentInfo;
  const markdownInfo = renderMarkDown(props);
  const content = prettier.format(markdownInfo, {
    parser: "markdown",
  });
  return content;
}

function renderMarkDown(props) {
  return `
  # 组件
  
  ## 参数 Props

  | 参数 |  说明 | 类型 | 默认值 | 必填 |
  | --- | --- | --- | --- | ---|
  ${Object.keys(props)
    .map((key) => renderProp(key, props[key]))
    .join("")}
  `;
}

function getType(type) {
  const handler = {
    enum: (type) =>
      type.value.map((item) => item.value.replace(/'/g, "")).join(" \\| "),
    union: (type) => type.value.map((item) => item.name).join(" \\| "),
  };
  if (typeof handler[type.name] === "function") {
    return handler[type.name](type).replace(/\|/g, `&nbsp;&#124;&nbsp;`);
  } else {
    return type.name.replace(/\|/g, `&nbsp;&#124;&nbsp;`);
  }
}

function renderProp(
  name,
  { type = { name: "-" }, defaultValue = { value: "-" }, required, description }
) {
  return `| ${name} | ${description || "-"}|${getType(type)} | ${
    defaultValue?.value?.replace(/\|/g, "<span>|</span>") || "-"
  } | ${required ? "true" : "false"} | 
  `;
}

3.2.4 结果反馈

最终,通过vscode.env.clipboard.writeText方法,将结果复制到粘贴板。

四、vscode插件调试

通过侧边栏的「运行和调试」,就会新开一个vscode窗口,拓展开发宿主环境,然后就可以进行代码逻辑的调试。

image.png

五、插件发布

插件开发调试完毕之后,就剩下最后的发布环节了。

5.1 Azure DevOps

5.1.1 创建组织

登录Azure DevOps创建组织,没有账号就走注册流程。

5.1.2 生成Token

Organization选择「All acceeeible organizations」,Scopes选择「Full access」。 生成Token后,及时记录。 image.png

5.2 publisher

接下来需要去应用市场创建发布者。

image.png 创建完publiser之后,需要在package.json中也加入该字段。

5.3 vsce安装

vsce(Visual Studio Code Extensions),一个用于打包、发布和管理 VS Code 扩展的命令行工具。

5.3.1 工具安装

npm install -g vsce

5.3.2 创建.vsix文件

vsce package

5.3.3 发布

# 登录
vsce login your-publisher-name Token
# 发布
vsce publish

审核通过后,就可以在插件市场找到你的插件啦。欢迎大家尝试这款插件,发现bug及时与我讨论,具体也可以参考源码哈。创作不易,点赞加关注,感谢您的支持!

参考