01.babel基础概念

169 阅读5分钟

是什么

babel 最开始叫 6to5,顾名思义是 es6 转 es5,但是后来随着 es 标准的演进,有了 es7、es8 等, 6to5 的名字已经不合适了,所以改名为了 babel ( / ˈbeɪbl /

babel在前端的定位

babel
  • JavaScript 编译器。
  • 把浏览器不认识的语法,编译成浏览器认识的语法。
  • 高级代码到低级代码,一种代码到另一种代码。
webpack
  • 代码到应用。
  • 现代 JavaScript 应用程序的静态模块打包器
babel和webpack的关系
  • babel可以单独使用
  • webpack不认识 vue 、react,需要通过babel转译
  • 浏览器不认识require,webpack实现了一套浏览器认识的 require
  • webpack可以打包编译node_modules和业务代码中的所有js
  • webpack 通过 babel-loader 使用 Babel, 其实是使用@babel/core 的 transform

用途

  • 转译 esnext、typescript、flow 等到目标环境支持的 js
  • 一些特定用途的代码转换。
    • 函数埋点
    • 自动国际化
    • 给代码加try catch
    • 小程序转译工具taro
    • 把vue代码转为react,或者把react转为vue
    • 等等等等.....
  • 代码的静态分析
    • linter工具,检查代码规范,如 eslint csslint
    • api文档自动生成工具,比如提取源码注释,生成文档
    • 压缩混淆,分析代码结构,进行删除死代码、变量名混淆、常量折叠等各种编译优化,生成体积更小、性能更优的代码
    • js 解释器,除了对 AST 进行各种信息的提取和检查以外,我们还可以直接解释执行 AST

编译流程与基础包

我们知道,babel 的主要编译流程是 parse、transform、generate。

  • parse 是把源码转成 AST
  • transform 是对 AST 做增删改
  • generate 是打印 AST 成目标代码并生成 sourcemap

编译流程对应的三个包

  • @babel/parser 解析源码成 AST,对应 parse 阶段
  • @babel/traverse 遍历 AST 并调用 visitor 函数,对应 transform 阶段
  • @babel/generate 打印 AST,生成目标代码和 sorucemap,对应 generate 阶段

其中,遍历过程中需要创建 AST,会用到:

  • @babel/types 创建、判断 AST
  • @babel/template 根据模块批量创建 AST

上面是每一个阶段的功能, babel 整体功能的入口是在:

  • @babel/core 解析配置、应用 plugin、preset,整体整体编译流程

插件和插件之间有一些公共函数,这些都是在:

  • @babel/helpers 用于转换 es next 代码需要的通过模板创建的 AST,比如 _typeof、_defineProperties 等
  • @babel/helper-xxx 其他的插件之间共享的用于操作 AST 的公共函数

当然,除了编译期转换的时候会有公共函数以外,运行时也有,这部分是放在:

  • @babel/runtime 主要是包含 corejs、helpers、regenerator 这 3 部分:
    • helper: helper 函数的运行时版本(不是通过 AST 注入了,而是运行时引入代码)
    • corejs: es next 的 api 的实现,corejs 2 只支持静态方法,corejs 3 还支持实例方法
    • regenerator:async await 的实现,由 facebook 维护

babel 做语法转换是自己实现的 helper,但是做 polyfill 都不是自己实现的,而是借助了第三方的 corejsregenerator

  • @babel/cli babel 的命令行工具,支持通过 glob 字符串来编译多个文件

常见AST分类

babeljs.io/docs/en/bab…

  • Literal 是字面量的意思,比如 let name = 'sam' ,sam 就是字面量。有很多字面量
    • NumericLiteral
    • BooleanLiteral
    • StringLiteral
    • TemplateLiteral
    • RegExpLiteral
    • BigintLiteral
    • NullLiteral
  • Identifier 是标识符的意思,变量名、属性名、参数名等各种声明和引用的名字,都是Identifer。
  • Statement 是语句的意思,比如 break、continue、debugger、return 或者 if 语句、while 语句、for 语句,还有声明语句,表达式语句等
  • Declaration 声明语句是一种特殊的语句,它执行的逻辑是在作用域内声明一个变量、函数、class、import、export 等。
  • Expression
  • Class 相关
    • 整个 class 的内容是 ClassBody,属性是 ClassProperty,方法是ClassMethod(通过 kind 属性来区分是 constructor 还是 method)。
  • Modules相关
  • Import 相关
  • Export相关
  • Program & Directive, program 是代表整个程序的节点
  • File & Comment
    • babel 的 AST 最外层节点是 File,它有 program、comments、tokens 等属性,分别存放 Program 程序体、注释、token 等,是最外层节点。
  • 公共属性
    • type
    • start
    • end
    • loc
    • leadingComments、innerComments、trailingComments: 表示开始的注释、中间的注释、结尾的注释,每个 AST 节点中都可能存在注释,而且可能在开始、中间、结束这三种位置,想拿到某个 AST 的注释就通过这三个属性。
  • 一些demo,查看示例

插件

使用方法
{
  "plugins": ["pluginA", ["pluginB"], ["pluginC", {/* options */}]]
}
plugin 函数返回对象形式

第一种是一个函数返回一个对象的格式,对象里有 visitor、pre、post、inherits、manipulateOptions 等属性。

export default function(api, options, dirname) {
  return {
    inherits: parentPlugin,
    manipulateOptions(options, parserOptions) {
        options.xxx = '';
    },
    pre(file) {
      this.cache = new Map();
    },
    visitor: {
      StringLiteral(path, state) {
        this.cache.set(path.node.value, 1);
      }
    },
    post(file) {
      console.log(this.cache);
    }
  };
} 
  • api 里包含了各种 babel 的 api,比如 types、template 等,这些包就不用在插件里单独引入了,直接取来用就行。
  • options 就是外面传入的参数
  • dirname 是目录名(不常用)

返回的对象有 inherits、manipulateOptions、pre、visitor、post 等属性。

  • inherits 指定继承某个插件,和当前插件的 options 合并,通过 Object.assign 的方式。
  • visitor 指定 traverse 时调用的函数。
  • pre 和 post 分别在遍历前后调用,可以做一些插件调用前后的逻辑,比如可以往 file(表示文件的对象,在插件里面通过 state.file 拿到)中放一些东西,在遍历的过程中取出来。
  • manipulateOptions 用于修改 options,是在插件里面修改配置的方式,比如 syntaxt plugin一般都会修改 parser options
plugin格式 直接返回对象

插件的第二种格式就是直接写一个对象,不用函数包裹,这种方式用于不需要处理参数的情况。

export default plugin =  {
  pre(state) {
    this.cache = new Map();
  },
  visitor: {
    StringLiteral(path, state) {
      this.cache.set(path.node.value, 1);
    }
  },
  post(state) {
    console.log(this.cache);
  }
};

插件集

插件集就是把很多插件集合起来一起返回,插件集也有两者格式

// 导出函数的方式
export default function(api, options) {
  return {
    plugins: ['pluginA'],
    presets: [['presetsB', { options: 'bbb'}]]
  }
}

// 直接导出对象的的方式
export default obj = {
  plugins: ['pluginA'],
  presets: [['presetsB', { options: 'bbb'}]]
}
插件和插件集顺序

babel 会按照如下顺序处理插件和 preset,这个顺序是 babel 的规定。

  1. 先应用 plugin,再应用 preset
  2. plugin 从前到后,preset 从后到前