认识babel插件
babel主要由babylon
, babel-traverse
, babel-types
, babel-generator
和babel-template
几个模块组成,其中涉及到插件运行的模块为babel-traverse
和babel-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);
}
}
},
},
},
};
}