如果不是近期项目需要,急需通过babel这种自动化手段解决一些项目场景,可能还将会在很长一段时间被babel蒙在鼓里,什么ast一大串看不懂的玩意,对此深有误解,以为门槛太高,不敢轻易尝试,心想着以我目前的技术(菜鸟水平)写不出这么高深的东西。但是身处前端岗位,岂有不卷的道理,终于皇天不负有心人,今天也算是摸到了点babel插件的门道,真香;
背景
先来说说此次研究的背景,为啥突然卷起了babel插件。其实需求很简单,验收标准中有一项功能————所有表单输入框要做特殊字符处理,好家伙,所有?听到需求的我都惊呆了。然后搜索了一下Input,好家伙,上百个文件,都是直接从antd引入的input,即便是封装一个自己的Input框,也要改上百个文件,太吓人了;然后就整理了一下目前的诉求:
- 批量处理,把antd input换成 MyInput
- 解决后面的新人不知道这需求,还是直接从antd引入,需要二次加工的问题 思前想后,再没有比babel plugins更合适了,一键式解决所有后顾之忧;剩下的就是理清babel插件需要做的事情(把antd input 替换成自己封装的),步骤如下:
- 遇到import { Input } from 'antd',则将Input删除
- 基于ast语法生成语句:import myInput from 'myInputPath';
- 在下一行的位置插入生成后的语句;
- 灵活性,假如将来不只替换input,替换目标不只是antd,于是提供配置参数,动态获取 原理不多说了,直接上成品代码, ast在线解析入口
/**
* 1,useage,在babelrc添加
* {
* plugins: [
* ['./plugins/antdInputToCustom', {
* 'antd': { // 需要转换的ui框架
* 'Input': '@/component/Input', 需要替换的组件
* }
* }]
* ]
* }
*
* 2, state,在babelrc中第二个参数的值,通过state.opts获取
*
*
* @param t
* @returns {{name: string, visitor: {ImportSpecifier(*, *): void}}}
*/
module.exports = function ({ types: t, template: template }) {
return {
name: 'antd-input-to-custome',
visitor: {
ImportSpecifier(path, state) {
const parentBody = path.parentPath.parentPath.get('body');
const uiType = path.parent.source.value;
const componentType = path.node.local.name;
const optional = state.opts || {};
if (optional.hasOwnProperty(uiType) && optional[uiType][componentType]) {
// 基于template写法,推荐,简单
const templaStr = template(`import LEFT from RIGHT`);
path.replaceWidth(templaStr({
LEFT: componentType,
RIGHT: optional[uiType][componentType],
}))
/*path.remove(); // 1. 将匹配目标删除
// 2. types写法,基于ast语句生成 import xxx from 'xxx'
const node = t.ImportDeclaration(
[
t.ImportDefaultSpecifier(t.Identifier(componentType)),
],
t.StringLiteral(optional[uiType][componentType]) // 替换成目标文件
);
// 3. 在目标位置的下一行插入语句
parentBody[0].insertAfter(node);
*/
}
},
},
};
};
umi集成babel的坑
普通的webpack项目,配置babel直接在项目根目录添加一个.babelrc,plugins写成本地插件的相对路径就完事了,但是umi这种零配置,开箱即用的框架,由于封装性太多,直接添加babelrc/babel.config.js不生效,那只能重温官网api,查看对外暴露的配置,终于发现一个叫extraBabelPlugins的,往项目里一加,
import myBabelPlugin from 'myBabelPluginPath'; // 本地插件路径
extraBabelPlugins: [
myBabelPlugin, // 必须是import进来的实例,path.resolve('相对路径')将会报错;
]
添加完extraBabelPlugins,发现一个问题,插件是运行了,但是转换不彻底,经过一番周折,发现是跟dynamicImport冲突了,dynamicImport先于babel插件执行,导致动态加载部分的转换就失败了,关闭dynamicImport:false后虽然能正常,但是只生成一个umi.js,显示包太大了,不是我们想要的结果; 解决umi集成babelrc的终极方案(任选其一):
- 新建.env文件,开启umi对babelrc的识别开关,加入:BABELRC=true
- 新建babel.config.js,添加plugins
附码
## babel.config.js
module.exports = {
"plugins": [
["./plugins/antdInputToCustom", {
"antd": {
"Input": "@/components/Input"
}
}]
]
}
## .env
BABELRC=true
总结
umi集成babel方案,归根到底是对webpack的上层封装,最终的配置项都会解析成webpack配置,所以单看umi api并不能很好地解决我们的问题,问题的本质还是得回到webpack本身,webpack纯天然支持.babelrc/babel.config.js,所以抛开umi api配置来看,如何让umi识别babelrc就是一个解决思路的方向,之前自己也是跑偏了方向,较真怎么通过umi自带功能如何解决,转换不彻底时如何调整插件执行顺序等,折腾了很久,绕了这么多弯路,结果就是这么一个BABELRC的问题,果然卷得还不够,思路不够开阔,继续卷吧