
什么是babel
babel是一个编译器,负责将一些未纳入标准的js语法转换成符合当前浏览器(或node)的标准
可以毫不避讳的说,babel的出现让前端开发进入了新时期,新阶段
为了理解其独一无二的作用,下面我们看个例子

一个简单的方法将n=>n**2
转换成Math.pow(n,2)
—— 正则匹配法



但是对于下面这种格式的情形,正则表达式就出问题了,虽然它可以在字符串的世界里可以无敌,却识别不了词法和语法

独门秘技抽象语法树

语法树的构建





结点替换




复杂的使用完全可以使用语法树进行拆解,字符串类型直接被识别到了 不进行转换,nice!!

将BinaryExpression
的结点树替换成CallExpression
,再把树通过babel/generator
生成为代码,nice!!
plugins
)
)
前置知识

入口文件及对应的接收者

结点校验

结点构建

template
用types来构建复杂树非常反人类,所以template出现了

)
)
nodePath
)
)
Scope 
如何创建插件

)
)
)
)
例子
最简陋的实现
以我们代码中常用到的可选链
插件为例,我们来重新实现一下这个插件



虽然简陋但是实现了最简单的效果
但是再加一个属性,就立刻跪了


修复正常.符号也会触发?.转换问题
很显然,b.c不需要判断b,所以我们继续完善

再试运行一次,差强人意

但是如果再加个可选值

就会出现问题

存储计算结果
a==null?undefined:a.b
被重复声明了两次,这样会造成一个重复执行三目运算,显然是不合理的
所以我们再加以优化,如下所示

我们先生成一个当前作用域里独一无二的变量并注入到作用域,再边进行三目运算,边进行结果赋值,对运算结果进行存储

你以为这就完事大吉了,可还有个问题呢

防止undefined被声明 如let undefined = 2
为了防止真的有大哥对undefined进行赋值,我们要用babel的api在作用域中对undefined值进行build

结果

添加&&的实现方式
还没完 我们再玩一下,babel的可传入option,构造另一种形式

参数传入

运行结果


一个简单的插件就完成了。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| module.exports = function plugin( { types: t, template }, { loose = false } = {} ) { const buildLoose = template.expression(` (%%tmp%% = %%obj%%) && %%expr%% `);
return { name: "optional-chaining", visitor: { OptionalMemberExpression(path) { let {object, property} = path.node if (path.node.optional) { let tmp = path.scope.generateUidIdentifier("obj"); path.scope.push({id: tmp}); let undef = path.scope .buildUndefinedNode(); if(loose){ path.replaceWith(buildLoose({tmp,obj:object,expr:template.expression.ast`${tmp}.${property}`})) return } path.replaceWith( template.expression.ast`(${tmp} =${object})== null?${undef}:${tmp}.${property}` ) } else { path.replaceWith( template.expression.ast`${object}.${property}` ) } } } } };
|
另一种实现
这是原作者的实现,但是理解起来比较复杂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| module.exports = function plugin( { types: t, template }, { loose = false } = {} ) { const buildLoose = template.expression(` (%%tmp%% = %%obj%%) && %%expr%% `);
return { name: "optional-chaining", visitor: { OptionalMemberExpression(path) { let originalPath = path; let tmp = path.scope.generateUidIdentifier("obj"); path.scope.push({id: tmp});
while (!path.node.optional) { path = path.get("object"); }
let {object} = path.node;
let memberExpr = tmp; do { memberExpr = t.memberExpression( memberExpr, path.node.property, path.node.computed, );
if (path === originalPath) { break; }
path = path.parentPath; } while (true); if (loose) { path.replaceWith( buildLoose({ tmp, obj: object, expr: memberExpr, }) ) } else { let undef = path.scope .buildUndefinedNode();
originalPath.replaceWith( template.expression.ast` (${tmp} = ${object}) == null ? ${undef} : ${memberExpr} ` ) } } } } };
|
代码仓库地址

转换过程

所用模块


js到语法树——解析

词法分析

)
)
)
语法分析
)
)

)
语义分析
)
)
)
语法树的遍历——深度优先遍历
)
)
一个例子fn(3)->[6,fn]

知识补充:
二叉树的遍历一般使用循环或者递归进行。
以递归为例
深度优先遍历,会优先遍历left结点,没有left结点,再遍历right结点。
递是函数的入栈,归是函数的出栈,从首节点往下递,从尾结点网上归。
)
)
)
)
)
)
)

当CallExpression出栈之后,进行结点替换操作

)
)
)
)
)
)
)
结语
一句话与大家共勉:认识是不断螺旋上升的过程,理论的形成也是基于当时的时空,在不断变化的环境中,不断实践才能出真知。
参考
视频地址
原作者github