1

在做项目中一直使用的是脚手架搭建的环境,一直没有仔细的去了解 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

处理顺序

  1. plugins 优先于 presets
  2. plugins 从数组第一个到最后一个进行编译
  3. presets 从数组最后一个到第一个进行编译 ,这个目的主要为了向后降级

presets

presets 用于设置转码的规则,常用的 presetsenvstage-x

关于 env 后文会提到,先来看看 stage-x

stage-x 是新特性纳入标准所经过的几个阶段,x 值越小,表示阶段越靠后,靠后的阶段包含前面的所有内容,即 stage-0 包含 stage-1/2/3 的所有内容

babelstage

上图是 stage-2index.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-polyfillbabel-runtime 的产生

babel-polyfill

  1. 把所有的 polyfill 一次性全部引入,不管你在项目中是否真正用到
  2. 污染全局对象,可能引发冲突。如果你开发的是一个应用项目,那么这一点可以暂时忽略,但是如果你开发的是一款插件或者别人将来引入的包,那么很有可能会给使用者带来不便
  3. 因为需要在自己的代码之前运行这些 polyfill 所以该包应该被添加到 dependency
  4. 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-runtimehelper 函数的自动引入,并且它还做了公用方法的抽离,你引入的函数都是引用自一个地方,就避免了重复的代码

该包依赖 babel-runtime ,这也是为什么我们在使用 webpack 配置 babel 的时候,只需要安装 babel-plugin-transform-runtime 的原因,
devDependencies 里只看见了 babel-plugin-transform-runtime

该插件主要做了三件事:

  1. 当你使用 generators/async 方法、函数时自动调用 babel-runtime/regenerator
  2. 当你使用 ES6 的 Map 或者内置的东西时自动调用 babel-runtime/core-js
  3. 移除内联 babel helpers 并替换使用 babel-runtime/helpers 来替换

优点

  1. 不会污染全局变量
  2. 多次使用只打包一次
  3. 依赖按需引入,无重复引入
  4. 适合编写库类型的代码

缺点

  1. 不支持实例化的方法 'foobar'.includes('foo') 不能转化

配置

一般直接默认就行,不需要对该插件进行配置
{
  "plugins": [
    ["transform-runtime", {
      "polyfill": true,       // 是否把新特性转换为非全局的 polyfill
      "helpers": true,        // 是否用模块中的 helpers 替换内联 helpers
      "regenerator": true,    // 是否把生成器函数转换为非全局的 polyfill
      "moduleName": "babel-runtime"  // 导入 helpers 的时候的模块路径
    }]
  ]
}

一次小测试

.babelrc

babelrctest

转码前的 a.js

babelrctestajs

转码后的 a-compiled.js

babelrctestacjs

可以看到实例方法 "foo".includes('f') 并没有被转换

再来一个小测试

.babelrc

babelrctest2

转码前的 b.js

babelrctest2bjs

使用 babel 转码后,Set 转码成功,并且可以看到在转码后的文件中,打印的并不是原生的 Set ,而是 babel 为我们包装的一个替代原生 Set 的模块,避免了全局污染。

babelrctest2bcjs


当我们把 .babelrc 中改为 polyfill: false 时,再次对 b.js 转码,转码后,语句没有被处理。打印的就是原生的 Set ,污染了全局变量。

babelrctest2bncjs


如果需要对实例方法进行转码,可以这么来,当然你需要在 .babelrc 里改为 polyfill: true ,不然没有这个 polyfill 根本没有这些方法。

转码前:

babelrctest3bjs

转码后:

babelrctest3bcjs

如果非要在不支持的环境下使用实例方法的话,就还得借助 babel-polyfill 了(或者你自己实现一个)。不过好像目前浏览器端对这种诸如 includesrepeat 之类的方法支持的还不错了。

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 可能会有问题

一般不推荐使用松散模式

简单来说,松散模式转换后的代码很容易就能看懂,而且很像我们平时写的代码,但这种不严谨的转换可能会造成问题,所以在开发中是不推荐的。

感受一下:

转码前:

babelloosesource

转码后(正常):

babelloosenormal

转码后(松散):

babellooseloose

modules

把 ES6 模块语法转为另一个模块类型,默认 commonjs

现在的 webpack 4.x 已经把模块统一的任务完成了,所以这里就不需要 babel 来做了,所以在 vue-cli 这种用 webpack 打包的脚手架里,你会看到 .babelrc 文件中有 module: false 这就是为了防止与 webpack 冲突

include

指定一组总是包括的插件,当原生实现有问题,或存在不支持或支持不好的特性时候使用,默认为 []

includeexclude 只工作于包含在 preset 里有的插件中,如果要使用 preset 里不包含的插件,直接填在 .babelrcplugins

exclude

指定一组总是不包括的插件,默认为 []

useBuiltIns

默认为 false
会启用一个插件来根据使用情况去按需加载 polyfill 来替代 import "babel-polyfill" 语句

其他 Babel 工具

所有的工具建议都在项目中安装,而不是采用全局安装
  1. babel-cli 用于命令行转码
  2. babel-node 跟随 babel-cli 安装,可直接运行 ES6 代码,因为采用这种方式是实时转码的,转码的所有工具都存在内存中,产生大量资源消耗因此只适合在开发中使用
  3. babel-register 改写 require 命令,每次使用 require 加载 js 文件时候,就会先转码,因为是实时转码,只适合在开发环境使用
  4. babel-core 提供 Babel 的 API,之后可以采用编程方式使用 Babel

参考链接

  1. Babel · The compiler for next generation JavaScript
  2. 对babel-transform-runtime,babel-polyfill的一些理解
  3. Babel 入门教程 - 阮一峰的网络日志
  4. babel-polyfill的几种使用方式
  5. Babel笔记 - Tony’s Blog
  6. Babel的使用
  7. Babel 6 松散模式

breezymelon
132 声望3 粉丝