babel 插件开发尝试
在阅读本文之前, 您应该已经知识如何安装及使用过babel,并且大概了解过AST是一个什么东西
背景知识
babel是一个用js写的编译器,用于将一种js编译成另外一种js。和大多数编译器一样,babel会先扫描输入的js代码,进行词法分析,生成原始的AST,在此AST基础上进行变换(transform) ,再由新的AST转化为目标代码。
详细的介绍请参考 babel-handbook
例子
这里主要讲述一个具体的例子,使用编译器把一个React 0.14+的stateless component转化为React 0.13 的component。
即把
(props)=>{
return <div/>
}
转化成
class ID extends React.Component {
render(){
const props = this.props;
return <div/>
}
}
主要是有一个0.13版本的旧系统,然后有一些只适配0.14+的库,要让旧代码去适配新的库,于是写了这样一个适配器。
visitor
所谓的stateless component,就是一个返回JSX element的函数,有一个入参表示 props。所以我们的visitor入口都是函数,而函数在js中有三种表示方式,一种是FunctionExpression,一种是FunctionDeclaration,es6新增ArrowFunctionExpression,所以我们在这三种方式中都要运行我们的插件visitor
转化条件
在ES6的语法下,函数体有两种表示方式,一种是用花括号,例如前面的例子,一种不用,即()=><div/>
,在这两种情景之下,我们要取出函数最后返回的表达式(expression),如果这个表达式是一个JSXExpression,我们认为这个函数是一个stateless component
if(t.isBlockStatement(body)){ // 函数有花括号
fnBody = body.body;
fnLastBlock = fnBody.slice(-1)[0];
ifCondition = t.isReturnStatement(fnLastBlock) && t.isJSXElement(fnLastBlock.argument);
}else{// 函数无花括号,只有箭头函数有这种情况,例如 ()=><div/>
fnBody = t.returnStatement(body);
fnLastBlock = body;
ifCondition = t.isJSXElement(fnLastBlock);
}
template
在handbook中一开始是说用babel-types
去构建一个目标语法树的,本人曾经试过,对于一些简单的例子还好,做过一次之后对于js语法的构成会有较深刻的理解。对于具体应用,推荐用babel-template
更加高效。
const ReactClassTemplate = template(`
class ID extends React.Component {
render(){
TPL_PROPS
TPL_FNBODY
}
}
`);
坑1-树的重复遍历
如果认真阅读过handbook,里面也有提过这个问题。具体说来,你的目标代码中的render
也是一个函数,也会进入visitor中,于是会不停地重复遍历。正如我们遍历一课树的时候,要标记好节点的访问情况那样。所以我们要设定一个循环的中止条件。在这里我是用代码行数的
let codePosition = 0;
if(body.loc){
codePosition = body.loc.start.line; //有代码行数,表明是从文件中读取出来的
}else{
return; //无代码行数,表明是template里面的ast
}
//......
if(!fnLastBlock || hasVisit[codePosition]) return; //空函数 || 已经访问过
hasVisit[codePosition] = true;
坑2-我原来就是 Component
这个错误比较低级,但也会犯,如果一个component原来就是class的形式,那么里面确实有一个函数,这会导致重复两次的AST transform。在这里只能一些语法特性去检测了。
if( //原来就是React组件
(t.isCallExpression(parent) &&
parent.arguments &&
parent.arguments[0] &&
parent.arguments[0].object &&
parent.arguments[0].object.name === 'React')
||
( node.id && node.id.name === 'render' )
){
return;
}
总结与迁移
写过babel插件之后,对于js语法的理解更加深刻了。比如在阅读node文档时,看到类似这样的语句while (null !== (chunk = readable.read())) {
,以前会觉得很绕,但现在回过头去理解,任何的表达式都能计算(evalute)出一个值,包含assignmentExpression,所以这个语法也是很好理解的。
本插件的写法主要是参考 babel-plugin-import的,在学习完之后也能给它提PR了(#67 #87)。嗯,取之于社区,反馈于社区
本文提及的代码在 https://github.com/p2227/babel-plugin-react-stateless-component
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 手机屏幕适配 原理与解决方案
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论