了解babel
1.1 功能
把 JavaScript 中 es2015/2016/2017/2046 的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行
1.2使用方法
- 使用单体文件 (standalone script), 对单个文件进行编译
- 命令行 (cli)
- 构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。
- 后面两种比较常见。第二种多见于 package.json 中的 scripts 段落中的某条命令
- 第三种就直接集成到构建工具中。
- 这三种方式只有入口不同而已,调用的 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 最本质的需求。
- 比起语法插件,转译插件其实更好理解,比如箭头函数 (a) => a 就会转化为 function (a) {return a}。完成这个工作的插件叫做 babel-plugin-transform-es2015-arrow-functions。
- 同一类语法可能同时存在语法插件版本和转译插件版本。如果我们使用了转译插件,就不用再使用语法插件了。
- 当我们添加 语法插件 之后,在解析这一步就使得 babel 能够解析更多的语法。(顺带一提,babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发)
1.4 配置文件
使用步骤
- 将插件的名字增加到配置文件中 (根目录下创建 .babelrc 或者 package.json 的 babel 里面,格式相同)
- 使用 npm install babel-plugin-xxx 进行安装
Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
Stage 1 - 提案: 初步尝试。
Stage 2 - 初稿: 完成初步规范。
Stage 3 - 候选: 完成规范和浏览器初步实现。
Stage 4 - 完成: 将被添加到下一年度发布
- Plugin 会运行在 Preset 之前。
- Plugin 会从前到后顺序执行。
- Preset 的顺序则 刚好相反(从后向前)。
preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是 [‘es2015’, ‘stage-0’]。这样必须先执行 stage-0 才能确保 babel 不报错。因此我们编排 preset 的时候,也要注意顺序,其实只要按照规范的时间顺序列出即可。
简略情况下,插件和 preset 只要列出字符串格式的名字即可。但如果某个 preset 或者插件需要一些配置项(或者说参数),就需要把自己先变成数组。第一个元素依然是字符串,表示自己的名字;第二个元素是一个对象,即配置对象
"presets": [
// 带了配置项,自己变成数组
[
// 第一个元素依然是名字
"env",
// 第二个元素是对象,列出配置项
{
"module": false
}
],
// 不带配置项,直接列出名字
"stage-2"
]
2.1 env (重点)
env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。例如目标浏览器支持 es2015,那么 es2015 这个 preset 其实是不需要的,于是代码就可以小一点(一般转化后的代码总是更长),构建时间也可以缩短一些 如果不写任何配置项,env 等价于 latest,也等价于 es2015 + es2016 + es2017 三个相加(不包含 stage-x 中的插件).env包含的插件列表
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}]
]
}
如上配置将考虑所有浏览器的最新2个版本(safari大于等于7.0的版本)的特性,将必要的代码进行转换。而这些版本已有的功能就不进行转化了。
{
"presets": [
["env", {
"targets": {
"node": "6.10"
}
}]
]
}
如上配置将目标设置为 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 时经常会使用如下模式:
- 把 babel-cli 安装为 devDependencies
- 在 package.json 中添加 scripts (比如 prepublish),使用 babel 命令编译文件
- npm publish
这样既可以使用较新规范的 JS 语法编写源码,同时又能支持旧版环境。因为项目可能不太大,用不到构建工具 (webpack 或者 rollup),于是在发布之前用 babel-cli 进行处理
3.1.2 babel-node
- babel-node 是 babel-cli 的一部分,它不需要单独安装。
- 它的作用是在 node 环境中,直接运行 es2015 的代码,而不需要额外进行转码。例如我们有一个 js 文件以 es2015 的语法进行编写(如使用了箭头函数)。我们可以直接使用 babel-node es2015.js 进行执行,而不用再进行转码了
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-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
详细见原文…