babel
是什么
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
可以做什么
- 语法转换
- 通过 polyfill 方式在目标环境中添加缺失的特性(eg:coreJs)
- 源码转换(codemods)
运行原理
Babel 的三个主要处理步骤分别是:解析(parse),转换(transform),生成(generate) babel 插件就是在转换过程中起作用的,即将解析完成的语法树对象按照自己的目的进行处理,然后再进行代码生成步骤。
配置文件分类
-
babel 接受的配置文件分很多种,包括
- babel.config.json
- babelrc.json
- package.json
常用 options 字段说明
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
"@babel/plugin-transform-runtime",
"react-hot-loader/babel",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-syntax-dynamic-import" ],
"env":
{ "production":
{ "plugins": ["transform-remove-console"] }
}
}
- env:指定在不同环境下使用的配置。比如 production 和 development 两个环境使用不同的配置,就可以通过这个字段来配置。env 字段的从 process.env.BABEL_ENV 获取,如果 BABEL_ENV 不存在,则从 process.env.NODE_ENV 获取,如果 NODE_ENV 还是不存在,则取默认值"development"
- plugins:要加载和使用的插件列表,插件名前的 babel-plugin-可省略;plugin 列表按从头到尾的顺序运行
- presets:要加载和使用的 preset 列表,preset 名前的 babel-preset-可省略;presets 列表的 preset 按从尾到头的逆序运行(为了兼容用户使用习惯) 同时设置了 presets 和 plugins,那么 plugins 的先运行;每个 preset 和 plugin 都可以再配置自己的 option 还有 stage-0 到 stage-4 的标准成形之前的各个阶段,这些都是实验版的 preset,建议不要使用。(具体每个阶段有些什么文末有简单介绍)
涉及到的 packages
- 核心包
- @babel/core
babel 转译器本身,提供了 babel 的转译 API,如 babel.transform 等,用于对代码进行转译。像 webpack 的 babel-loader 就是调用这些 API 来完成转译过程的。
- babylon:js 的词法解析器
- babel-traverse:用于对 AST(抽象语法树,想了解的请自行查询编译原理)的遍历,主要给 plugin 用
- babel-generator:根据 AST 生成代码
- 功能包
- babel-types:用于检验、构建和改变 AST 树的节点
- babel-template:辅助函数,用于从字符串形式的代码来构建 AST 树节点
- babel-plugin-xxx:babel 转译过程中使用到的插件,其中 babel-plugin-transform-xxx 是 transform 步骤使用的
- babel-helpers:一系列预制的 babel-template 函数,用于提供给一些 plugins 使用
- babel-code-frames:用于生成错误信息,打印出错误点源代码帧以及指出出错位置
- @babel/preset: 是一系列插件的集合,包含了我们在 babel6 中常用的 es2015,es2016, es2017 等最新的语法转化插件,允许我们使用最新的 js 语法,比如 let,const,箭头函数等等,但不包括 stage-x 阶段的插件。
- babel-polyfill:JS 标准新增的原生对象和 API 的 shim,实现上仅仅是 core-js 和 regenerator-runtime 两个包的封装。使用 preset-env 能将最新的语法转换为 ecmascript5 的写法,当我们需要使用新增的全局函数(比如 promise, Array.from)和实例方法(比如 Array.prototype.includes )时就需要引入 polyfill
- babel-runtime:功能类似 babel-polyfill,一般用于 library 或 plugin 中,因为它不会污染全局作用域
- 工具包
- babel-cli:babel 的命令行工具,通过命令行对 js 代码进行转译
- babel-register:通过绑定 node.js 的 require 来自动转译 require 引用的 js 代码文件
-
@babel/polyfill 和 @babel/preset-env 的关系 @babel/preset-env 中与 @babel/polyfill 的相关参数有 targets 和 useBuiltIns 两个
-
targets:
支持的目标浏览器的列表 -
useBuiltIns: 参数有 “entry”、”usage”、false 三个值。默认值是 false,此参数决定了 babel 打包时如何处理@babel/polyfilll 语句。
-
“entry”: 需要手动 import '@babel/polyfill',根据 browserlist 中浏览器版本的支持,将 polyfill 拆分引入浏览器不支持的 polyfill。这样会导致实际用不到的 polyfill 也会被打包到输出文件,导致文件比较大。
-
“usage”: 不需要手动在代码里写 import‘@babel/polyfilll’,打包时会自动根据实际代码的使用情况,结合 targets 引入代码里实际用到 部分 polyfilll 模块
-
false: 不启用 polyfill,如果 import '@babel/polyfill', 会无视 browserlist 将所有的 polyfill 加载进来。
- 新版本的 Babel,会提示直接引入 core-js 或者 regenerator-runtime/runtime 来代替@babel/polyfill。
需要注意的是在 webpack 打包文件配置的 entry 中引入的 @babel/polyfill 不会根据 useBuiltIns 配置任何转换处理。
总结:在业务项目中需要用到 polyfill 时, 可以使用和 @babel/preset-env 的 targets 和 useBuiltIns: usage 来根据目标浏览器的支持情况,按需引入用到的 polyfill 文件。
-
-
coreJs
- corejs 是什么
- 是一个 js 标准库的 polyfill,其支持最新的 es 标准,es 标准的提案,web standard
- 最大程度的模块化,可以只引入需要的部分
- 可以被使用而不污染全局命名空间
- 和 babel 紧密结合,做了很多相关优化
- corejs 最新也是推荐版本为 core-js@3
- core-js 怎么单独使用:
import 'core-js/features/array/from'; // <- at the top of your entry point
import 'core-js/features/array/flat'; // <- at the top of your entry point
import 'core-js/features/set'; // <- at the top of your entry point
import 'core-js/features/promise'; // <- at the top of your entry point
Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
[1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
- babel 和 core-js 结合使用
test: /\.js|jsx$/, exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage', // 按需引入,使用polyfill
corejs: { version: 3 },// 不写报错,找不到core-js
targets: { //兼容
"chrome": "58",
"ie": "9",
}
//["last 2 versions", "safari >= 7"]
}
]
]
}
}
babel 影响 treeshaking 的问题
以前你可能需要用 babel 来将 ES6 的模块语法转换为 AMD、CommonJS、UMD 之类的模块化标准语法,但是现在 webpack 已经把这个事情做了,所以就不需要 babel 来做了,但是 babel 配置项中的 modules 默认值是 commonjs,所以你需要将 modules 设置为 false 才行,不然就冲突了。
// .babelrc - webpack v1.*
{
"presets": [
"env",
"stage-0"
]
}
// .babelrc - webpack v2.* - v3.*
{
"presets": [
["env", {
"modules": false
}],
"stage-0"
]
}
很明显,一眼就能看出相对于 v1.*的版本,v2.*或者 v3.*版本多了"modules": false 这项配置。v1.*版本需要 babel 来将 ES6 的模块语法转换为 AMD、CommonJS、UMD 之类的模块化标准语法,但是现在 webpack 已经把这个事情做了,所以就不需要 babel 来做了,但是 babel 配置项中的 modules 默认值是 commonjs,所以需要将 modules 设置为 false 才行,不然就冲突了。并且,treeshaking 的前提是 esmodule 模块话,如果转成 commonjs 是不利于 treeshaking 的
如何区分 Babel 中的 stage-0,stage-1,stage-2 以及 stage-3
{
"presets": [
"es2015",
"react",
"stage-0"
],
"plugins": []
}
stage-x:stage-0、stage-1、stage-2、stage-3、stage-4 分别对应的就是进入标准之前的 5 个阶段,不同 stage-x 之间存在依赖关系,数字越小,阶段越靠后,靠后阶段包含前面阶段所有的功能,简单理解就是 stage-0 包含 stage-1/2/3 的内容,所以如果你不知道需要哪个 stage-x 的话,直接引入 stage-0 就好了。PS: babel-preset-stage-4 已经整合入 Presets 不单独发布了。
- 法力无边的 stage-0
stage-0 包含 stage-1, stage-2 以及 stage-3 的所有功能,另外再添加了
-
transform-do-expressions,在 jsx 中我们一般用三元表达式来做条件判断,有了该插件之后我们就可以如下来使用 if/else 了
return ( <div > {do { if(name == 'a') { <AComponent/>; }else if(name == 'b') { <BComponent/>; }else { <CComponent/>; } } }} </div> ) -
transform-function-bind 它提供了 :: 操作符来简化上下文切换操作, 作为 bind , call 的替代方案
obj::func // 等于 func.bind(obj) obj::func(val) // 等于 func.call(obj, val) ::obj.func(val) // 等于 func.call(obj, val) ``` 在react里,你甚至可以这样来绑定自己 ``` <Components onClick={::this.change}> ```
- stage-1 stage-1 则是囊括了 2 和 3 插件,另外增加了
- transform-class-constructor-call (已不建议使用,不做介绍)
- transform-export-extensions:export 方法的扩展
export * as ns from 'mod'; export v from 'mod';
- stage-2
同理,他拥有 3 的插件,还有
-
syntax-dynamic-import
用于动态 import ,我们知道 import 语法只支持静态加载模块 import() 作为一个提案,只被 babel 内部使用 -
transform-class-properties
ransform-class-properties 用与 class 的属性转化
- stage-3
拥有的插件
- transform-object-rest-spread
用来处理扩展运算符
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; - transform-async-generator-functions
用来处理 async 和 awaitasync function* agf() { await 1; yield 2; }
astexplore 地址:astexplorer.net/
参考资料
segmentfault.com/a/119000001… zhuanlan.zhihu.com/p/25961891 blog.csdn.net/SSLJY_YCFH/… www.jianshu.com/p/e9b94b2d5… www.jianshu.com/p/e9b94b2d5…