一、babel
babel插件可以通过三个函数来转换代码,它们是:
- parse:把代码 code 变成 AST
 - traverse:遍历 AST 修改代码
 - generator:把 AST 变成 code2
 
1.1 parse阶段
举个例子,以下为两个文件:
// main.js/** @Author: xxx* @Date: 2022-01-21 22:45:40* @LastEditors: xxx* @LastEditTime: 2022-01-21 22:45:40* @FilePath: /demo-1/main.js*/let a = 'let';let b = 123;
// index.tsimport { parse } from '@babel/parser';import traverse from '@babel/traverse';import generator from '@babel/generator';import fs from 'fs';fs.readFile('./main.js', function (error, data) {const ast = parse(code.toString(), { sourceType: 'module' });});
上面的代码是用fs模块将main.js中的文本引入后,使用babel的parse函数进行分析,此时会生成AST语法树。
AST是什么呢?本质上来说,它跟DOM树类似,就是树形节点的JavaScript对象。我们来看一下上面代码中会生成怎样的AST语法树。
以下是通过断点查看的ast对象,它是这样的:

主要观察我圈红的comments和program,两个属性分别对应main.js中的注释和代码。
// main.js/* ------ 以下为comments -------- *//** @Author: xxx* @Date: 2022-01-21 22:45:40* @LastEditors: xxx* @LastEditTime: 2022-01-21 22:45:40* @FilePath: /demo-1/main.js*//* -------- 以下为program ------ */let a = 'let';let b = 123;
其中comments属性是一个数组,里面有一个数组元素,它是这样的:

Babel将多行注释块解析成一个对象,这个对象的type是commentBlock,value是包含转义字符的具体注释文本。
program则是将代码块解析成一个对象,这个对象的body属性也是一个数组,数组中每个子项长这样:

kind——是声明的方式,有三种:letvarconstid——里面包含声明的变量信息,比如name:'a'表示声明的变量名init——里面包含值的信息,比如value:'let'表示变量名a的值为let,type:'StringLiteral'表示值的类型是字符串。type——类型,上图中类型是变量声明(VariableDeclaration)- …
 
总而言之,babel提供的parse函数是将一个文件中的代码给解析成AST语法树——这个语法树抽象了代码的各种信息。通过它,我们能够知道代码具体是使用哪种声明、声明后的值是什么类型、代码中的注释情况等等。
1.2 traverse阶段
traverse阶段是遍历AST语法树的阶段,当我们遍历语法树时,可以获取到每个节点里面的信息,也可以修改语法树的内容。以下的代码是将变量声明的方式从let修改成var。
// 遍历asttraverse(ast, {// 每进入节点时调用enter函数enter: item => {// 如果节点的类型是变量声明if (item.node.type === 'VariableDeclaration') {// 变量声明的种类是letif (item.node.kind === 'let') {// 设置成varitem.node.kind = 'var';}}},});
1.3 generator阶段
generator阶段是将语法树重新设置成代码的阶段。
// 将ast转化成代码const result = generator(ast, {}, data.toString());// 用w+的方式将代码写入到main2.js中fs.writeFile('./main2.js', result.code, { flag: 'w+' }, error => {console.log(error);});
1.4 借助工具将es6的代码转化成es5
我们可以通过一些别人写好的插件简化代码转化的过程,比如,通过@babel/core和@babel/preset-env将es6的代码转化成es5。
// es6.jslet a = [1, 2, 3, 4];let b = [...a];for (let item of a) {console.log(item);}let [c, ...rest] = a;
通过下面的代码,我们将es6.js中的代码转换成es5
// index.tsimport { parse } from '@babel/parser';import * as babel from '@babel/core';import * as fs from 'fs';fs.readFile('./es6.js', function (error, code) {const ast = parse(code.toString(), { sourceType: 'module' });const result = babel.transformFromAstSync(ast, code.toString(), {presets: ['@babel/preset-env'],});fs.writeFile('./es5.js', result.code, { flag: 'w+' }, () => {});});
转换后的结果:
// es5.js"use strict";var a = [1, 2, 3, 4];var b = [].concat(a);for (var _i = 0, _a = a; _i < _a.length; _i++) {var item = _a[_i];console.log(item);}var c = a[0],rest = a.slice(1);
1.5 小结
babel转换代码需要经过三个主要阶段:
- parse——解析代码成AST
 - traverse——遍历AST
 - generator——将AST转换成新的代码
 
AST语法树就是代码的抽象化后形成的类似于DOM树的对象。它将代码的类型、声明方式、变量名、值类型等所有信息都转化成对象的属性,通过AST,我们既可以知道代码的详细信息,还可以通过修改AST将代码转化成新的代码。
