Babel初体验

492 阅读5分钟

babel简介及简单使用

简介

babel是一个js语法的编译器,我们接触babel可能都是从webpack打包工具的babel-loader认识babel的。

众说周知,babel可以将ES6+代码转为ES5的代码。

主要作用有两个:语法转换补齐API

体验babel
  1. 先下载babel相关的包
npm i @babel/cli @babel/core @babel/preset-env -D

@babel/cli:babel的命令行工具,可以使用终端执行转换

@babel/core:babel的核心包,所以的流程处理逻辑都在这个包中

@babel/preset-env: babel的预设,可以理解为babel插件的集合,内置了很多转换插件。

  1. 使用

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源码转化为抽象语法树,主要分为两步:

  1. 词法分析

将整个代码字符串,分割成一个个不能细分的单词token

在线查看网站:esprima

const babel = 'babel' // 其中const、babel、= 、 'babel'都是一个token,一起组成tokens组。
  1. 语法分析

tokens进行递归封装,生成AST,按照不同的语法结构,把一组tokens组合成对象,建立分析语法单元之间的关系。

同时验证语法,语法错误,会抛出语法错误。

我们平时写代码时常常遇见的报错提示: image.png

编译流程

img 采集网络

对AST有了大概的了解后,大致的babel编译流程如下:

  1. parse:通过parser解析源文件为抽象语法树(AST)

    parse阶段的目的是把我们编写的源代码转换成机器能理解的AST。

  2. transfrom:遍历AST,调用各种transform插件对AST进行修改

    transform阶段处理的是上一阶段parse生成的AST,对AST进行深度优先遍历,遍历过程中处理不同的AST节点时会调用对应的visitor函数,visitor函数里可以对AST节点进行查增删改,返回新的AST树。

  3. 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

image.png

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
  • ······