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

以上是 babel 插件开发尝试 的全部内容, 来源链接: utcz.com/z/264715.html

回到顶部