https://juejin.cn/post/6844903956905197576
https://juejin.cn/post/6844903743121522701
把 JavaScript 中 es2015/2016/2017/2046 的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行
严格来说,babel 也可以转化为更低的规范。但以目前情况来说,es5 规范已经足以覆盖绝大部分浏览器,因此常规来说转到 es5 是一个安全且流行的做法。
使用方法
总共存在三种方式:
-使用单体文件 (standalone script)
-命令行 (cli)
-构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。
其中后面两种比较常见。第二种多见于 package.json 中的scripts段落中的某条命令;第三种就直接集成到构建工具中。
这三种方式只有入口不同而已,调用的 babel 内核,处理方式都是一样的,所以我们先不纠结入口的问题。
(1)babel的转译过程分为三个阶段:parsing、transforming、generating,以ES6代码转译为ES5代码为例,babel转译的具体过程如下:
•ES6代码输入
•babylon 进行解析得到 AST
•plugin 用 babel-traverse 对 AST 树进行遍历转译,得到新的AST树
•用 babel-generator 通过 AST 树生成 ES5 代码
运行方式和插件
babel 总共分为三个阶段:解析,转换,生成。
babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。
Babel 的插件分为两种: 语法插件和转换插件
1. 当我们添加语法插件之后,在解析这一步就使得 babel 能够解析更多的语法。(顺带一提,babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发)
举个简单的例子,当我们定义或者调用方法时,最后一个参数之后是不允许增加逗号的,如callFoo(param1, param2,)就是非法的。如果源码是这种写法,经过 babel 之后就会提示语法错误。
但最近的 JS 提案中已经允许了这种新的写法(让代码 diff 更加清晰)。为了避免 babel 报错,就需要增加语法插件babel-plugin-syntax-trailing-function-commas
2. 当我们添加转译插件之后,在转换这一步把源码转换并输出。这也是我们使用 babel 最本质的需求。
比起语法插件,转译插件其实更好理解,比如箭头函数(a) => a就会转化为function (a) {return a}。完成这个工作的插件叫做babel-plugin-transform-es2015-arrow-functions。
同一类语法可能同时存在语法插件版本和转译插件版本。如果我们使用了转译插件,就不用再使用语法插件了。
配置插件
既然插件是 babel 的根本,那如何使用呢?总共分为 2 个步骤:
1. 将插件的名字增加到配置文件中 (根目录下创建 .babelrc 或者 package.json 的babel里面,格式相同)
2. 使用npm install babel-plugin-xxx进行安装
preset
比如 es2015 是一套规范,包含大概十几二十个转译插件。如果每次要开发者一个个添加并安装,配置文件很长不说,npm install的时间也会很长,更不谈我们可能还要同时使用其他规范呢。
为了解决这个问题,babel 还提供了一组插件的集合。因为常用,所以不必重复定义 & 安装。(单点和套餐的差别,套餐省下了巨多的时间和配置的精力)
preset 分为以下几种:
1. 官方内容,目前包括 env, react, flow, typescript等。这里最重要的是 env,后面会详细介绍。
2. stage-x,这里面包含的都是当年最新规范的草案,每年更新。
这里面还细分为
Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
Stage 1 - 提案: 初步尝试。
Stage 2 - 初稿: 完成初步规范。
Stage 3 - 候选: 完成规范和浏览器初步实现。
Stage 4 - 完成: 将被添加到下一年度发布。
例如syntax-dynamic-import就是 stage-2 的内容,transform-object-rest-spread就是 stage-3 的内容。
此外,低一级的 stage 会包含所有高级 stage 的内容,例如 stage-1 会包含 stage-2, stage-3 的所有内容。
stage-4 在下一年更新会直接放到 env 中,所以没有单独的 stage-4 可供使用。
3. es201x, latest
这些是已经纳入到标准规范的语法。例如 es2015 包含arrow-functions,es2017 包含syntax-trailing-function-commas。但因为 env 的出现,使得 es2016 和 es2017 都已经废弃。所以我们经常可以看到 es2015 被单独列出来,但极少看到其他两个。
latest 是 env 的雏形,它是一个每年更新的 preset,目的是包含所有 es201x。但也是因为更加灵活的 env 的出现,已经废弃
执行顺序
很简单的几条原则:
-Plugin 会运行在 Preset 之前。
-Plugin 会从前到后顺序执行。
-Preset 的顺序则刚好相反(从后向前)。
preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是['es2015', 'stage-0']。这样必须先执行stage-0才能确保 babel 不报错。因此我们编排 preset 的时候,也要注意顺序,其实只要按照规范的时间顺序列出即可。
插件和 preset 的配置项
简略情况下,插件和 preset 只要列出字符串格式的名字即可。但如果某个 preset 或者插件需要一些配置项(或者说参数),就需要把自己先变成数组。第一个元素依然是字符串,表示自己的名字;第二个元素是一个对象,即配置对象。
最需要配置的当属 env,如下:
"presets": [
// 带了配置项,自己变成数组
[
// 第一个元素依然是名字
"env",
// 第二个元素是对象,列出配置项
{
"module": false
}
],
// 不带配置项,直接列出名字
"stage-2"
]
babel将es6+(指es6及以上版本)分为
语法层: let、const、class、箭头函数等,这些需要在构建时进行转译,是指在语法层面上的转译,(比如class...将来会被转译成var function...)
api层:Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义
插件(用于处理语法层)
polyfill(用于处理api层)
polyfill的中文意思是垫片,顾名思义就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。
如何使用:
首先,安装@babel/polyfill依赖。注意这是一个运行时依赖,所以不要加-dev
npm install --save @babel/polyfill
@babel/polyfill模块包括core-js和一个自定义的regenerator runtime模块,可以模拟完整的 ES2015+ 环境。
虽然看起来Promise还是没有转译,但是我们引入的 polyfill 中已经包含了对Promise的es5的定义,所以这时候代码便可以在低版本浏览器中运行了。
useBuiltIns属性(deprecated)
代码里边只用到了几个es6,却需要引入所有的垫片,要优化这一点,就需要用到useBuiltIns这个属性了。
useBuiltIns这一配置项,它的值有三种:
false: 不对polyfills做任何操作
entry: 根据target中浏览器版本的支持,将polyfills拆分引入,仅引入有浏览器不支持的polyfill
usage(新):检测代码中ES6/7/8等的使用情况,仅仅加载代码中用到的polyfills
//.babelrc
{
"presets": [
["@babel/preset-env",{
"useBuiltIns": "usage",
"corejs":3
}]
]
}
@babel/plugin-transform-runtime解决代码冗余
写一个语法层面的es6-->class(let,const这些对比不出问题,所以用class),看看babel会把class转化成什么
这种结果看起来没什么毛病,但事实上,如果其他文件中也使用了class,_classCallCheck在每个文件中都会出现,这就造成了代码冗余(示例中只使用了class,可能冗余的不明显,实际项目中这些函数可能是很长的)。
这时候需要用到另外一个插件了@babel/plugin-transform-runtime。该插件会开启对 Babel 注入的辅助函数(比如上边的_classCallCheck)的复用,以节省代码体积,这些辅助函数在@babel/runtime中,所以需要安装@babel/runtime,当然@babel/runtime也是运行时依赖。(在对一些语法进行编译的时候,babel需要借助一些辅助函数)
安装@babel/plugin-transform-runtime和@babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
可以发现,相关的辅助函数是以require的方式引入而不是被直接插入进来的,这样就不会冗余了。
除了解决代码冗余,@babel/plugin-transform-runtime还有另外一个重要的能力——解决全局污染。
解决全局污染
全局污染是出现在转译api层出现的问题
这次依然用Promise来做实验
可以看出preset-env在处理例如Promise这种的api时,只是引入了core-js中的相关的js库,这些库重新定义了Promise,然后将其挂载到了全局。
这里的问题就是:必然会造成全局变量污染,同理其他的例如Array.from等会修改这些全局对象的原型prototype,这也会造成全局对象的污染。
解决方式就是:将core-js交给transform-runtime处理。添加一个配置即可,非常简单
{
"presets": [
["@babel/preset-env"]
],
"plugins": [
["@babel/plugin-transform-runtime",{
"corejs":3
}]
]
}
可以看出,我们是将core-js这个属性添加到@babel/plugin-transform-runtime这个插件的配置下,让这个插件处理,同时也不需要配置useBuiltIns了,因为在babel7中已经将其设置为默认值(transform-runtime是利用plugin自动识别并替换代码中的新特性,检测到需要哪个就用哪个)
可以看出,这时的代码并没有在全局直接添加一个Promse,而是定义了一个_promise["default"]方法,这样便不会出现全局变量污染的情况
所以综上可得出@babel/plugin-transform-runtime这个插件的强大之处有以下几点:
-实现对辅助函数的复用,解决转译语法层时出现的代码冗余
-解决转译api层出现的全局变量污染
但是transform-runtime也有缺点:
-每个特性都会经历检测和替换,随着应用增大,可能会造成转译效率不高