这个问题是对本身的发问,但我相信会有不少跟我同样的同窗。 对于 babel 的使用,近半年来一直停留在与 webpack 结合使用,以及在浏览器开发环境下。致使不少 babel 的包,我都不清楚他们是干吗的。好比 babel-register,还有 babel-runtime,各类 presets 的区别,transform-runtime 和 babel-polyfill 的区别,helpers 是干吗的。尽管网上的 babel 的教程不少了,可是解答本身的一些疑问,仍是要花费一些功夫。因此抽出时间来总结一下。若是你对于以上概念已经比较清楚了,就不须要往下看了。javascript
本次的 example 代码都在 github 上,并且每一个文件夹都有详细的 README,说明个人使用方式。能够去参照一下用例的使用,并 clone 下来本身研究一下。前端
说实话,从我作前端的时候,接触 babel 的时候,就已是 babel 6 了,可是这不妨碍了解一下它的重大版本变化。 上一个版本 babel 5 是全家桶,包括各类package, plugins,尽量的想经过你的一次安装,达到全能的效果。不过你如今安装npm install babel
,会获得一个 warning。babel 6 是 2015年10月30号发布,主要作了如下更新:java
babel-core
,babel-node
,babel-cli
...差很少了,我感受其余的也不须要了解了。node
babel 里面有好多的包,因此必须搞清楚他们都是干吗的,才能让咱们更好的使用这个工具。react
能够看作 babel 的编译器。babel 的核心 api 都在这里面,好比 transform,主要都是处理转码的。它会把咱们的 js 代码,抽象成 ast,即 abstract syntax tree 的缩写,是源代码的抽象语法结构的树状表现形式。咱们能够理解为,它定义的一种分析 js 语法的树状结构。也就是说 es6 的新语法,跟老语法是不同的,那咱们怎么去定义这个语法呢。因此必需要先转成 ast,去发现这个语法的 kind,分别作对应的处理,才能转化成 es5.android
主要 api:webpack
var babel = require('babel-core');
var transform = babel.transform;
复制代码
transform("code", options) // => { code, map, ast }
复制代码
var path = require('path');
babel.transformFile(path.resolve(__dirname) + "/test.js", {
presets: ['env'],
plugins: ['transform-runtime'],
}, function(err, result) {// { code, map, ast }
console.log(result);
});
复制代码
var result = babel.transformFileSync(path.resolve(__dirname) + "/test.js", {
presets: ['env'],
plugins: ['transform-runtime'],
});
console.log(result, 'res');
复制代码
反转,你把 ast 传入,解析为 code 代码。ios
optionsgit
提供命令行运行 babel。也就是你能够 babel filename
去对文件转码。 安装的话es6
npm install --save-dev babel-cli
npm isntall babel-cli -g
复制代码
使用对应就是
node_module/.bin/babel script.js --out-file script-compiled.js
babel script.js --out-file script-compiled.js
复制代码
具体使用仍是看官方文档吧,我就不搬文档了。
babel-cli 中的一个command,用来生成一段代码,包含 babel 全部的 helper 函数。
首先咱们须要了解什么是 helpers。babel 有不少辅助函数,例如 toArray函数, jsx转化函数。这些函数是 babel transform 的时候用的,都放在 babel-helpers
这个包中。若是 babe 编译的时候检测到某个文件须要这些 helpers,在编译成模块的时候,会放到模块的顶部。 像这样
(function(module, exports, __webpack_require__) {
function _asyncToGenerator(fn) { return function () { }; } // 模块顶部定义 helper
// some code
// async 语法已经被 transform-async-to-generator 转化,再利用 helper 函数包装,消费 generator。
const func = (() => {
var _ref = _asyncToGenerator(function* () {
console.log('begin');
yield new Promise(function (resolve) {
setTimeout(function () {
resolve();
}, 1000);
});
console.log('done');
});
})
})
复制代码
可是若是多个文件都须要提供,会重复引用这些 helpers,会致使每个模块都定义一份,代码冗余。因此 babel 提供了这个命令,用于生成一个包含了全部 helpers 的 js 文件,用于直接引用。而后再经过一个 plugin,去检测全局下是否存在这个模块,存在就不须要从新定义了。
使用:
node_modules/.bin/babel-external-helpers > helpers.js
复制代码
注意:示例代码的包都是装到项目中的,也就是本地。一样你能够全局安装直接执行。 2. 安装 plugin
npm install --save-dev babel-plugin-external-helpers
复制代码
{
"plugins": ["external-helpers"]
}
复制代码
require('./helpers.js');
复制代码
这样就能够啦,仍是能够减小不少代码量的。另外若是使用了 transform-runtime,就不须要生成 helpers.js 文件了,这个在后面的 babel-runtime 再说。
也是 babel-cli 下面的一个 command,主要是实现了 node 执行脚本和命令行写代码的能力。举两个栗子就清楚了。
node 环境确定是不支持 jsx 的
// test.js
const React = require('react');
const elements = [1, 2, 3].map((item) => {
return (
<div>{item}</div>
)
});
console.log(elements);
复制代码
执行 test.js,会报错,不认识这个语法。
node test.js //报错
复制代码
可是使用 babel-node 就能够。
node_modules/.bin/babel-node --presets react test.js
复制代码
--presets react 是参数,等同于
{
"presets": ["react"]
}
复制代码
执行正常。
注意: 本文全部代码示例,均在 node 版本 4.8.4 下执行。
写个解构赋值的,直接运行 node,不支持。
运行 node_modules/.bin/babel-node --presets env
获得 a 的 value 是 1。
经过栗子基本已经介绍了 babel-node 的用法了,就是方便咱们日常开发时候,写一些脚本的。因此它不适用于生产环境。另外,babel-node 已经内置了 polyfill,并依赖 babel-register 来编译脚本。好,那 babel-register 是什么呢
npm install babel-register --save-dev
复制代码
babel-node 能够经过它编译代码,能够了解到,它其实就是一个编译器。咱们一样能够在代码中引入它 require('babel-register')
,并经过 node 执行咱们的代码。
它的原理是经过改写 node 自己的 require,添加钩子,而后在 require 其余模块的时候,就会触发 babel 编译。也就是你引入require('babel-register')
的文件代码,是不会被编译的。只有经过 require 引入的其余代码才会。咱们是否是能够理解,babel-node 就是在内存中写入一个临时文件,在顶部引入 babel-register,而后再引入咱们的脚本或者代码?
举个栗子,仍是 node 中执行 jsx,要经过 babel 编译。咱们能够把 jsx 的代码 a.js 编译完输出到一个 b.js,而后 node b.js
也是能够执行的。可是太麻烦,不利于开发。让咱们看一下经过 register 怎么用:
// register.js 引入 babel-register,并配置。而后引入要执行代码的入口文件
require('babel-register')({ presets: ['react'] });
require('./test')
复制代码
// test.js 这个文件是 jsx...
const React = require('react');
const elements = [1, 2, 3].map((item) => {
return (
<div>{item}</div>
)
});
console.log(elements);
复制代码
// 执行
$ node register.js
复制代码
它的特色就是实时编译,不须要输出文件,执行的时候再去编译。因此它很适用于开发。总结一下就是,多用在 node 跑程序,作实时编译用的,一般会结合其余插件做编译器使用,好比 mocha 作测试的时候。
值得一提的是,babel-register 这个包以前是在 babel-core 下面的,因此也能够 require('babel-core/register')
去引入,跟require('babel-register')
是同样的。可是,babel 的团队把 register 独立出来了,并且将来的某一天(升 7.0)会从 babel-core 中废除,因此咱们如今最好仍是使用 babel-register 吧。babel-core/register.js
npm install babel-runtime --save
复制代码
这个包很简单,就是引用了 core-js 和 regenerator,而后生产环境把它们编译到 dist 目录下,作了映射,供使用。那么什么是 core-js 和 regenerator 呢。 首先咱们要知道上面提到的 babel-core 是对语法进行 transform 的,可是它不支持 build-ints(Eg: promise,Set,Map),prototype function(Eg: array.reduce,string.trim),class static function (Eg:Array.form,Object.assgin),regenerator (Eg:generator,async)等等拓展的编译。因此才要用到 core-js 和 regenerator。
core-js 是用于 JavaScript 的组合式标准化库,它包含 es5 (e.g: object.freeze), es6的 promise,symbols, collections, iterators, typed arrays, es7+提案等等的 polyfills 实现。也就是说,它几乎包含了全部 JavaScript 最新标准的垫片。不过为何它不把 generator 也实现了... 😁
// 好比,只不过须要单个引用
require('core-js/array/reduce');
require('core-js/object/values');
复制代码
它是来自于 facebook 的一个库,连接。主要就是实现了 generator/yeild, async/await。
因此 babel-runtime 是单纯的实现了 core-js 和 regenerator 引入和导出,好比这里是 filter 函数的定义,作了一个中转并处理了 esModule 的兼容。
module.exports = { "default": require("core-js/library/fn/array/filter"), __esModule: true };
复制代码
还记得提 babel-external-helpers 的时候,介绍 helpers 了吗。babel-runtime 里面的 helpers 就至关于咱们上面经过 babel-external-helpers 生成的 helpers.js。只不过它把每一个 helper 都单独放到一个文件夹里。这样,配合 transform-runtime 使用的时候,须要用 helper 转化的时候,就从 babel-runtime 中直接引用了。
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
复制代码
能够单独引入require('babel-runtime/core-js/object/values');
不过这些模块都作了 esModule 的兼容处理,也就是上面引入的模块是{ "default": require("core-js/library/fn/array/filter"), __esModule: true }
这样的,要使用还得加上 .default
。因此咱们期待的是,最好能有帮咱们自动处理的插件,babel-plugin-transform-runtime
就是用来作这个的。这个咱们放到 plugin 去讲。
npm install babel-polyfill --save
复制代码
babel-runtime 已是一堆 polyfill 了,为何这里还有一个相似的包,它一样是引用了 core-js 和 regenerator,垫片支持是同样的。官网是这么说的,babel-polyfill 是为了模拟一个完整的ES2015 +环境,旨在用于应用程序而不是库/工具。而且使用babel-node时,这个polyfill会自动加载(这个咱们在介绍 babel-node 的最后已经说了)。
也就是说,它会让咱们程序的执行环境,模拟成完美支持 es6+ 的环境,毕竟不管是浏览器环境仍是 node 环境对 es6+ 的支持都不同。它是以重载全局变量 (E.g: Promise),还有原型和类上的静态方法(E.g:Array.prototype.reduce/Array.form),从而达到对 es6+ 的支持。不一样于 babel-runtime 的是,babel-polyfill 是一次性引入你的项目中的,就像是 React 包同样,同项目代码一块儿编译到生产环境。
咱们结合 babel-register 去使用一下
// index.js
require('babel-core/register')({});
require('babel-polyfill'); // 是的,你要手动引入。
require('./async');
复制代码
// async.js
async function a() {
console.log('begin');
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000)
})
console.log('done');
}
a();
复制代码
$ node index.js
复制代码
完美运行。 注意:babel-polyfill 只是为当前环境全局下注入垫片,ES6 语法(E.g: arrow func,esModules)仍是要加入 plugins 去 transform 的。
要说 plugins 就不得不提 babel 编译的过程。babel 编译分为三步:
因此 plugins 是在第二步增强转译的,因此假如咱们本身写个 plugin,应该就是对 ast 结构作一个遍历,操做。
上面咱们知道,transform-runtime 是为了方便使用 babel-runtime 的,它会分析咱们的 ast 中,是否有引用 babel-rumtime 中的垫片(经过映射关系),若是有,就会在当前模块顶部插入咱们须要的垫片。试一下:
npm install babel-plugin-transform-runtime
复制代码
// 编译前
console.log(Object.values({ 1: 2 }));
复制代码
node_modules/.bin/babel --plugins transform-runtime values.js
复制代码
// 编译后
'use strict';
var _values = require('babel-runtime/core-js/object/values');
var _values2 = _interopRequireDefault(_values);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
onsole.log((0, _values2.default)({ 1: 2 }));
复制代码
另外,它还有几个配置
// 默认值
{
"plugins": [
["transform-runtime", {
"helpers": true,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
复制代码
若是你只须要用 regenerator,不须要 core-js 里面的 polyfill 那你就能够在 options 中把 polyfill 设为 false。helpers 设为 false,就至关于没有启用 babel-plugin-external-helpers
的效果,好比翻译 async 的时候,用到了 asyncToGenerator 函数,每一个文件还会从新定义一下。moduleName 的话,就是用到的库,你能够把 babel-runtime 换成其余相似的。
其实经过上面的介绍咱们已经了解他们是干什么的了,这里再稍微总结区分一下吧。我在这里把 babel-runtime 和 babel-plugin-transform-runtime 统称为 transform-runtime,由于一块儿用才比较好。
另外,关于 babel-runtime 为何是 dependencies 依赖。它只是一个集中了 polyfill 的 library,对应须要的 polyfill 都是要引入项目中,并跟项目代码一块儿打包的。不过它不会都引入,你用了哪一个,plugin 就给你 require 哪一个。因此即便你最终项目只是 require('babel-runtime/core-js/object/values')
其中的一个文件,可是对于这包来讲,也是生产依赖的。
注意:babel-polyfill 并非必定会污染全局环境,在引入这个 js,并运行的时候,它会先判断当前有没有这个方法,在看要不要重写。如上图
各类配置 plugin 实在是费劲,es6+ 编译要加入好多 plugins,好比为了在 node 中使用 esmodule,要把 esmodule 转化成 commomjs,使用 transform-es2015-modules-commonjs
,还有 asyncToGenerator,React jsx转化等等,不只要装好多,还要配好多。
presets 就是 plugins 的组合,你也能够理解为是套餐... 主要有
大部分的 presets 我以为都不须要介绍了,官网上写的比较详细。并且 babel-preset-lastet(包括 es2105,es2016,es2017)跟默认状况下的 env 是同样的,也就是说包括 lastest 在内,这四个 presets 都要被 babel-preset-env 代替。即:
{ "presets": ["latest"] } === { "presets": ["env"] }
复制代码
这个 preset 真是神器啊,它能根据当前的运行环境,自动肯定你须要的 plugins 和 polyfills。经过各个 es标准 feature 在不一样浏览器以及 node 版本的支持状况,再去维护一个 feature 跟 plugins 之间的映射关系,最终肯定须要的 plugins。
详情:
{
"presets": [
[
"env",
{
"targets": { // 配支持的环境
"browsers": [ // 浏览器
"last 2 versions",
"safari >= 7"
],
"node": "current"
},
"modules": true, //设置ES6 模块转译的模块格式 默认是 commonjs
"debug": true, // debug,编译的时候 console
"useBuiltIns": false, // 是否开启自动支持 polyfill
"include": [], // 老是启用哪些 plugins
"exclude": [] // 强制不启用哪些 plugins,用来防止某些插件被启用
}
]
],
plugins: [
"transform-react-jsx" //若是是须要支持 jsx 这个东西要单独装一下。
]
}
复制代码
主要介绍 debug 和 很好用的 useBuiltIns 吧。
开启debug后,编译结果会获得使用的 targets,plugins,polyfill 等信息
Using targets:
{
"chrome": "59",
"android": "4.4.3",
"edge": "14",
"firefox": "54",
"ie": "10",
"ios": "10",
"safari": "7",
"node": "4.8.4"
}
Modules transform: commonjs
Using plugins:
check-es2015-constants {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
transform-es2015-arrow-functions {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
transform-es2015-block-scoped-functions {"android":"4.4.3","ie":"10","safari":"7"}
transform-es2015-block-scoping {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
...
Using polyfills:
es6.typed.array-buffer {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.int8-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.uint8-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.uint8-clamped-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.int16-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
...
复制代码
env 会自动根据咱们的运行环境,去判断须要什么样的 polyfill,并且,打包后的代码体积也会大大减少,可是这一切都在使用 useBuiltIns,并且须要你安装 babel-polyfill,并 import。它会启用一个插件,替换你的import 'babel-polyfill'
,不是整个引入了,而是根据你配置的环境和我的须要单独的引入 polyfill。 我尝试了一下是否真的有效,下面是个人对比实验过程:
step1: 首先是这样一段测试编译的代码,有 jsx,Object.values,async。env 的配置除了 useBuiltIns 都跟上面的配置同样。而后经过 webpack + babel-loader 打包,生成 build.js
require('./async');
// import 'babel-polyfill';
const React = require('react');
const elements = [1, 2, 3].map((item) => {
return (
<div>{item}</div>
)
});
console.log(elements);
async function a() {
console.log('begin');
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000)
})
console.log('done');
}
a();
console.log(Object.values({ 1: 2 }));
console.log(Array.isArray([]));
复制代码
step2: 而后经过设置不一样的参数,打包,获取 build.js,并执行。获得下表
preset-env 条件下 | useBuiltIns: true |
不引入 polyfill | build.js 代码体积 158k,node build.js 执行报错。 |
引入 polyfill | build.js 体积 369k,执行经过。包确实减少了。 |
具体的过程、截图猛戳 这里
最终的结论就是,使用了 useBuiltIns 确实体积变小了,比直接 import 'babel-polyfill'
好了许多。
step3: 而后... 我又试了一下 env 下,使用 transform-runtime。在不加 useBuiltIns,不引入 babel-polyfill 的状况下。build.js 体积234k,执行经过。
咦,这样好像体积更小啊。别忘了,咱们的 babel-polyfill 是配置了执行环境的,经过环境看你须要哪些 polyfill。而 transform-runtime,是发现咱们代码须要什么 polyfill,固然会少不少了。因此,又回到了用哪一个的问题... 😓 参考上面的总结。
helpers 的问题。开发项目,使用 preset-env,并 import 'babel-polyfill'
,可是 helpers 好像没有地方配置。并且我试了两个文件分别用 async 函数,编译后每一个模块都定义了 asyncToGenerat 函数,这种状况下我以为最后就是本身生成一个 helpers.js 文件了。
如今看起来开发大点的项目,最好用的配置应该就是 preset-env,肯定本身的运行环境,若是有须要,再加上 useBuiltIns,并生成一份 helpers.js 的文件。不过,一切仍是看你的需求,个人前提是开发大点的“项目”,不过了解了这些东西,你会作出本身的选择。
目前 babel 官方推荐是写到 .babelrc 文件下,你还能够在 package.json 里面添加 babel 字段。不用配置文件的话,能够把配置当作参数传给 babel-cli
{
"presets": [
"env"
],
"plugins": [
["transform-runtime", {
"helpers": true,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
复制代码
"babel": {
"presets": [
"env"
],
}
复制代码
babel script.js --plugins=transform-runtime --presets=env
复制代码
比较经常使用,除了 babel 本身的包,多装一个 babel-loader
配合 webpack 使用。并在 webpack.config.js 中加入 loader 的配置
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/,
}
]
}
复制代码
项目里的代码都是用 es6+ 写的,可是作单元测试的时候,测试框架并不认识你的什么 esModule,es6+ 的一些语法,mocha 是 node 程序,因此你要把 esModule 转化成 commomjs 之类的。
mocha 是支持编译器的,经过 --compilers
指定,这里咱们用 babel,举个栗子
// 求和函数 add.js
const add = (x, y) => x + y;
export default add;
复制代码
// 测试脚本 add.test.js
import { expect } from 'chai'; // chai 是断言库
import add from './add';
describe('es6 两数相加', () => {
it('2 + 4 = 6', () => {
expect(add(2, 4)).equal(6);
})
});
复制代码
./node_modules/mocha/bin/mocha --compilers js:babel-register add.test.js
复制代码
由于 mocha 终究是在跑 node 程序的,适用于实时编译,因此能够用 babel-register 作编译器。
总结这些东西花了我两三天的时间,虽然搞清楚了这些包是干吗的,可是又在想到底应不该该花时间研究这些,工具始终是用来使用的,对于 babel 来讲更应该研究的是它对 ast 的处理方式?不过看到本身的产出,我以为是有必要的,另外,由于对工具更进一步的了解,才能更好的在项目中使用它们,而不是一顿照搬,代码是能够正经常使用了,可是可能会有不少不须要的东西,致使代码体积变大。“割鸡焉用牛刀”,我以为仍是须要有精益求精的精神。但愿对你有所帮助。
个人我的博客地址 https://github.com/sunyongjian/blog ,欢迎订阅,star,谢谢。