babel学习笔记

babel 到底做了什么?怎么做的?

简单来说,babel主要用于把 JavaScript 中 es2015/2016/2017/2046 的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行。本文以 babel 6.x 为基准进行讨论。
严格来说,babel 也可以转化为更低的规范。但以目前情况来说,es5 规范已经足以覆盖绝大部分浏览器,因此常规来说转到 es5 是一个安全且流行的做法。

接下来,我们逐个给大家介绍一下babel里的包都是干什么的

babel-core

可以看做 babel 的编译器。babel 的核心 api 都在这里面,比如 transform,主要用来处理转码。它会把我们的 js 代码,抽象成 ast,即抽象语法树,是源代码的抽象语法结构的树状表现形式。我们可以理解为,它定义的一种分析 js 语法的树状结构。也就是说 es6 的新语法,跟老语法是不一样的,那我们怎么去定义这个语法呢。所以必须要先转成 ast,去发现这个语法的 kind,分别做对应的处理,才能转化成 es5。

babel-cli

提供命令行运行 babel。也就是你可以 babel filename 去对文件转码。
如下图:

babel script.js --out-file script-compiled.js

babel-runtime

这个包很简单,就是引用了 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
    core-js 是用于 JavaScript 的组合式标准化库,它包含 es5 (e.g: object.freeze), es6的 promise,symbols, collections, iterators, typed arrays, es7+提案等等的 polyfills 实现。也就是说,它几乎包含了所有 JavaScript 最新标准的垫片。

// 需要单个引用
require(‘core-js/array/reduce’);
require(‘core-js/object/values’);

  • regenerator
    它是来自于 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-polyfill

babel-runtime 已经是一堆 polyfill 了,为什么这里还有一个类似的包,它同样是引用了 core-js 和 regenerator,垫片支持是一样的。官网是这么说的,babel-polyfill 是为了模拟一个完整的ES2015 +环境,旨在用于应用程序而不是库/工具。并且使用babel-node时,这个polyfill会自动加载。
也就是说,它会让我们程序的执行环境,模拟成完美支持 es6+ 的环境,毕竟无论是浏览器环境还是 node 环境对 es6+ 的支持都不一样。它是以重载全局变量 (E.g: Promise),还有原型和类上的静态方法(E.g:Array.prototype.reduce/Array.form),从而达到对 es6+ 的支持。不同于 babel-runtime 的是,babel-polyfill 是一次性引入你的项目中的,就像是 React 包一样,同项目代码一起编译到生产环境。
注意:babel-polyfill 只是为当前环境全局下注入垫片,ES6 语法(E.g: arrow func,esModules)还是要加入 plugins 去 transform 的。

plugins

要说 plugins 就不得不提 babel 编译的过程。babel 编译分为三步:

  1. parser:通过 babylon 解析成 AST。
  2. transform[s]:All the plugins/presets ,进一步的做语法等自定义的转译,仍然是 AST。
  3. generator: 最后通过 babel-generator 生成 output string。

所以 plugins 是在第二步加强转译的,所以假如我们自己写个 plugin,应该就是对 ast 结构做一个遍历,操作。

babel-plugin-transform-runtime

上面我们知道,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 }; 
}
console.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

我们时常在项目中看到 .babelrc 中使用 babel-plugin-transform-runtime,而 package.json 中的 dependencies (注意不是 devDependencies) 又包含了 babel-runtime,那这两个是不是成套使用的呢?他们又起什么作用呢?
先说 babel-plugin-transform-runtime。
babel 会转换 js 语法,之前已经提过了。以 async/await 举例,如果不使用这个 plugin (即默认情况),转换后的代码大概是:

// babel 添加一个方法,把 async 转化为 generator
function _asyncToGenerator(fn) { return function () {....}} // 很长很长一段

// 具体使用处
var _ref = _asyncToGenerator(function* (arg1, arg2) {
  yield (0, something)(arg1, arg2);
});

可以看到,这个 _asyncToGenerator 在当前文件被定义,然后被使用了,以替换源代码的 await。但每个被转化的文件都会插入一段 _asyncToGenerator 这就导致重复和浪费了。
在使用了 babel-plugin-transform-runtime 了之后,转化后的代码会变成

// 从直接定义改为引用,这样就不会重复定义了。
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);

// 具体使用处是一样的
var _ref = _asyncToGenerator3(function* (arg1, arg2) {
  yield (0, something)(arg1, arg2);
});

从定义方法改成引用,那重复定义就变成了重复引用,就不存在代码重复的问题了。

transform-runtime 对比 babel-polyfill

其实通过上面的介绍我们已经了解他们是干什么的了,这里再稍微总结区分一下吧。我在这里把 babel-runtime 和 babel-plugin-transform-runtime 统称为 transform-runtime,因为一起用才比较好。

  • babel-polyfill 是当前环境注入这些 es6+标准的垫片,好处是引用一次,不再担心兼容,而且它就是全局下的包,代码的任何地方都可以使用。缺点也很明显,它可能会污染原生的一些方法而把原生的方法重写。如果当前项目已经有一个polyfill的包了,那你只能保留其一。而且一次性引入这么一个包,会大大增加体积。如果你只是用几个特性,就没必要了,如果你是开发较大的应用,而且会频繁使用新特性并考虑兼容,那就直接引入吧。
  • transform-runtime 是利用 plugin 自动识别并替换代码中的新特性,你不需要再引入,只需要装好 babel-runtime 和 配好 plugin 就可以了。好处是按需替换,检测到你需要哪个,就引入哪个 polyfill,如果只用了一部分,打包完的文件体积对比 babel-polyfill 会小很多。而且transform-runtime 不会污染原生的对象,方法,也不会对其他 polyfill 产生影响。所以 transform-runtime 的方式更适合开发工具包,库,一方面是体积够小,另一方面是用户(开发者)不会因为引用了我们的工具,包而污染了全局的原生方法,产生副作用,还是应该留给用户自己去选择。缺点是随着应用的增大,相同的 polyfill 每个模块都要做重复的工作(检测,替换),虽然 polyfill 只是引用,编译效率不够高效。

另外,关于 babel-runtime 为什么是 dependencies 依赖。它只是一个集中了 polyfill 的 library,对应需要的 polyfill 都是要引入项目中,并跟项目代码一起打包的。不过它不会都引入,你用了哪个,plugin 就给你 require 哪个。所以即使你最终项目只是 require(‘babel-runtime/core-js/object/values’)其中的一个文件,但是对于这包来说,也是生产依赖的。

注意:babel-polyfill 并不是一定会污染全局环境,在引入这个 js,并运行的时候,它会先判断当前有没有这个方法,再看要不要重写。

presets

各种配置 plugin 实在是费劲,es6+ 编译要加入好多 plugins,比如为了在 node 中使用 esmodule,要把 esmodule 转化成 commomjs,使用 transform-es2015-modules-commonjs,还有 asyncToGenerator,React jsx转化等等,不仅要装好多,还要配好多。
presets 就是 plugins 的组合,你也可以理解为是套餐… 主要有

  • env
  • es2015
  • react
  • lastet
  • stage-x 具体的语法属于哪个 stage 可参照tc39

大部分的 presets 我觉得都不需要介绍了,官网上写的比较详细。而且 babel-preset-lastet(包括 es2105,es2016,es2017)跟默认情况下的 env 是一样的,也就是说包括 lastest 在内,这四个 presets 都要被 babel-preset-env 代替。即:

{ “presets”: [“latest”] } === { “presets”: [“env”] }

babel-preset-env

这个 preset 真是神器啊,它能根据当前配置的运行环境,自动确定你需要的 plugins 和 polyfills。通过各个 es标准 feature 在不同浏览器以及 node 版本的支持情况,再去维护一个 feature 跟 plugins 之间的映射关系,最终确定需要的 plugins。

preset-env 配置
{   
	"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 这个东西要单独装一下。   
    ]
}

babel 的配置

目前 babel 官方推荐是写到 .babelrc 文件下,你还可以在 package.json 里面添加 babel 字段。不用配置文件的话,可以把配置当做参数传给 babel-cli

  • .babelrc
 {
  "presets": [
    "env"
  ],
  "plugins": [
    ["transform-runtime", {
      "helpers": true,
      "polyfill": true,
      "regenerator": true,
      "moduleName": "babel-runtime"
    }]
  ]
}
  • 写到 package.json
"babel": {
  "presets": [
    "env"
  ],
}
  • babel cli
 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/,
      }
    ]
  }

参考文档:
https://juejin.im/post/59b9ffa8f265da06710d8e89#heading-21
https://juejin.im/post/5c19c5e0e51d4502a232c1c6#heading-13

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值