Babel

  • 概述
  • 使用方法
  • 使用单体文件(standalone script)
  • 使用命令行(cli)
  • babel-cli
  • babel-node
  • babel-polyfill
  • babel-register
  • babel-runtime 和 babel-plugin-transform-runtime
  • 使用构建工具的插件((webpack 的 babel-loader, rollup 的 rollup-plugin-babel)
  • babel-loader
  • 运行方式和插件
  • 运行阶段
  • 解析
  • 转换
  • 生成
  • 插件
  • 语法插件
  • 转译插件
  • 配置文件
  • preset
  • 官方内容
  • stage-x
  • es201x, latest
  • 执行顺序
  • 插件和preset的配置项
  • env


概述

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

使用方法

Bable的三种使用方式只有入口不同,调用babel内核,处理方式是一样的

使用单体文件(standalone script)

使用命令行(cli)

babel-cli

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

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

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

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

babel-node

babel-node 是 babel-cli 的一部分,它不需要单独安装。

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

可以说:babel-node = babel-polyfill + babel-register

babel-polyfill

babel 默认只转换 js 语法,而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。
举例来说,es2015 在 Array 对象上新增了 Array.from 方法。babel 就不会转码这个方法。如果想让这个方法运行,必须使用 babel-polyfill。(内部集成了 core-js 和 regenerator)

使用时,在所有代码运行之前增加 require(‘babel-polyfill’)。或者更常规的操作是在 webpack.config.js 中将 babel-polyfill 作为第一个 entry。因此必须把 babel-polyfill 作为 dependencies 而不是 devDependencies

babel-polyfill 主要有两个缺点:

  1. 使用 babel-polyfill 会导致打出来的包非常大,因为 babel-polyfill是一个整体,把所有方法都加到原型链上。比如我们只使用了 Array.from,但它把 Object.defineProperty 也给加上了,这就是一种浪费了。这个问题可以通过单独使用 core-js 的某个类库来解决,core-js 都是分开的。
  2. babel-polyfill 会污染全局变量,给很多类的原型链上都作了修改,如果我们开发的也是一个类库供其他开发者使用,这种情况就会变得非常不可控。

因此在实际使用中,如果我们无法忍受这两个缺点(尤其是第二个),通常我们会倾向于使用 babel-plugin-transform-runtime

但如果代码中包含高版本 js 中类型的实例方法 (例如 [1,2,3].includes(1)),这还是要使用 polyfill。

babel-register

babel-register 模块改写 require 命令,为它加上一个钩子。此后,每当使用 require 加载 .js、.jsx、.es 和 .es6 后缀名的文件,就会先用 babel 进行转码。

使用时,必须首先加载 require(‘babel-register’)。

需要注意的是,babel-register 只会对 require 命令加载的文件转码,而 不会对当前文件转码。

另外,由于它是实时转码,所以 只适合在开发环境使用。

babel-runtime 和 babel-plugin-transform-runtime

使用构建工具的插件((webpack 的 babel-loader, rollup 的 rollup-plugin-babel)

babel-loader

babel-loader 也会读取 .babelrc 或者 package.json 中的 babel 段作为自己的配置,之后的内核处理也是相同
注:babel-loader和webpack交互,因此需要在webpack中进行配置

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel-loader'
    }
  ]
}

运行方式和插件

运行阶段

babel中共分为三个阶段

解析

转换

babel本省不具有任何转化功能,他把转化的功能都分解到一个个plugin里面。
不配置任何插件是,进过babel的代码和输入是相同的

生成

插件

语法插件

语法插件在解析这一步能使babel解析更多的语法(babel 内部使用的解析类库叫做 babylon)

转译插件

转移插件在转换阶段把源码转换输出
注:同一类语法可能同时存在语法插件版本和转译插件版本。如果我们使用了转译插件,就不用再使用语法插件了。

配置文件

配置文件的目的是配置使用babel插件

  • 将插件的名字增加到配置文件中 (根目录下创建 .babelrc 或者 package.json 的 babel 里面,格式相同)
  • 使用 npm install babel-plugin-xxx 进行安装

preset

为了节省配置文件的时间和精力,babel提供了一组插件的集合,避免重复定义和安装。比如 es2015 是一套规范,包含大概十几二十个转译插件。如果每次要开发者一个个添加并安装,配置文件很长不说,npm install 的时间也会很长,更不谈可能还要同时使用其他规范呢。

官方内容

目前包括 env, react, flow, minify 等。
注:其中最重要的是env

stage-x

包含当年最新规范的草案,每年更新

  • Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
  • Stage 1 - 提案: 初步尝试。
  • Stage 2 - 初稿: 完成初步规范。
  • Stage 3 - 候选: 完成规范和浏览器初步实现。
  • Stage 4 - 完成: 将被添加到下一年度发布。
    注:
    低一级的stage会包含说有高级 stage 的内容,例如 stage-1 会包含 stage-2, stage-3 的所有内容。
    stage-4 在下一年更新会直接放到 env 中,所以没有单独的 stage-4 可供使用。

es201x, latest

这些是已经纳入到标准规范的语法。
例如 es2015 包含 arrow-functions,es2017 包含 syntax-trailing-function-commas。但因为 env 的出现,使得 es2016 和 es2017 都已经废弃。所以我们经常可以看到 es2015 被单独列出来,但极少看到其他两个。

latest 是 env 的雏形,它是一个每年更新的 preset,目的是包含所有 es201x。但也是因为更加灵活的 env 的出现,已经废弃。

执行顺序

  • Plugin 会运行在 Preset 之前。
  • Plugin 会从前到后顺序执行。
  • Preset 的顺序则从后向前。

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

插件和preset的配置项

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

最需要配置的当属 env,如下:

"presets": [
    // 带了配置项,自己变成数组
    [
        // 第一个元素依然是名字
        "env",
        // 第二个元素是对象,列出配置项
        {
          "module": false
        }
    ],

    // 不带配置项,直接列出名字
    "stage-2"
]

env

env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。
例如目标浏览器支持 es2015,那么 es2015 这个 preset 其实是不需要的,于是代码就可以小一点(一般转化后的代码总是更长),构建时间也可以缩短一些。

如果不写任何配置项,env 等价于 latest,也等价于 es2015 + es2016 + es2017 三个相加(不包含 stage-x 中的插件)。env 包含的插件列表维护在这里

下面列出几种比较常用的配置方法:

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "safari >= 7"]
      }
    }]
  ]
}

将目标设置为 nodejs,并且支持 6.10 及以上的版本。

{
  "presets": [
    ["env", {
      "targets": {
        "node": "6.10"
      }
    }]
  ]
}