babel简介及简单使用
简介
babel是一个js语法的编译器,我们接触babel可能都是从webpack打包工具的babel-loader认识babel的。
众说周知,babel可以将ES6+代码转为ES5的代码。
主要作用有两个:语法转换、补齐API
体验babel
- 先下载
babel相关的包
npm i @babel/cli @babel/core @babel/preset-env -D
@babel/cli:babel的命令行工具,可以使用终端执行转换
@babel/core:babel的核心包,所以的流程处理逻辑都在这个包中
@babel/preset-env: babel的预设,可以理解为babel插件的集合,内置了很多转换插件。
- 使用
install好之后,src\index.js中复制以下代码。
const add = (a,b) => a+b
add(1,2)
上述代码使用了ES6规范,1. 箭头函数 2. const命名
打开终端,运行命令:npx babel src --out-dir dist 转换src文件夹下文件到dist目录
之后文件夹中出现dist目录,发现文件中代码并没有作转换。
babel默认没有转换的功能,将所有转换的逻辑分配到了各个插件中,方便扩展。
我们在根目录下新建babel.config.js文件,复制以下命令:
module.exports = {
presets: ["@babel/env"],
plugins: []
}
再次运行npx babel src --out-dir dist,可以看到代码已被转换成ES5的代码。
"use strict";
var add = function add(a, b) {
return a + b;
};
add(1, 2);
babel的工作流程
前置知识-AST
AST是抽象语法树,一种源代码的抽象表现形式(树状表现形式)。
ast在线查看网站:astexplorer
AST如何生成的
AST通过JS Parser(解析器),将js源码转化为抽象语法树,主要分为两步:
- 词法分析
将整个代码字符串,分割成一个个不能细分的单词token。
在线查看网站:esprima
const babel = 'babel' // 其中const、babel、= 、 'babel'都是一个token,一起组成tokens组。
- 语法分析
把tokens进行递归封装,生成AST,按照不同的语法结构,把一组tokens组合成对象,建立分析语法单元之间的关系。
同时验证语法,语法错误,会抛出语法错误。
我们平时写代码时常常遇见的报错提示:
编译流程
采集网络
对AST有了大概的了解后,大致的babel编译流程如下:
-
parse:通过parser解析源文件为抽象语法树(AST)
parse阶段的目的是把我们编写的源代码转换成机器能理解的AST。
-
transfrom:遍历AST,调用各种transform插件对AST进行修改transform阶段处理的是上一阶段parse生成的AST,对AST进行深度优先遍历,遍历过程中处理不同的AST节点时会调用对应的visitor函数,visitor函数里可以对AST节点进行查增删改,返回新的AST树。 -
generate:把转换后的AST输出目标代码generate阶段会把AST打印成目标代码字符串,并且生成sourcemap。不同的AST对应不同结构的字符串。
体验babel的API
- parse阶段的
@babel/parser,功能是把源码转成AST - transform阶段有
@babel/traverse,可以遍历AST,调用visitor函数修改AST,修改AST就要知道修改哪个节点的,就需要@babel/types来判断是是否自己要找的节点 - generate阶段把AST打印成目标代码字符串,需要
@babel/generate包
API
首先引入上方用到的包,由于没有webpack环境,这里使用node环境下的require进行导入。
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
const t = require('@babel/types')
const sourceCode = `const babel = 'babel'`
// 第一步 parse源代码转AST
const ast = parser.parse(sourceCode)
// 第二步 对AST树节点进行增删改 转成目标AST
traverse(ast, {
转换的逻辑处理
})
// 第三步 通过AST生成目标代码
const { code } = generate(ast)
转换AST的解析器有很多种,但大同小异,都由key-value组成的树形结构来表示源代码的信息,并且可以进行互转。
我们使用的babel插件就是在traverse到AST之后,对旧的AST进行增加、删除、修改节点,构造成新的AST,所以对AST修改的逻辑都写在traverse中。
比如要将const babel = 'babel'中的const替换为var,我们可以如下步骤:
首先在astexplorer网站,写入测试代码。
查看const对应的节点,可以看到节点是VariableDeclaration
babel为我们提供的traverse中,可以写入这样:
traverse(ast, {
VariableDeclaration(path) {
if(path.node.kind === 'const') {
path.node.kind = 'var'
}
}
})
简单的将const替换为了var,然后这是简单粗暴的替换,真正的替换还需要考虑多种情况,如作用域信息。
babel插件
使用了babel提供的关键包之后,真正编写插件的时候其实是不用做这些重复性工作的(源代码转ast,ast转目标代码),babel将所有的流程都整合到了核心包@babel/core中
在安装有babel的环境下,在文件中只要向外暴露一个方法,我们只需要做traverse的逻辑处理,其余的工作交给babel帮我们做。
解构的types就是上面说的@babel/types
module.exports = ({ types: t }) => {
return {
visitor: {
VariableDeclaration(path) {
if(path.node.kind === 'const') {
path.node.kind = 'var'
}
}
}
}
}
最后
AST的作用很广
- eslint、prettier中代码的修复
- typescript编译javascript
- vue的模板编译,有对应的解析器,vue内部自己实现的
- 将vue2转vue3
- ······