如何开发一个vscode插件自动生成React组件文档
背景
组件化开发早已成为前端开发者必备技能之一了,在团队协作中,一个组件开发完成后,往往是需要被其他同事使用的,随之而来的问题就是“你这个组件怎么用呀?”,因此配套的组件文档是必不可少的,但同时也会带来其他问题:
- 码字耗时,增加了开发时间
- 文档格式不统一,不规范
- …
因此,为了解决以上问题,自动生成统文档已经急不可待。考虑到团队中大部分小伙伴使用的IDE是vscode,且不想对旧代码、旧文档有浸入式修改,故通过vscode extension
的形式做文档优化。
阅读本文,你将了解到:
- 如何使用
react-docgen
开发一个vscode插件,自动生成react jsx
组件文档,效果如上图 react-docgen
(其他前端工具、插件)赖以生存的抽象语法树AST
准备
前置知识和依赖、环境安装。
react-docgen
react-docgen是一个CLI和工具箱,帮助从React组件中提取信息,并从中生成文档。它使用ast-types和@babel/parser将源解析成AST,并提供方法来处理这个AST以提取所需的信息。输出/返回值是一个JSON blob/JavaScript对象。
要使用一个组件,我们需要知道组件基本信息,如用途、传参,最好还能有举例。props
是react组件间通讯最常用方式之一,组件内可通过prop-types
和defaultProps
来做类型检查和设置默认值,而react-docgen可通过它们和jsdoc注释内容来获取组件信息。具体安装和使用规则,可点这里查看。
vscode extension
关于vscode插件的开发环境搭建教程网上有很多,这里就不进行赘述了,具体可以参考官方文档或者这里。执行脚手架命令后,生成目录大致如下:
vscode-react-doc // 插件根目录
├── src // 源码
│ ├── commands // 命令文件夹
│ │ ├── jsx2md.js
│ │ ├── ...
│ ├── utils // 放自定义生成文档方法
│ ├── extension.js // 插件入口文件
├── README.md // 插件说明
└── package.json // 项目配置文件
复制代码
开发
安装好react-docgen
和vscode extension
脚手架后环境后,即可开始开发,这里以一个FC
组件为例(class
同样支持)。
组件Button
使用prop-types
和defaultProps
设置默认props,使用/** ... */
注释。
import React from "react";
import PropTypes from "prop-types";
/**
* 自定义信息,用在生成组件使用
* @description 我是组件简介
* @author 我是组件开发者
* @remark 我是备注
*/
function Button(props) {
const { text, clickHandle } = props;
return <div onClick={clickHandle}>{text}</div>;
}
Button.propTypes = {
/**
* 按钮文字
*/
text: PropTypes.string,
/**
* 按钮点击事件
*/
clickHandle: PropTypes.func.isRequired,
};
Button.defaultProps = {
text: "按钮文字",
};
export default Button;
复制代码
配置命令
分别配置extension.js
和package.json
,这里新增一个名为jsx2md
的命令,在选中jsx组件文件时激活插件。
// extension.js
const vscode = require("vscode");
const jsx2md = require("./commands/jsx2md.js");
//销毁插件回调函数
function activate(context) {
console.log("插件激活成功");
//注册命令
const jsx2mdCmd = vscode.commands.registerCommand(
"vscode-react-doc.jsx2md", //命令名:jsx2md
(fileUri) => {
jsx2md(fileUri); //将选择的文件作为参数传入
}
);
//订阅命令,可push多个
context.subscriptions.push(jsx2mdCmd);
}
//销毁插件回调函数
function deactivate() {}
module.exports = {
activate,
deactivate,
};
复制代码
// package.json
{
...
// 什么情况下可激活插件
"activationEvents": [
//onCommand表示输入命令时激活,和extension.js统一
"onCommand:vscode-extension-demo.jsd2md"
],
// 入口文件
"main": "./src/extension.js",
// 插件配置项
"contributes": {
//菜单栏
"menus": {
//IDE右键菜单栏
"editor/context": [
{
"when": "resourceExtname == .jsx", //选择文件后缀名
"command": "vscode-react-doc.jsx2md",
"group": "navigation"
}
],
//文件夹右键菜单栏
"explorer/context": [
{
"when": "resourceExtname == .jsx",
"command": "vscode-react-doc.jsx2md",
"group": "navigation"
}
]
},
//注册的命令,和extension.js统一
"commands": [
{
"when": "editorLangId == javascriptreact",
"command": "vscode-react-doc.jsx2md",
"title": "jsx2md"
}
]
}
...
}
复制代码
解析组件,生成文档
使用react-docgen
解析组件文件,生成AST
并返回组件信息json对象,使用正则/操作字符串方式操作json,根据自身需求,生成文档内容,并复制粘贴使用。
// jsx2md.js
const fs = require("fs-extra");
const prettier = require("prettier");
const vscode = require("vscode");
const reactDocs = require("react-docgen");
const jsx2md = (fileUri) => {
const uri = fileUri.fsPath;
// 生成的文档内容
let resultMd = "";
// 文件原始内
const originContent = fs.readFileSync(uri, "utf-8");
// 解析后的组件信息,json格式
const componentInfo = reactDocs.parse(originContent);
// 根据componentInfo内容,自定义生成文档内容(字符串相关操作)
// ...
// 使用prettier格式化文档内容
const formatMd = prettier.format(resultMd, {
parser: "markdown",
});
//复制文档内容
try {
vscode.env.clipboard.writeText(resultMd);
vscode.window.showInformationMessage(
`${resultMd ? "文档生成成功,可粘贴使用" : "无可用信息"}`
);
} catch (error) {
vscode.window.showErrorMessage(error.message);
}
}
module.exports = jsx2md;
复制代码
componentInfo
打印出来如下:
{
description:'@description 我是组件简介\n@author 我是组件开发者\n@remark 我是备注',
displayName:'Button',
methods:[],
props:{
text: {
defaultValue:{
computed:false,
value:'"按钮文字"'
},
description:'按钮文字',
required:false,
type:{
name:'string'
}
},
clickHandler: {
description:'按钮点击事件'
required:true,
type:{
name:'func'
}
}
}
复制代码
主要包含:
displayName
组件名称description
组件的自定义信息注释methods
组件内部的方法props
组件的属性参数
其中这里的props
是我们组件文档的核心内容,在提取的内容中,已经涵盖了属性的 属性名、属性描述、类型、默认值、是否必传,加上自定义生成一个用例说明,这些内容基本可满足我们阅读组件文档所需要的信息。
如果有相同应用场景的小伙伴,可以在插件市场搜索vscode-react-doc
下载使用,或者点击这里下载。
拓展
react-docgen
原理是通过babel
解析文件内容,生成AST
,再转成一个含组件信息的json。其实,在前端领域中,AST
影子随处可见,在这里,我们继续深入探究一下它吧。
什么是AST
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
这里借用工具ast explorer来进一步了解AST,在工具中左边输入:
let sum = 1 + 2;
复制代码
在工具右边,会生成当前代码对应的AST
{
"type": "Program",
"start": 0,
"end": 17,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 16,
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 15,
"id": {
"type": "Identifier",
"start": 4,
"end": 7,
"name": "sum"
},
"init": {
"type": "BinaryExpression",
"start": 10,
"end": 15,
"left": {
"type": "Literal",
"start": 10,
"end": 11,
"value": 1,
"raw": "1"
},
"operator": "+",
"right": {
"type": "Literal",
"start": 14,
"end": 15,
"value": 2,
"raw": "2"
}
}
}
],
"kind": "let"
}
],
"sourceType": "module"
}
复制代码
AST如何生成
AST是通过js parser
(解析器),将js源码转化成抽象语法树,主要分为两步:
词法分析
读取代码中的字符流,然后通过词法分析生成每个不可分割的最小语法单元token
,js中的保留关键字、运算符、括号、数字、字符串都能作为token
,如上面的例子,let
就为一个token
。最后,整个代码将被分割进一个数组tokens
中:
[
{ type: "Keyword", value: "let", range: [0, 3] },
{ type: "Identifier", value: "sum", range: [4, 7] },
{ type: "Punctuator", value: "=", range: [8, 9] },
{ type: "Numeric", value: "1", range: [10, 11] },
{ type: "Punctuator", value: "+", range: [12, 13] },
{ type: "Numeric", value: "2", range: [14, 15] },
{ type: "Punctuator", value: ";", range: [16, 17] },
]
复制代码
语法分析
遍历tokens
,识别语句(statement)和表达式(expression),转化成树形结构。同时,检查语法准确性,如果有错误,则抛出语法错误,没有错误则输出上述的AST。
AST在前端的应用
如上图,在前端工程化过程中,那些我们日夜相对的插件、工具,无不都是基于AST上开发:
- ES6+转ES5(babel)
- 模块打包(webpack)
- 代码校验(eslint)
- 代码美化(prettier)
- css预处理(less/sass)
- vue中template、react中jsx的编译
- taro等跨平台框架
- …
由此可见AST不可或缺,学习它不仅能加深我们平时对上面列举的各种神器原理的学习,在需要的时候,我们也能利用它来解决自己需求场景内的问题。