是什么
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 都不是自己实现的,而是借助了第三方的 corejs、regenerator)
- @babel/cli babel 的命令行工具,支持通过 glob 字符串来编译多个文件
常见AST分类
- 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 的规定。
- 先应用 plugin,再应用 preset
- plugin 从前到后,preset 从后到前