一个工具实现typescript枚举文件生成options选项数据

1,238 阅读2分钟

前言

在日常的开发中我们经常会用到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文件 image.png 在其它文件就可以直接导入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

更多配置可以查看仓库主页