前端通过Babel插件修改请求

28 阅读3分钟

认识babel插件

babel主要由babylon, babel-traverse, babel-types, babel-generatorbabel-template几个模块组成,其中涉及到插件运行的模块为babel-traversebabel-types

babel首先通过babylon将代码转换成ast,然后通过babel-traverse遍历ast,这个过程中我们可以访问和修改所有类型的节点,而babel-types则相当于一个针对ast的类lodash库,可以协助我们进行各式各样的修改。

以下通过一个案例解释插件的构成:

// 源代码
function square(n){
-    return n*n
+    return n**2
}


// 插件
(
    // babel-types的引用
    {types: t}
) => {
    // return出去的就是babel-traverse的处理流程
    return {
        visitor: {
            // 监听表达式,不同类型的节点有不同的监听对象,可以自行去查阅文档
            BinaryExpression: {
                enter(path){
                    // path为当前ast节点,replaceWith替换当前的节点
                    path.replaceWith(
                        传入通过babel-types生成的新表达式
                        t.binaryExpression('**', path.node.left, t.numberLiteral(2)
                    )
                }    
            }                
        }
    }
}

上述案例表示的是将square函数返回的表达式进行修改的流程,可以看出babel的强大之处,能够让你对代码进行各种各样的修改,但是需要按照官方提供的方式来进行修改(除了一些简单的修改如字符串数字等修改),所以需要一定的学习成本。

案例实践

下面通过一个案例来进一步了解babel插件的开发

背景

在项目中接到一个需求,需要将项目中所有请求都添加一个前缀,由于有几个项目部署在同一域名不同子目录,因此无法通过nginx代理修改,只能考虑在项目中进行修改,并且将项目部署的子目录作为请求的前缀。

项目中使用到两种请求方式,一种是基于axios的工具库请求,可以通过中间件统一配置请求前缀;另一种是基于原生fetch的请求,见于公司级封装的私有导入导出组件库,且没有提供统一修改请求配置。

思路分析

基于目前的情况,并不能单纯的通过axios中间件实现所有请求的修改,也不可能每一个使用到导入导出的地方都进行修改,工作量大且效率低。要考虑一种可以覆盖两种请求工具的方案,babel的强大功能可以满足我们的需求。

项目中所有请求url都有一个通用前缀/web ,利用这一特性,我们可以在babel-traverse中监听到所有请求url的代码,然后给它加一个前缀,这样处理,打包之后的请求url代码就会改成有一个新前缀。

实现

module.exports = ({ types: t }) => {
  return {
    visitor:{
      // 监听字符串
      StringLiteral: {
        enter(path) {
          // 生产环境替换前缀
          if (
            process.env.NODE_ENV === 'production' &&
            path.node.value.startsWith('/web') &&
            // 由于每一次调用replaceWith修改节点,会引起babel-traverse对修改节点重新访问一次,
            // 因此判断其是否已被修改,否则会引起死循环
            path.parent.type !== 'BinaryExpression'
          ) {
            // 给字符串添加前缀,前缀已绑定到window的hostKey属性上,此处需要将字符串转换为表达式
            const expression = t.binaryExpression(
              '+',
              t.memberExpression(t.identifier('window'), t.identifier('hostkey')),
              path.node,
            );
            // 如果是作为jsx的属性传递,则需要先用jsxExpressionContainer包一层,即相当于你无法直接再jsx属性上写表达式,而需要先用大括号包一层
            if (path.parent.type === 'JSXAttribute') {
              path.replaceWith(t.jsxExpressionContainer(expression));
            } else {
              path.replaceWith(expression);
            }
          }
        },
      },
    },
  };
}

参考

babel插件手册

babel-types文档