🌮三、Babel基础
Babel 是一个 JavaScript 的转译器,其执行过程就是一个编译转换的过程。作为一个js转译器,babel暴露了很多 api,利用这些 api 可以完成源代码到 AST 的 parse,AST 的遍历与处理以及目标代码的生成。babel将这些功能的实现放到了不同的包里面,下面逐一介绍。
- @babel/parser解析源码得到AST
- @babel/traverse 遍历 AST节点
- @babel/types 用于构建AST节点和判断AST节点类型
- @babel/generate 打印 AST,生成目标代码和 sorucemap(即将ast转换成js代码)
babel的处理步骤:主要有三个阶段:解析(parse), 转换 (transform),生成(generate)。
- parse
将源码转成 AST,用到@babel/parser模块。
- transform
对AST 进行遍历,在此过程中对节点进行添加、更新及移除等操作。因此这是bebel处理代码的核心步骤,是我们的讨论重点,主要使用@babel/traverse和@babel/types模块。
- generate
打印 AST 成目标代码并生成 sourcemap,用到@babel/generate模块。
接下来我们来重点了解转换这一步,上面我们提到,转换的第一步是遍历AST。说到这里就不得不提到一个设计模式——访问者模式。
访问者模式,即将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式,简单来说,就是定义了用于在一个树状结构中获取具体节点的方法。当访问者把它用于遍历中时,每当在树中遇见一个对应类型时,都会调用该类型对应的方法。
🍰四、案例展示
从 babel7 开始,所有的官方插件和主要模块,都放在了 @babel 的命名空间下。从而可以避免在 npm 仓库中 babel 相关名称被抢注的问题,并且采用了Babel Monorepo风格的仓库。在测试之前需要安装@babel/core
、@babel/cli
、@babel/preset-env
yarn add @babel/core @babel/cli -D
@babel/core
是Babel 实现转换的核心,他是依赖能力更底层的@babel/parser
、 @babel/code-frame
、@babel/generator
、@babel/traverse
、@babel/types
等。
- @babel/parser: 接受源码,进行词法分析、语法分析,生成AST。
- @babel/traverse:接受一个AST,并对其遍历,根据preset、plugin进行逻辑处理,进行替换、删除、添加节点。
- @babel/generator:接受最终生成的AST,并将其转换为代码字符串,同时此过程也可以创建source map。
- @babel/types:用于检验、构建和改变AST树的节点
@babel/cli
是 Babel 提供的命令行,它可以在终端中通过命令行方式运行,编译文件。
@babel/preset-env' Babel
只是一个’编译器’你需要告诉他转换规则,需要在transformer,利用我们配置好的 plugins/presets把 Parser生成的 AST转变为新的 AST,即@babel/preset-env'
就是一套转换规则集合。
下图为转换流程le
t
声明转换为var
声明
const parser = require('@babel/parser'); const traverse = require('@babel/traverse'); const generator = require('@babel/generator'); const transToLet = code => { const ast = parser.parse(code); // 访问者对象 const visitor = { // 遍历声明表达式 VariableDeclaration(path) { if (path.node.type === 'VariableDeclaration') { // 替换 if (path.node.kind === 'var') { path.node.kind = 'let'; } } }, }; traverse.default(ast, visitor); // 生成代码 const newCode = generator.default(ast, {}, code).code; return newCode; }; const code = `const a = 1 var b = 2 let c = 3`; console.log(transToLet(code))
通过parse解析得到了ast
,具体如下:
Node { type: 'File', start: 0, end: 31, loc: SourceLocation { start: Position { line: 1, column: 0, index: 0 }, end: Position { line: 3, column: 9, index: 31 }, filename: undefined, identifierName: undefined }, errors: [], program: Node { type: 'Program', start: 0, end: 31, loc: SourceLocation { start: [Position], end: [Position], filename: undefined, identifierName: undefined }, sourceType: 'script', interpreter: null, body: [ [Node], [Node], [Node] ], directives: [] }, comments: [] }
执行
node babel.js
输出
const a = 1; let b = 2; let c = 3;
可见var
都变成了let
🍔五、手写babel插件
该插件为superLog
,源码如下:
const generator = require('@babel/generator'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse'); const types = require('@babel/types'); const trans = require('./trans.js') const addNode = code => { const ast = parser.parse(code); // 访问者对象 const visitor = { // 遍历调用表达式 CallExpression(path) { const { callee } = path.node; if (types.isCallExpression(path.node) && types.isMemberExpression(callee)) { const { object, property } = callee; if (object.name === 'console' && property.name === 'log') { const newArg = trans(path.node.arguments); path.node.arguments = [...newArg]; } } }, }; traverse.default(ast, visitor); // 生成代码 const newCode = generator.default(ast, {}, code).code; return newCode; };
//callee
Node { type: 'MemberExpression', start: 86, end: 97, loc: SourceLocation { start: Position { line: 8, column: 0, index: 86 }, end: Position { line: 8, column: 11, index: 97 }, filename: undefined, identifierName: undefined }, object: Node { type: 'Identifier', start: 86, end: 93, loc: SourceLocation { start: [Position], end: [Position], filename: undefined, identifierName: 'console' }, name: 'console' }, computed: false, property: Node { type: 'Identifier', start: 94, end: 97, loc: SourceLocation { start: [Position], end: [Position], filename: undefined, identifierName: 'log' }, name: 'log' } }
//path.node.arguments的值
[ Node { type: 'Identifier', start: 98, end: 99, loc: SourceLocation { start: [Position], end: [Position], filename: undefined, identifierName: 'a' }, name: 'a' }, Node { type: 'MemberExpression', start: 101, end: 108, loc: SourceLocation { start: [Position], end: [Position], filename: undefined, identifierName: undefined }, object: Node { type: 'MemberExpression', start: 101, end: 106, loc: [SourceLocation], object: [Node], computed: false, property: [Node] }, computed: false, property: Node { type: 'Identifier', start: 107, end: 108, loc: [SourceLocation], name: 'b' } }, Node { type: 'CallExpression', start: 110, end: 118, loc: SourceLocation { start: [Position], end: [Position], filename: undefined, identifierName: undefined }, callee: Node { type: 'MemberExpression', start: 110, end: 116, loc: [SourceLocation], object: [Node], computed: false, property: [Node] }, arguments: [] } ]
新建trans,js文件
const types = require('@babel/types'); // 获取父辈节点并拼接 const getNodeName = node => { const getPreValue = node => { if (node.object && node.property) { return `${node.property.name}.${getPreValue(node.object)}`; } else { return node.name; } }; return getPreValue(node) .split('.') .reverse() .map((item, index, arr) => (index === arr.length - 1 ? item : `${item}.`)) .join(''); }; const actionMap = { // 调用表达式 CallExpression: node => getNodeName(node.callee), // 标识符 Identifier: node => node.name, // 成员表达式 MemberExpression: node => getNodeName(node), // 字符串 StringLiteral: node => '', }; const trans = list => { //初始化一个数组长度为传的参数2倍 let res = new Array(list.length * 2).fill(null); list.forEach((node, index) => { res[index * 2 + 1] = node; console.log(node.type,'1111111111111111') console.log(node,'22222222222222222') const strNodeName = actionMap[node.type](node); res[index * 2] = strNodeName ? types.stringLiteral(`${strNodeName}`) : ''; }); return res; }; module.exports = trans;
其中node.type分别是:
Identifier MemberExpression CallExpression
最后打印结果为
const obj = { a: { b: 'xiaom' }, fn: () => null }; const a = 2; console.log("a", a, "obj.a.b", obj.a.b, "obj.fn", obj.fn());