标题党了哈哈哈~~~原文地址相信很多人和笔者从前一样,babel的配置都是从网上复制黏贴或者使用现成的脚手架,虽然这能够工作但还是希望大家能够知其所以然,因此本文将对babel(babel@7)的配置做一次较为完整的梳理。语法和apies6增加的内容可以分为语法和api两部分,搞清楚这点很重要,新语法比如箭头函数、解构等:const fn = () => {}const arr2 = […arr1]新的api比如Map、Promise等:const m = new Map()const p = new Promise(() => {})@babel/core@babel/core,看名字就知道这是babel的核心,没他不行,所以首先安装这个包npm install @babel/core它的作用就是根据我们的配置文件转换代码,配置文件通常为.babelrc(静态文件)或者babel.config.js(可编程),这里以.babelrc为例,在项目的根目录下创建一个空文件命名为.babelrc,然后创建一个js文件(test.js)测试用:/* test.js /const fn = () => {}这里我们安装下@babel/cli以便能够在命令行使用babelnpm install @babel/cli安装完成后执行babel编译,命令行输入npx babel test.js –watch –out-file test-compiled.js结果发现test-compiled.js的内容依然是es6的箭头函数,不用着急,我们的.babelrc还没有写配置呢Plugins和PresetsNow, out of the box Babel doesn’t do anything. It basically acts like const babel = code => code; by parsing the code and then generating the same code back out again. You will need to add plugins for Babel to do anything.上面是babel官网的一段话,可以理解为babel是基于插件架构的,假如你什么插件也不提供,那么babel什么也不会做,即你输入什么输出的依然是什么。那么我们现在想要把剪头函数转换为es5函数只需要提供一个箭头函数插件就可以了:/ .babelrc /{ “plugins”: ["@babel/plugin-transform-arrow-functions"] }转换后的test-compiled.js为:/ test.js /const fn = () => {}/ test-compiled.js /const fn = function () {}那我想使用es6的解构语法怎么办?很简单,添加解构插件就行了:/ .babelrc /{ “plugins”: [ “@babel/plugin-transform-arrow-functions”, “@babel/plugin-transform-destructuring” ] }问题是有那么多的语法需要转换,一个个的添加插件也太麻烦了,幸好babel提供了presets,他可以理解为插件的集合,省去了我们一个个引入插件的麻烦,官方提供了很多presets,比如preset-env(处理es6+规范语法的插件集合)、preset-stage(处理尚处在提案语法的插件集合)、preset-react(处理react语法的插件集合)等,这里我们主要介绍下preset-env:/ .babelrc /{ “presets”: ["@babel/preset-env"] }preset-env@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s).以上是babel官网对preset-env的介绍,大致意思是说preset-env可以让你使用es6的语法去写代码,并且只转换需要转换的代码。默认情况下preset-env什么都不需要配置,此时他转换所有es6+的代码,然而我们可以提供一个targets配置项指定运行环境:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “targets”: “ie >= 8” }] ] }此时只有ie8以上版本浏览器不支持的语法才会被转换,查看我们的test-compiled.js文件发现一切都很好:/ test.js /const fn = () => {}const arr1 = [1, 2, 3]const arr2 = […arr1]/ test-compiled.js /var fn = function fn() {};var arr1 = [1, 2, 3];var arr2 = [].concat(arr1);@babel/polyfill现在我们稍微改一下test.js:/ test.js /const fn = () => {}new Promise(() => {})/ test-compiled.js /var fn = function fn() {};new Promise(function () {});我们发现Promise并没有被转换,什么!ie8还支持Promise?那是不可能的…。还记得本文开头提到es6+规范增加的内容包括新的语法和新的api,新增的语法是可以用babel来transform的,但是新的api只能被polyfill,因此需要我们安装@babel/polyfill,再简单的修改下test.js如下:/ test.js /import ‘@babel/polyfill’const fn = () => {}new Promise(() => {})/ test-compiled.js /import ‘@babel/polyfill’;var fn = function fn() {};new Promise(function () {});现在代码可以完美的运行在ie8的环境了,但是还存在一个问题:@babel/polyfill这个包的体积太大了,我们只需要Promise就够了,假如能够按需polyfill就好了。真巧,preset-env刚好提供了这个功能:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “useBuiltIns”: “entry”, “targets”: “ie >= 8” }] ] }我们只需给preset-env添加一个useBuiltIns配置项即可,值可以是entry和usage,假如是entry,会在入口处把所有ie8以上浏览器不支持api的polyfill引入进来,如下:/ test.js /import ‘@babel/polyfill’const fn = () => {}new Promise(() => {})/ test-compiled.js /import “core-js/modules/es6.array.copy-within”;import “core-js/modules/es6.array.every”;import “core-js/modules/es6.array.fill”;… //省略若干引入import “core-js/modules/web.immediate”;import “core-js/modules/web.dom.iterable”;import “regenerator-runtime/runtime”;var fn = function fn() {};new Promise(function () {});细心的你会发现transform后,import ‘@babel/polyfill’消失了,反倒是多了一堆import ‘core-js/…‘的内容,事实上,@babel/polyfill这个包本身是没有内容的,它依赖于core-js和regenerator-runtime这两个包,这两个包提供了es6+规范的运行时环境。因此当我们不需要按需polyfill时直接引入@babel-polyfill就行了,它会把core-js和regenerator-runtime全部导入,当我们需要按需polyfill时只需配置下useBuiltIns就行了,它会根据目标环境自动按需引入core-js和regenerator-runtime。前面还提到useBuiltIns的值还可以是usage,其功能更为强大,它会扫描你的代码,只有你的代码用到了哪个新的api,它才会引入相应的polyfill:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “useBuiltIns”: “usage”, “targets”: “ie >= 8” }] ] }transform后的test-compiled.js相应的会简化很多:/ test.js /const fn = () => {}new Promise(() => {})/ test-compiled.js /import “core-js/modules/es6.promise”;import “core-js/modules/es6.object.to-string”;var fn = function fn() {};new Promise(function () {});遗憾的是这个功能还处于试验状态,谨慎使用。 事实上假如你是在写一个app的话,以上关于babel的配置差不多已经够了,你可能需要添加一些特定用途的Plugin和Preset,比如react项目你需要在presets添加@babel/preset-react,假如你想使用动态导入功能你需要在plugins添加@babel/plugin-syntax-dynamic-import等等,这些不在赘述。假如你是在写一个公共的库或者框架,下面提到的点可能还需要你注意下。@babel/runtime有时候语法的转换相对复杂,可能需要一些helper函数,如转换es6的class:/ test.js /class Test {}/ test-compiled.js /function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); } }var Test = function Test() { _classCallCheck(this, Test);};示例中es6的class需要一个_classCallCheck辅助函数,试想假如我们多个文件中都用到了es6的class,那么每个文件都需要定义一遍_classCallCheck函数,这也是一笔不小的浪费,假如将这些helper函数抽离到一个包中,由所有的文件共同引用则可以减少可观的代码量。而@babel/runtime做的正好是这件事,它提供了各种各样的helper函数,但是我们如何知道该引入哪一个helper函数呢?总不能自己手动引入吧,事实上babel提供了一个@babel/plugin-transform-runtime插件帮我们自动引入helper。我们首先安装@babel/runtime和@babel/plugin-transform-runtime:npm install @babel/runtime @babel/plugin-transform-runtime然后修改babel配置如下:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “useBuiltIns”: “usage”, “targets”: “ie >= 8” }] ], “plugins”: [ “@babel/plugin-transform-runtime” ] }现在我们再来看test-compiled.js文件,里面的_classCallCheck辅助函数已经是从@babel/runtime引入的了:/ test.js /class Test {}/ test-compiled.js /import _classCallCheck from “@babel/runtime/helpers/classCallCheck”;var Test = function Test() { _classCallCheck(this, Test);};看到这里你可能会说,这不扯淡嘛!几个helper函数能为我减少多少体积,我才懒得安装插件。事实上@babel/plugin-transform-runtime还有一个更重要的功能,它可以为你的代码创建一个sandboxed environment(沙箱环境),这在你编写一些类库等公共代码的时候尤其重要。上文我们提到,对于Promise、Map等这些es6+规范的api我们是通过提供polyfill兼容低版本浏览器的,这样做会有一个副作用就是污染了全局变量,假如你是在写一个app还好,但如果你是在写一个公共的类库可能会导致一些问题,你的类库可能会把一些全局的api覆盖掉。幸好@babel/plugin-transform-runtime给我们提供了一个配置项corejs,它可以将这些变量隔离在局部作用域中:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “targets”: “ie >= 8” }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “corejs”: 2 }] ] }注意:这里一定要配置corejs,同时安装@babel/runtime-corejs2,不配置的情况下@babel/plugin-transform-runtime默认是不引入这些polyfill的helper的。corejs的值现阶段一般指定为2,可以近似理解为是@babel/runtime的版本。我们现在再来看下test-compiled.js被转换成了什么:/ test.js /class Test {}new Promise(() => {})/ test-compiled.js /import _Promise from “@babel/runtime-corejs2/core-js/promise”;import _classCallCheck from “@babel/runtime-corejs2/helpers/classCallCheck”;var Test = function Test() { _classCallCheck(this, Test);};new _Promise(function () {});如我们所愿,已经为Promise的polyfill创建了一个沙箱环境。最后我们再为test.js稍微添加点内容:/ test.js /class Test {}new Promise(() => {})const b = [1, 2, 3].includes(1)/ test-compiled.js */import _Promise from “@babel/runtime-corejs2/core-js/promise”;import _classCallCheck from “@babel/runtime-corejs2/helpers/classCallCheck”;var Test = function Test() { _classCallCheck(this, Test);};new _Promise(function () {});var b = [1, 2, 3].includes(1);可以发现,includes方法并没有引入辅助函数,可这明明也是es6里面的api啊。这是因为includes是数组的实例方法,要想polyfill必须修改Array的原型,这样一来就污染了全局环境,因此@babel/plugin-transform-runtime是处理不了这些es6+规范的实例方法的。tips以上基本是本文的全部内容了,最后再来个总结和需要注意的地方:本文没有提到preset-stage,事实上babel@7已经不推荐使用它了,假如你需要使用尚在提案的语法,请直接添加相应的plugin。对于普通项目,可以直接使用preset-env配置polyfill对于类库项目,推荐使用@babel/runtime,需要注意一些实例方法的使用本文内容是基于babel@7,项目中遇到问题可以尝试更新下babel-loader的版本…待补充全文完