Babel配置

159 阅读7分钟

Babel事用来将我们的ES6代码转化为ES5代码的工具。

按照官方的例子,我们首先需要安装对应的npm包:

npm install --save-dev @babel/core @babel/cli @babel/preset-env

然后创建一个babel.config.json

{ 
    "presets": [ 
        [ "@babel/preset-env", 
            { 
                "targets": { 
                    "edge": "17", 
                    "firefox": "60", 
                    "chrome": "67", 
                    "safari": "11.1" 
                }, 
                "useBuiltIns": "usage", 
                "corejs": "3" 
            } 
        ] 
    ] 
}

然后我们可以执行

./node_modules/.bin/babel src --out-dir lib

npx babel src --out-dir lib

将我们src目录下的代码转化然后输出到lib目录。

@babel/preset-env

这里主要讲解下这个babel的配置,@babel/preset-env是一个处理语法的Plugjin的集合,babel对每种语法的处理都单独抽离成了一个插件,比如处理箭头函数的插件@babel/plugin-transform-arrow-functions,那么如果我们要处理箭头函数,需要进行如下配置:

{ 
    "plugings": [ 
        "@babel/plugin-transform-arrow-functions"
    ] 
}

如果还要处理其他语法,那么每种语法的插件都要这样用npm install安装然后配置在plugins数组里,这样处理起来肯定太麻烦,所以@babel/preset-env就是所有的这些插件的集合,我们只需要引入它就可以了。

targets

我们在presets里配置@babel/preset-env就会处理所有的语法吗?其实这样说是不严谨的,可以看到配置里还有一个targets配置,这个配置是告诉babel我们需要支持哪些浏览器, 只有这些需要支持的浏览器里还没有的那些语法的语法才会被处理,比如说

 "targets": { 
                    "ie": "11", 
                },

说明我们需要支持ie 11浏览器,因为ie 11不支持箭头函数的写法,所以代码里的箭头函数会被转换,如果配置是这样的

 "targets": { 
                    "ie": "16", 
                },

因为ie 16支持箭头函数,所以即使我们在代码里用了箭头函数也是不会被转换的。

browserslist

上面说的targets配置是用来配置我们的项目需要支持哪些浏览器,一般我们不会将这个配置写在targets里,而是写在一个单独的文件中.browserslistrc,或者写在package.json中,比如

  // package.json
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead",
    "not ie 11"
  ]

因为不止babel需要用到这些配置,处理css的时候也需要用到这些配置,所以放到一个统一的单独的文件比较好。

useBuiltIns

useBuiltIns是一个非常重要的配置,用来处理对Api的转换,即polyfill。默认@babel/preset-env只会对语法进行转换,即constlet=>这些,不会对Promise、Map以及数组实例方法includes这些进行转换,对这些API进行转换需要配置useBuiltIns。useBuiltIns有三个可选的配置项

false
usage
entry

false

当useBuiltIns为false时,不对API做处理。

usage

当useBuiltIns为usage时,babel会根据代码自动引入所需的垫片,这时候我们的babel配置如下:

    "presets": [ 
        [ "@babel/preset-env", 
                "useBuiltIns": "usage", 
                "corejs": "3" 
            } 
        ] 
    ] 

我们的代码如下

new Promise(resolve => resolve(11)).then(num => {});

使用npx babel src --out-dir lib转换后得到的代码如下

"use strict";

require("core-js/modules/es.object.to-string.js");

require("core-js/modules/es.promise.js");

new Promise(resolve => resolve(11)).then(num => {});

可以看到代码里引入了两个npm库,这两个库就是处理Promise需要的垫片,我们可以理解为这两个库里面定义了一个全局变量Promise,然后实现了这个Promise的功能。

usage时按需引入,我们的文件里使用了什么Api,且这个API在browserslist浏览器还不支持,那么才会引入对应的垫片。

entry

entry也是引入对应的垫片,和usage不同的是entry是全量引入,即不管你代码里有没有使用Promise,都会引入所有需要的垫片,且需要我们自己引入,设置useBuiltIns为entry后,我们需要在我们的主文件里添加一行引入代码

import 'core-js';

这时候我们的配置如下

    "presets": [ 
        [ "@babel/preset-env", 
                "useBuiltIns": "entry", 
                "corejs": "3" 
            } 
        ] 
    ]

main.js如下

import 'core-js';

new Promise(resolve => resolve(11)).then(num => {});

生成的代码如下:

"use strict";

require("core-js/modules/es.symbol.js");

require("core-js/modules/es.symbol.description.js");

require("core-js/modules/es.symbol.async-iterator.js");

// 省略很多其它require...

new Promise(resolve => resolve(11)).then(num => {});

即当我们配置了useBuiltIns为entry后,babel会自动将我们引入的代码import 'core-js';转换为垫片的引入,这些垫片包括所有我们配置的浏览器列表不支持的功能,不管我们的代码里有没有用到对用的API。

这里引入的垫片数量只和我们设置的浏览器列表有关,比如当我们设置

// .brwoserslistrc
last 1 version

// .brwoserslistrc
last 2 versions

这时候引入的垫片数量是不一样的,只需要支持最新的一个版本引入的垫片数量会少一些。

corejs

当我们将useBuiltIns设置为usage或entry后,我们还需要设置一个参数corejs,因为当我们设置useBuiltIns为usage或entry后,我们需要使用core-js这个npm包来对API进行处理,我们需要指定需要使用哪个版本的core-js,大版本主要为2和3,我们可以设置为23或更具体的版本号像3.6.5这样,一般指定3就可以了,2已经不再维护了,一些新的API在2里面可能没有。

转换后我们的代码里会引入对应的垫片

require("core-js/modules/es.promise.js");

我们也需要安装这个npm包

npm i core-js

通过以上的配置我们就可以开发项目啦,具体要选择entry还是usage可以自己决定。

@babel/plugin-transform-runtime

另一个非常重要的知识点时@babel/plugin-transform-runtime,这是一个转换API的插件,和上面的垫片功能是一样的,所以很多同学可能就有疑问了,我们通过配置@babel/preset-envuseBuiltIns就可以实现对应的垫片功能了,为什么还需要用到@babel/plugin-transform-runtime呢,造这么多功能相同的轮子干嘛?

其实主要区别是使用useBuiltIns加入的垫片会污染全局变量,而使用@babel/plugin-transform-runtime加入的垫片不会污染全局变量,当我们开发项目时使用useBuiltIns问题还不大,但是当我们开发第三方库时,如果污染了全局变量那就不太合理了,比如说你引入一个jquery库,结果jquery库里把全局的Promise以及数组的一些方法(比如includes)全部重写了一遍,然后你还引入了其它的库,其它的库也重写了一遍这些方法,这不全乱套了吗,说不定哪里就有冲突了。

所以当开发第三方库的时候,我们要使用@babel/plugin-transform-runtime来对API进行转换,当开发项目时,可以直接使用@babel/preset-envuseBuiltIns

使用@babel/plugin-transform-runtime时对应的配置如下

// babel.config.json
{
    "presets": [
      [
        "@babel/preset-env",
        {
          "useBuiltIns": false,
        //   "corejs": "3.6.5"
        }
      ]
    ],
    "plugins": [
        ["@babel/plugin-transform-runtime", {
            "corejs": 3
        }]
    ]
  }

可以看到我们将useBuiltIns设置为了false,即我们不使用@babel/preset-env内置的功能来添加垫片,而是使用@babel/plugin-transform-runtime,需要设置一个对应的参数corejs,这个值为2或3,新项目都使用3就可以了。

我们目前的代码如下:

new Promise(resolve => resolve(11)).then(num => {console.log(num)});

然后使用npx babel src --out-dir lib转换后如下:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

new _promise.default(function (resolve) {
  return resolve(11);
}).then(function (num) {
  console.log(num);
});

可以看到代码里的Promise不见了,被转换成了_promise.default,而这个_promise是从@babel/runtime-corejs3/core-js-stable/promise导入的,这样就解决了全局变量污染的问题,现在所有的变量作用范围都是在这个文件的作用域内。

而且使用@babel/plugin-transform-runtime可以帮我们减少一些代码体积,比如另外的文件也使用了Promise这个方法,babel不会在每个文件里都定义一遍Promise这个方法,方法都定义在@babel/runtime-corejs3/core-js-stable/promise里面,每个文件只需要引入就可以了。

这里同样需要注意用这个方法还需要引入@babel/runtime-corejs3这个npm包

npm i @babel/runtime-corejs3

一般来说我们使用node 转换后的代码路径是可以直接运行这个文件的进行测试的。

Webpack

Webpack其实就是多了一个babel-loader,babel-loader其实就是做一个桥梁的作用,实际还是调用上面的这些方法作转换。