了解babel

1.1 功能

把 JavaScript 中 es2015/2016/2017/2046 的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行

1.2使用方法

  1. 使用单体文件 (standalone script), 对单个文件进行编译
  2. 命令行 (cli)
  3. 构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。
  1. 后面两种比较常见。第二种多见于 package.json 中的 scripts 段落中的某条命令
  2. 第三种就直接集成到构建工具中。
  3. 这三种方式只有入口不同而已,调用的 babel 内核,处理方式都是一样的

1.3 运行方式和插件

babel 总共分为三个阶段:解析,转换,生成

babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。

  • 插件总共分为两种:
    • 当我们添加 语法插件 之后,在解析这一步就使得 babel 能够解析更多的语法。(顺带一提,babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发)
      • 举个简单的例子,当我们定义或者调用方法时,最后一个参数之后是不允许增加逗号的,如 callFoo(param1, param2,) 就是非法的。如果源码是这种写法,经过 babel 之后就会提示语法错误。
      • 但最近的 JS 提案中已经允许了这种新的写法(让代码 diff 更加清晰)。为了避免 babel 报错,就需要增加语法插件 babel-plugin-syntax-trailing-function-commas
      • 当我们添加 转译插件 之后,在转换这一步把源码转换并输出。这也是我们使用 babel 最本质的需求。
    1. 比起语法插件,转译插件其实更好理解,比如箭头函数 (a) => a 就会转化为 function (a) {return a}。完成这个工作的插件叫做 babel-plugin-transform-es2015-arrow-functions。
      1. 同一类语法可能同时存在语法插件版本和转译插件版本。如果我们使用了转译插件,就不用再使用语法插件了。

1.4 配置文件

  1. 使用步骤
  1. 将插件的名字增加到配置文件中 (根目录下创建 .babelrc 或者 package.json 的 babel 里面,格式相同)
  2. 使用 npm install babel-plugin-xxx 进行安装
  1. Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
  2. Stage 1 - 提案: 初步尝试。
  3. Stage 2 - 初稿: 完成初步规范。
  4. Stage 3 - 候选: 完成规范和浏览器初步实现。
  5. Stage 4 - 完成: 将被添加到下一年度发布
  • Plugin 会运行在 Preset 之前。
  • Plugin 会从前到后顺序执行。
  • Preset 的顺序则 刚好相反(从后向前)。

preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是 [‘es2015’, ‘stage-0’]。这样必须先执行 stage-0 才能确保 babel 不报错。因此我们编排 preset 的时候,也要注意顺序,其实只要按照规范的时间顺序列出即可。


简略情况下,插件和 preset 只要列出字符串格式的名字即可。但如果某个 preset 或者插件需要一些配置项(或者说参数),就需要把自己先变成数组。第一个元素依然是字符串,表示自己的名字;第二个元素是一个对象,即配置对象

  1. "presets": [
  2. // 带了配置项,自己变成数组
  3. [
  4. // 第一个元素依然是名字
  5. "env",
  6. // 第二个元素是对象,列出配置项
  7. {
  8. "module": false
  9. }
  10. ],
  11. // 不带配置项,直接列出名字
  12. "stage-2"
  13. ]

2.1 env (重点)

env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。例如目标浏览器支持 es2015,那么 es2015 这个 preset 其实是不需要的,于是代码就可以小一点(一般转化后的代码总是更长),构建时间也可以缩短一些 如果不写任何配置项,env 等价于 latest,也等价于 es2015 + es2016 + es2017 三个相加(不包含 stage-x 中的插件).env包含的插件列表

  1. {
  2. "presets": [
  3. ["env", {
  4. "targets": {
  5. "browsers": ["last 2 versions", "safari >= 7"]
  6. }
  7. }]
  8. ]
  9. }

如上配置将考虑所有浏览器的最新2个版本(safari大于等于7.0的版本)的特性,将必要的代码进行转换。而这些版本已有的功能就不进行转化了。

  1. {
  2. "presets": [
  3. ["env", {
  4. "targets": {
  5. "node": "6.10"
  6. }
  7. }]
  8. ]
  9. }

如上配置将目标设置为 nodejs,并且支持 6.10 及以上的版本。也可以使用 node: ‘current’ 来支持最新稳定版本。例如箭头函数在 nodejs 6 及以上将不被转化,但如果是 nodejs 0.12 就会被转化了

另外一个有用的配置项是 modules。它的取值可以是 amd, umd, systemjs, commonjs 和 false。这可以让 babel 以特定的模块化格式来输出代码。如果选择 false 就不进行模块化处理


3.1 其他配套工具

实际上这些 babel-* 大多是不同的入口(方式)来使用 babel

3.1.1 babel-cli

cli 就是命令行工具。安装了 babel-cli 就能够在命令行中使用 babel 命令来编译文件。

在开发 npm package 时经常会使用如下模式:

  1. 把 babel-cli 安装为 devDependencies
  2. 在 package.json 中添加 scripts (比如 prepublish),使用 babel 命令编译文件
  3. npm publish

这样既可以使用较新规范的 JS 语法编写源码,同时又能支持旧版环境。因为项目可能不太大,用不到构建工具 (webpack 或者 rollup),于是在发布之前用 babel-cli 进行处理

3.1.2 babel-node
  • babel-node 是 babel-cli 的一部分,它不需要单独安装。
  • 它的作用是在 node 环境中,直接运行 es2015 的代码,而不需要额外进行转码。例如我们有一个 js 文件以 es2015 的语法进行编写(如使用了箭头函数)。我们可以直接使用 babel-node es2015.js 进行执行,而不用再进行转码了
  1. babel-node = babel-polyfill + babel-register

babel-register

  • babel-register 模块改写 require 命令,为它加上一个钩子。此后,每当使用 require 加载 .js、.jsx、.es 和 .es6 后缀名的文件,就会先用 babel 进行转码。
  • 使用时,必须首先加载 require(‘babel-register’)。
  • 需要注意的是,babel-register 只会对 require 命令加载的文件转码,而 不会对当前文件转码。
  • 另外,由于它是实时转码,所以 只适合在开发环境使用。

babel-polyfill

babel-runtime 和 babel-plugin-transform-runtime (重点)


后续内容请点击标题 链接跳转原文…

一口(很长的)气了解 babel


小结一下

名称 作用 备注
babel-cli 允许命令行使用 babel 命令转译文件
babel-node 允许命令行使用 babel-node 直接转译+执行 node 文件 随 babel-cli 一同安装 babel-node = babel-polyfill + babel-register
babel-register 改写 require 命令,为其加载的文件进行转码,不对当前文件转码 只适用于开发环境
babel-polyfill 为所有 API 增加兼容方法 需要在所有代码之前 require,且体积比较大
babel-plugin-transform-runtime & babel-runtime 把帮助类方法从每次使用前定义改为统一 require,精简代码 babel-runtime 需要安装为依赖,而不是开发依赖
babel-loader 使用 webpack 时作为一个 loader 在代码混淆之前进行代码转换

Babel 7.x

详细见原文…

一口(很长的)气了解 babel