这次给大家带来babel转换es6方法实现,babel转换es6的注意事项有哪些,下面就是实战案例,一起来看一下。
我们在项目中都是通过配置插件和预设(多个插件的集合)来转换特定代码,例如env、stage-0等。
实际上babel可以通过自定义插件的方式实现任何代码的转换,接下来我们通过一个“把es6的 class 转换为es5”的例子来了解一下babel。
内容如下:
webpack环境配置
大家应该都配置过babel-core这个loader,它的作用是提供babel的核心api,实际上我们的代码转换都是通过插件来实现的。
接下来我们不用第三方的插件,自己实现一个es6类转换插件。先执行以下几步初始化一个项目:
npm install webpack webpack-cli babel-core -d
新建一个webpack.config.js
配置webpack.config.js
如果我们的插件名字想叫transform-class,需要在webpack配置中做如下配置:
接下来我们在node_modules中新建一个babel-plugin-transform-class的文件夹来写插件的逻辑(如果是真实项目,你需要编写这个插件并发布到npm仓库),如下图:
红色区域是我新建的文件夹,它上面的是一个标准的插件的项目结构,为了方便我只写了核心的index.js文件。
如何编写bable插件
babel插件其实是通过ast(抽象语法树)实现的。
babel帮助我们把js代码转换为ast,然后允许我们修改,最后再把它转换成js代码。
那么就涉及到两个问题:js代码和ast之间的映射关系是什么?如何替换或者新增ast?
好,先介绍一个工具:astexplorer.net:
这个工具可以把一段代码转换为ast:
如图,我们写了一个es6的类,然后网页的右边帮我们生成了一个ast,其实就是把每一行代码变成了一个对象,这样我们就实现了一个映射。
再介绍一个文档: babel-types :
这是创建ast节点的api文档。
比如,我们想创建一个类,先到astexplorer.net中转换,发现类对应的ast类型是 classdeclaration 。好,我们去文档中搜索,发现调用下面的api就可以了:
创建其他语句也是一样的道理,有了上面这两个东西,我们可以做任何转换了。
下面我们开始真正编写一个插件,分为以下几步:
在index.js中export一个函数
函数中返回一个对象,对象有一个visitor参数(必须叫visitor)
通过astexplorer.net查询出 class 对应的ast节点为 classdeclaration
在vistor中设置一个捕获函数 classdeclaration ,意思是我要捕获js代码中所有 classdeclaration 节点
编写逻辑代码,完成转换
module.exports = function ({ types: t }) {
return {
visitor: {
classdeclaration(path) {
//在这里完成转换
}
}
};
}
代码中有两个参数,第一个 {types:t} 东西是从参数中解构出变量t,它其实就是babel-types文档中的t(下图红框),它是用来创建节点的:
第二个参数 path ,它是捕获到的节点对应的信息,我们可以通过 path.node 获得这个节点的ast,在这个基础上进行修改就能完成了我们的目标。
如何把es6的class转换为es5的类
上面都是预备工作,真正的逻辑从现在才开始,我们先考虑两个问题:
我们要做如下转换,首先把es6的类,转换为es5的类写法(也就是普通函数),我们观察到,很多代码是可以复用的,包括函数名字、函数内部的代码块等。
如果不定义class中的 constructor 方法,javascript引擎会自动为它添加一个空的 constructor() 方法,这需要我们做兼容处理。
接下来我们开始写代码,思路是:
拿到老的ast节点
创建一个数组用来盛放新的ast节点(虽然原class只是一个节点,但是替换后它会被若干个函数节点取代) 初始化默认的 constructor 节点(上文提到,class中有可能没有定义constructor)
循环老节点的ast对象(会循环出若干个函数节点)
判断函数的类型是不是 constructor ,如果是,通过取到数据创建一个普通函数节点,并更新默认 constructor 节点
处理其余不是 constructor 的节点,通过数据创建 prototype 类型的函数,并放到 es5fns 中
循环结束,把 constructor 节点也放到 es5fns 中
判断es5fns的长度是否大于1,如果大于1使用 replacewithmultiple 这个api更新ast
module.exports = function ({ types: t }) {
return {
visitor: {
classdeclaration(path) {
//拿到老的ast节点
let node = path.node
let classname = node.id.name
let classinner = node.body.body
//创建一个数组用来成盛放新生成ast
let es5fns = []
//初始化默认的constructor节点
let newconstructorid = t.identifier(classname)
let constructorfn = t.functiondeclaration(newconstructorid, [t.identifier('')], t.blockstatement([]), false, false)
//循环老节点的ast对象
for (let i = 0; i < classinner.length; i++) {
let item = classinner[i]
//判断函数的类型是不是constructor
if (item.kind == 'constructor') {
let constructorparams = item.params.length ? item.params[0].name : []
let newconstructorparams = t.identifier(constructorparams)
let constructorbody = classinner[i].body
constructorfn = t.functiondeclaration(newconstructorid, [newconstructorparams], constructorbody, false, false)
}
//处理其余不是constructor的节点
else {
let prototypeobj = t.memberexpression(t.identifier(classname), t.identifier('prototype'), false)
let left = t.memberexpression(prototypeobj, t.identifier(item.key.name), false)
//定义等号右边
let prototypeparams = classinner[i].params.length ? classinner[i].params[i].name : []
let newprototypeparams = t.identifier(prototypeparams)
let prototypebody = classinner[i].body
let right = t.functionexpression(null, [newprototypeparams], prototypebody, false, false)
let prototypeexpression = t.assignmentexpression("=", left, right)
es5fns.push(prototypeexpression)
}
}
//循环结束,把constructor节点也放到es5fns中
es5fns.push(constructorfn)
//判断es5fns的长度是否大于1
if (es5fns.length > 1) {
path.replacewithmultiple(es5fns)
} else {
path.replacewith(constructorfn)
}
}
}
};
}
优化继承
其实,类还涉及到继承,思路也不复杂,就是判断ast中没有 superclass 属性,如果有的话,我们需要多添加一行代码 bird.prototype = object.create(parent) ,当然别忘了处理 super 关键字。
打包后代码
运行 npm start 打包后,我们看到打包后的文件里 class
语法已经成功转换为一个个的es5函数。
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
js实现停留在界面提示框vue
cli升级webpack4步骤详解
vue 单页应用前端路由如何配置
以上就是babel转换es6方法实现的详细内容。