小春日和の秘密基地

记一次大量调整组件参数的经历

watch_later2021年06月11日
menu_book总字数:1.4k
access_alarm预计阅读时间:17分钟
local_offerJavaScriptlocal_offer前端

图片来源:pixiv:こっちの世界が気になるフレンズ 作者:m-くん

前言

Gitee pages上挂的这个静态博客,全都是技术文章博客被以政治敏感封禁了。询问客服,说我的博客里有敏感词,难道是日文?如果是日文的话,那Gitee确实不适合我。

背景

公司的管理后台系统中某几个类型的下拉筛选框全部都需要添加一个选项搜索的功能,虽然这个功能组件库已经提供,但涉及到数十个页面页面中上百个筛选框,修改起来仍是个不小的问题,经过考虑后得出了4种解决方案:

  1. 手动修改,缺点最多,工作量极大且容易出错,作为最后选择
  2. 正则表达式匹配筛选框代码块,进行替换,最终因为要匹配的情况太多,单靠正则出错的可能较大,放弃
  3. 为筛选框组件添加一个代理层进行处理,缺点是会导致组件行为和组件库的文档出现差异,对理解上造成混乱,因此作为备选
  4. 使用基于代码分析的代码修改工具,技术上没有缺点,就是之前没有接触过,这也是这篇文章的主题

2021年10月11日追记:也可通过patch-package修改UI库代码中的默认值来实现,但缺点同3。

开始

之前闲着没事逛掘金的时候看到了阿里名下一个叫做“GoGoCode”的AST处理库,正好中文文档对于刚开始接触这种工具的我也比较好理解。

公司项目使用React + antd UI组件库开发。

需要将A、B、C、D、E、F(涉及公司业务,这里使用字母代替)这几个类型的筛选框添加选项搜索的功能,要为这几种类型的筛选框添加showSearch以及optionFilterProp属性,同时还要将一部分代码中现有的showSearch={false}去掉。

首先按照文档中的示例

$_$1$_$2 相当于正则中的通配符,但是在这里只会匹配代码里有效的 AST 节点
$$$1 $$$2 则可以匹配剩下的节点,有点像 es6 里的 …,体现在AST结构里,$$$节点需要在数组属性内

.replace(
  '<Select $$$1 placeholder="A" $$$2>$$$3</Select>',
  '<Select showSearch $$$1 placeholder="A" $$$2 optionFilterProp="children">$$$3</Select>'
)

不出意外地失败了,猜测可能是$$$不会匹配空节点的情况,例如可能无法匹配<Select placeholder"a"></Select>这样的代码。
看来单靠replace方法是无法实现我需要的操作了,接下来只好使用each方法去遍历,这样就是不得不面对一个问题——需要手动获取并操作节点:

// glob是一个使用minimatch库进行文件匹配的库,具体使用请google ?? baidu
glob('src/**/*.@(js|ts|jsx|tsx)', (err, filePaths) => {
  if (err) throw err
  filePaths.forEach(filePath => {
    const result = $.loadFile(filePath, {
      parseOption: { plugins: ['jsx'] }
    })
      .find('<Select $$$1>$$$2</Select>') // 首先匹配参数和内容
      .each(item => {
        const attributes = getAttrs(item.match['$$$1'])
        // 获取jsx节点的参数map
        function getAttrs(nodes) {
          return Object.fromEntries(
            nodes.map(item => {
              // name为空,不知道是什么原因,这里返回一个null用于接下来滤掉
              if (item.name === undefined) return null
              return [
                item.name.name, // 属性名
                item.value ? item.value.value : undefined  // 属性值,若为jsx简写属性,则没有值
              ]
            })
            .filter(item => !!item)   // 滤掉name为空的项
          )
        }

        // 拿到全部筛选框的属性后,就可以进一步的缩小范围,得出需要修改的筛选框了

        // 筛选placeholder为A,B,C,D,E,F的筛选框。
        if (['A', 'B', 'C', 'D', 'E', 'F'].includes(attributes.placeholder)) {
          // item.node.openingElement属性上保存着当前节点的信息,关于这个属性文档上并没有介绍,需要手动debug查看
          // 这里需要将节点上已有的showSearch和optionFilterProp去掉,否则输出的代码会发生属性重复
          item.node.openingElement.attributes = item.node.openingElement.attributes
            .filter(item => ['showSearch', 'optionFilterProp'].includes(item.name.name) === false)

          /*
            向节点添加属性,该方法是在节点的core属性上的,在调用时第一个参数需要将节点本身传入,
            关于core属性,该属性带有很多操作节点的方法,不过这个属性仍然没有在文档中介绍,
            谷歌了一番发现了个貌似是同一家叫GoGoAST的库(作者为阿里员工):https://github.com/shuerguo999/gogoAST,
            可以参考GoGoAST的文档调用core属性上的方法
          */
          item.core.appendJsxAttr(item, {
            optionFilterProp: `'children'`,   // 注意这里的写法
            showSearch: '{true}'
          })
        }

        // 最后,拼接文件的输出路径
        const targetPath = path.join('output', filePath)

        // 导出文件
        try {
          // results是导出的代码内容
          $.writeFile(result, targetPath)
        } catch(e) {
          fs.mkdirSync(path.join(targetPath, '../'), { recursive: true })
          $.writeFile(result, targetPath)
        }
      })
  })
})

以上,就完成了使用GoGoCode批量修改代码的逻辑。

参见

版权声明:本文为原创文章,版权归 小春日和 所有

文章链接:https://koharubiyori.github.io/JavaScript/记一次大量调整组件参数的经历/

所有原创文章采用 署名-非商业性使用 4.0 国际 (CC BY-NC 4.0)

您可以自由转载和修改,但必须保证在显著位置注明文章来源,且不能用于商业目的。

north