书摘&心得
【核心】借助https://astexplorer.net/查看path.node的属性,而不是硬背属性
需求:
- 希望通过 babel 能够自动在 console.log 等 api 中插入文件名和行列号的参数,方便定位到代码。
- 思路:
- CallExrpession 节点有两个属性,callee 和 arguments,分别对应调用的函数名和参数, 所以我们要判断当 callee 是 console.xx 时,在 arguments 的数组中中插入一个 AST 节点
- 最终代码: ```javascript const ast = parser.parse(sourceCode, { sourceType: ‘unambiguous’, plugins: [‘jsx’] });
traverse(ast, {
CallExpression (path, state) {
if ( types.isMemberExpression(path.node.callee)
&& path.node.callee.object.name === ‘console’
&& [‘log’, ‘info’, ‘error’, ‘debug’].includes(path.node.callee.property.name)
) {
const { line, column } = path.node.loc.start;
path.node.arguments.unshift(types.stringLiteral(filename: (${line}, ${column})
))
}
}
});
注意节点属性都是通过[https://astexplorer.net/](https://astexplorer.net/)查看path.node的属性,所以AST语法树才是babel转换的核心
<a name="jqXmI"></a>
## 改造成babel插件
babel 支持 transform 插件,形式是函数返回一个对象,对象有 visitor 属性。<br />第一个参数可以拿到 types、template 等常用包的 api,不需要单独引入这些包。(这就是随处可见但没有任何显示引用的原因!)作为插件用的时候,并不需要自己调用 parse、traverse、generate,只需要提供一个 visitor 函数,在这个函数内完成转换功能。<br /><br />这就是babel-plugin-import的插件形式,最终的代码:
```javascript
const targetCalleeName = ['log', 'info', 'error', 'debug'].map(item => `console.${item}`);
module.exports = function({types, template}) {
return {
visitor: {
CallExpression(path, state) {
if (path.node.isNew) {
return;
}
const calleeName = generate(path.node.callee).code;
if (targetCalleeName.includes(calleeName)) {
const { line, column } = path.node.loc.start;
const newNode = template.expression(`console.log("${state.filename || 'unkown filename'}: (${line}, ${column})")`)();
newNode.isNew = true;
if (path.findParent(path => path.isJSXElement())) {
path.replaceWith(types.arrayExpression([newNode, path.node]))
path.skip();
} else {
path.insertBefore(newNode);
}
}
}
}
}
}
最后一步是利用@babel/core中的 transformSync 方法来调用该插件,实际在配置项目babel插件的时候,往往这一步是内置的,核心还是插件本身,也就是@babel/traverse、@babel/types、@babel/template以及最重要的AST语法树。