前言
在日常的开发中我们经常会用到Select选择器,通常需要我们需要这样的数据。 options的每一项中都有label和value属性
export const actionsTypeOptions = [
{
label: '新增',
value: ActionsType.Add,
},
{
label: '编辑',
value: ActionsType.Edit,
},
{
label: '详情',
value: ActionsType.Detail,
},
{
label: '完善',
value: ActionsType.Perfect,
},
]
其中value值来自enum枚举值
/**
* 操作类型
*/
export enum ActionsType {
// 新增
Add,
// 编辑
Edit,
// 详情
Detail,
// 完善
Perfect,
}
当业我们的业务模块越来越多,枚举越来越多,在写options时就需要花费很多时间。
需求
- 将枚举文件转换为对应的options结构的文件
- 支持解析多个枚举文件
- 提供label和value值的查询方法
- 支持配置文件
思路
大致流程如下
graph TD
读取枚举文件 --> 生成ast --> 遍历树节点找到枚举值和注释信息 --> 通过模板引擎生成新的文件
源码
enum-opt工具地址 大家有兴趣可以看看
效果
npx enum-opt enum.ts
生成了enum-opt.ts文件
在其它文件就可以直接导入actionsTypeOptions使用了
技术点
读取枚举文件并生成ast,使用@babel/parser解析,@babel/traverse遍历节点。ast的信息非常多,我们处理一下,只取需要的数据。这里比较坑的是枚举上方的注释(例如前面enum.ts文件中的‘操作类型’注释)并不在ast遍历的结构中,它存放在ast的comments里。我是用枚举的起始行往上找最近的注释节点。
/**
* 读取枚举文件创建ast
* @param entry
* @param optionSuffix
*/
function createAsset(entry: string, optionSuffix) {
const enumPath = path.resolve(process.cwd(), entry)
const source = fs.readFileSync(enumPath, {
encoding: "utf-8"
})
const ast = parser.parse(source, {
sourceType: 'module',
ranges: false,
plugins: ['typescript']
})
const optionsData: OptionsAstItem[] = []
traverse.default(ast, {
TSEnumDeclaration({ node }) {
if (!inExclude(node.id.name)) {
optionsData.push(
{
name: `${toLowerCaseFirstLetter(node.id.name)}${optionSuffix}`,
options: getOptions(node.members, node.id.name),
startLine: node.loc.start.line,
sourceEnum: node.id.name,
comments: findComments(ast.comments, node.loc.start.line)
}
)
}
},
})
return optionsData
}
/**
* 查找注释
* @param comments 注释数组
* @param startLine
*/
function findComments(comments, startLine) {
const node = comments.find((item) => {
return item.loc.end.line === (startLine - 1)
})
if (node) {
return simplifyLabel(node.value)
} else return ''
}
模板引擎使用的是ejs,调用ejs的render方法得到最终的代码
/**
* 读取模板生成代码
* @param data
* @param fileName
* @param enumPath
*/
function generate(data, fileName, enumPath) {
const template = fs.readFileSync(path.resolve(__filename, '..', '..', 'template/options.ejs'), {
encoding: "utf-8"
})
const outputPath = path.resolve(process.cwd(), mergeConfig(config).outDir, fileName)
const code = ejs.render(template, { data, enumPath })
fs.writeFileSync(outputPath, code, {
encoding: "utf-8"
})
console.log('done:', outputPath)
}
ejs模板
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { <%= data.map((item, index) => index > 0 ? ' ' + item.sourceEnum : item.sourceEnum).join(',') %> } from '<%= enumPath %>'
<% data.forEach((item) => { -%>
/**
* <%= item.comments %>
*/
export const <%= item.name %> = [
<% item.options.forEach((option) => { -%>
{
label: '<%= option.label %>',
value: <%= option.value %>,
},
<% }) -%>
]
<% }) %>
enum-opt
安装
npm install enum-opt --save
使用
npx enum-opt yourfile.ts
或在package.json中script添加脚本
"scripts": {
"enum-opt": "enum-opt"
}
npm run enum-opt
通过配置文件入口设置可以实现转换多个文件,以下配置将会生成enum-options.ts和actions-options.ts
//enumoptconfig.json
{
"entry": {
"enum-options": "enum.ts",
"actions-options": "action-enum.ts"
}
}
提供查询label或value值的方法
import { valueToLabel, labelToValue } from 'enum-opt'
import { actionTypeOptions } from './util/enum-opt'
import { ActionType } from './enum'
valueToLabel(ActionType.Add, actionTypeOptions) // 新增
labelToValue('新增', actionTypeOptions) // 1
更多配置可以查看仓库主页