在做项目中一直使用的是脚手架搭建的环境,一直没有仔细的去了解 babel
这一工具,这周末抽出一天时间通过官网还有各种博客文章算是了解了一些内容,起码可以在项目中自己完成 .babelrc
的配置了。
这篇文章就是把自己的理解和找到的优秀文章的内容做一融合和整理,理解有误的地方还请大家批评指正~
因为主要是面对项目,所以本文内容主要还是围绕 .babelrc
展开,与babel
相关的其他工具无关。
Babel 是什么?
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in old browsers or environments.
由于浏览器对 ECMAScript 的支持各有差异,因此 Babel 是一个用来将 ES6 版本以上代码转为 ES5 版本代码的工具,从而使得编写的代码可以在指定的环境下运行。
.babelrc 配置文件
在项目中我们使用 Babel 做转码一般使用配置文件的形式。Babel 的配置文件名为 .babelrc
并且通常放在项目根路径下,其格式大致如下:
{
"env": {
"production": {
"presets": [], // 转码规则
"plugins": [], // 插件
"ignore": ["node_modules/"] // 转码时候忽略的文件
}
}
}
这里的 env
的值取得是项目中的 process.env.BABEL_ENV
如果该值找不到,则取 process.env.NODE_ENV
最后如果该值还找不到,则设为 development
处理顺序
-
plugins
优先于presets
-
plugins
从数组第一个到最后一个进行编译 -
presets
从数组最后一个到第一个进行编译 ,这个目的主要为了向后降级
presets
presets
用于设置转码的规则,常用的 presets
有 env
和 stage-x
关于 env
后文会提到,先来看看 stage-x
stage-x
是新特性纳入标准所经过的几个阶段,x
值越小,表示阶段越靠后,靠后的阶段包含前面的所有内容,即stage-0
包含stage-1/2/3
的所有内容
上图是 stage-2
的 index.js
文件,可以看到其中直接引用了 stage-3
的所有特性。
实际上来说,presets
就是 plugins
的集合,如果没有 preset
也是可以完成代码转换工作的,如下。
{
"plugins": [
"check-es2015-constants",
"es2015-arrow-functions"
]
}
但是由于这么配置过于繁琐,因此 Babel 把一些属于同一标准的 transform-plugins
划归到一个 presets
中,这样有了 presets
就不用再一个一个地导入 transform-plugins
了。
babel-polyfill & babel-runtime
Babel 默认只转换 JS 语法,而不转换新的 API ,新标准中的全局对象和定义在这些全局对象上的方法都不会转码,这些 API 很多,具体参考 definitions.js
这就导致了 babel-polyfill
和 babel-runtime
的产生
babel-polyfill
- 把所有的
polyfill
一次性全部引入,不管你在项目中是否真正用到 - 污染全局对象,可能引发冲突。如果你开发的是一个应用项目,那么这一点可以暂时忽略,但是如果你开发的是一款插件或者别人将来引入的包,那么很有可能会给使用者带来不便
- 因为需要在自己的代码之前运行这些
polyfill
所以该包应该被添加到dependency
- 与
webpack
结合使用时候需要放在entry
数组中entry: ["babel-polyfill", "app.js"]
babel-runtime
与 babel-polyfill
的一次性引入不同,babel-runtime
支持自己手动引入 helper
函数,来完成对某一 API 的转码。它更像是一个个分散的 polyfill
模块。
显而易见 babel-runtime
的缺点之一就是每次使用 API 的时候,都需要我们进行手动引入,很麻烦;此外,在代码中直接引入 helper
函数,会导致打包的文件中出现很多重复 helper
代码。因此现在实际工作中会使用 babel-runtime + babel-plugin-transform-runtime
的形式
babel-plugin-transform-runtime
这个包可以帮我们完成 babel-runtime
中 helper
函数的自动引入,并且它还做了公用方法的抽离,你引入的函数都是引用自一个地方,就避免了重复的代码
该包依赖 babel-runtime
,这也是为什么我们在使用 webpack
配置 babel 的时候,只需要安装 babel-plugin-transform-runtime
的原因,
即 devDependencies
里只看见了 babel-plugin-transform-runtime
该插件主要做了三件事:
- 当你使用
generators/async
方法、函数时自动调用babel-runtime/regenerator
- 当你使用 ES6 的
Map
或者内置的东西时自动调用babel-runtime/core-js
- 移除内联
babel helpers
并替换使用babel-runtime/helpers
来替换
优点
- 不会污染全局变量
- 多次使用只打包一次
- 依赖按需引入,无重复引入
- 适合编写库类型的代码
缺点
- 不支持实例化的方法
'foobar'.includes('foo')
不能转化
配置
一般直接默认就行,不需要对该插件进行配置
{
"plugins": [
["transform-runtime", {
"polyfill": true, // 是否把新特性转换为非全局的 polyfill
"helpers": true, // 是否用模块中的 helpers 替换内联 helpers
"regenerator": true, // 是否把生成器函数转换为非全局的 polyfill
"moduleName": "babel-runtime" // 导入 helpers 的时候的模块路径
}]
]
}
一次小测试
.babelrc
转码前的 a.js
转码后的 a-compiled.js
可以看到实例方法 "foo".includes('f')
并没有被转换
再来一个小测试
.babelrc
转码前的 b.js
使用 babel 转码后,Set
转码成功,并且可以看到在转码后的文件中,打印的并不是原生的 Set
,而是 babel 为我们包装的一个替代原生 Set
的模块,避免了全局污染。
当我们把 .babelrc
中改为 polyfill: false
时,再次对 b.js
转码,转码后,语句没有被处理。打印的就是原生的 Set
,污染了全局变量。
如果需要对实例方法进行转码,可以这么来,当然你需要在 .babelrc
里改为 polyfill: true
,不然没有这个 polyfill
根本没有这些方法。
转码前:
转码后:
如果非要在不支持的环境下使用实例方法的话,就还得借助 babel-polyfill
了(或者你自己实现一个)。不过好像目前浏览器端对这种诸如 includes
、repeat
之类的方法支持的还不错了。
babel-preset-env
Without any configuration options, babel-preset-env behaves exactly the same as babel-preset-latest (or babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017 together).
首先官网上给了公式
没有任何配置的 env = latest = es2015 + es2016 + es2017
babel-preset-env
由于其灵活的配置和全面的功能,被官网推荐,同时也是目前应用很频繁的 presets
配置项
targets
提供需要支持的环境信息,版本等,默认为 {}
spec
通过牺牲转换时间来支持该 preset
的更多规范兼容性,默认为 false
loose
为 preset
中的插件开启松散转换
松散模式优点:转换的代码更加简洁,没有为了接近 ES6 而添加的繁杂逻辑,文件更小,运行速度更快,兼容性更好
缺点:直接使用原生 ES6 可能会有问题
一般不推荐使用松散模式
简单来说,松散模式转换后的代码很容易就能看懂,而且很像我们平时写的代码,但这种不严谨的转换可能会造成问题,所以在开发中是不推荐的。
感受一下:
转码前:
转码后(正常):
转码后(松散):
modules
把 ES6 模块语法转为另一个模块类型,默认 commonjs
现在的webpack 4.x
已经把模块统一的任务完成了,所以这里就不需要babel
来做了,所以在vue-cli
这种用webpack
打包的脚手架里,你会看到.babelrc
文件中有module: false
这就是为了防止与webpack
冲突
include
指定一组总是包括的插件,当原生实现有问题,或存在不支持或支持不好的特性时候使用,默认为 []
include
和exclude
只工作于包含在preset
里有的插件中,如果要使用preset
里不包含的插件,直接填在.babelrc
的plugins
中
exclude
指定一组总是不包括的插件,默认为 []
useBuiltIns
默认为 false
会启用一个插件来根据使用情况去按需加载 polyfill
来替代 import "babel-polyfill"
语句
其他 Babel 工具
所有的工具建议都在项目中安装,而不是采用全局安装
-
babel-cli
用于命令行转码 -
babel-node
跟随babel-cli
安装,可直接运行 ES6 代码,因为采用这种方式是实时转码的,转码的所有工具都存在内存中,产生大量资源消耗因此只适合在开发中使用 -
babel-register
改写require
命令,每次使用require
加载js
文件时候,就会先转码,因为是实时转码,只适合在开发环境使用 -
babel-core
提供 Babel 的 API,之后可以采用编程方式使用 Babel