babel的正确打开姿势

2019/03/20

本来题目是babel是怎么运行的,但是看了一天文档之后,发现只会会简单的使用了,不会太懵逼了而已,完全算不算是知道怎么运行啊,所以名字就改成了现在这样,感觉莫名的舒适多了。然后进入正题,主要讲的是

什么是babel

babel干了什么

babel怎么干的等等问题

babel是什么

babel直译之后的意思是巴别塔,是一座可以通天的高塔,具体可以参见维基百科,巴别塔主要指的是一个故事,上帝为了防止一群人很好的沟通,打乱了这些人的语言,并把这些人分散到了世界各地,这也暗指各地语言的不同的原因,这个不同语言更加像是因为地理隔离所以国内各种人的方言不一样的现象。语言不相同就好像这个故事很像现在各个浏览器的不兼容,而babel这个巴别塔就是大家的来源,可以统一各种语言。当然这只是个故事,这个故事说明了babel的核心目标——语言统一的平台。所以babel在javascript中主要解决的就是es6出现之后,各大浏览器无法识别新的语法,API等等。babel的主要工作分为

  • transform systax 语法转化,把新的语法转化为浏览器可以兼容的语法
  • polyfill features 兼容新的特性,增加polyfill代码,让新的API可以正常工作。
  • transform code 代码转换,实现兼容的同时也需要转化代码,比如所有的polyfill代码置顶,或者代码重新排布,以及支持jsx,就需要一个相对比较复杂的代码转化。

babel6.x和7.x

babel在6.0的时候把项目拆分成了多个小项目,包括babel-core, babel-cli, babel-register, babel-polyfill。这个可能增加了对babel理解的负担,就像webpack拆分代码为webpack和webpack-cli一样。babel拆分代码也是有原因的。babel7的时候把babel里的东西名字都改成@babel/xxx了,比如babel-core叫做@babel/core,而且所有的名字也都被改了一下。强推使用babel7,用起来会舒服很多。

@babel/core

babel的最核心的东西就是这个,大多数人只知道这个东西的用处就是,只要使用babel就必须装这个玩意,当然我也是酱紫滴。

首先执行下边的命令,安装babel-core,这个会安装babel7的版本

cnpm i @babel/core --save-dev

如果想要安装旧的版本需要可以执行

cnpm i babel-core --save-dev

然后在创建一个transfrom.js和index.js,并且执行node transform,就会发现生成了一个main.js文件。但是并没有通知babel转化的规则,所以main.js文件和index.js文件是一样的。

// transform.js
const babel = require('@babel/core')
const fs = require('fs')

const result = babel.transformFileSync('index.js')
fs.writeFileSync('./main.js', result.code)

// index.js
const a = () => {
console.log('hello world')
}
a()

babel-core上有很多方法供我们来转化代码。

主要是transform, transformFile, transformAst。分别是转化字符串,文件和ast语法树的,然后每个方法会带着两个额外的方法transformAsync和transformSync表示异步的转化或者同步的转化,示例中用的就是transformFileSync同步转化文件。

这个就是babel-core最核心的功能。也就是说有了babel-core的transform功能就可以不依赖任何其他的东西用来转化代码了。

详见官方文档 https://babel.docschina.org/docs/en/babel-core

@babel/cli

说到编译文件,用babel-core写个node脚本看起来挺方便的,但终究没有cli方便。

cli是什么意思呢,cli又叫command line interface,就是命令行交互的意思,所以babel为了大家可以更加方便的编译文件,不用写脚本所以就出现了babel-cli。

使用babel-cli的话可以全局安装babel-cli,就好了然后就会有一个全局的babel命令。

sudo cnpm i babel-cli -g 

使用babel-cli需要加一个.babelrc文件。这样才能读取配置。所以需要先创建一个.babelrc文件然后输入一个{}表示空的配置。

然后执行babel index.js --out-file main.js

这样就可以达到和上边的东西相同的效果了。

.babelrc

.babelrc这个文件负责babel的配置,除了写在.babelrc文件中,也可以在package.json中写一个babel的配置项,当然大多数人选择添加一个新的.babelrc文件,因为这样可以更加直观的感受到babel的配置。

.babelrc为什么要以点开头呢,因为.意味着文件是工具配置文件,为什么加个rc呢,rc的意思是run command,就是说这个文件会作用在项目运行阶段,比如.eslintrc还有.prettierrc都是这个意思,属于工具配置文件还会跟着项目一起跑。

.babelrc 通常会有plugin, preset等选项。

plugin是babel进行transform的基本单位,每一种plugin可以一种特定的语法,可以理解为一个plugin支持一个feature。

preset意思是“预制”,表示是一个plugin的集合,因为通常对于plugin都是成套出现去兼容某一种环境的。

Plugin的使用

比如这段代码里用到了,四种es6特性。

1. 对象属性简写

2. 函数的属性默认值

3. 箭头函数

4. const变量声明

const name = 'xiao han'
const a = { name }
const sayName = (name = 'xiao han') => {
console.log(name)
}

假如果.babelrc什么都不写,那么就什么都不做,因为babel会认为你什么特性都不想要。

然后安装一个支持箭头函数的plugin

cnpm i @babel/plugin-transform-arrow-functions --save-dev

修改.babelrc

{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}

这个将会意味着我们的babel可以识别箭头函数了,因为是babel7所以名字都会很长,当然这个不是重点,重点是他可以识别我们的箭头函数了。

下边是解析完成的代码,当然还有一点就是除了箭头函数以外的其他特性都没有被转化。

const name = 'xiao han';
const a = {
name
};

const sayName = function (name = 'xiao han') {
console.log(name);
};

@babel/plugin-transform-block-scoping 这个可以支持块级作用域,也会支持let和const

@babel/plugin-transform-parameters 这个可以用来支持默认参数

@babel/plugin-transform-shorthand-properties 这个可以支持属性简写

再加上第一个,就能很好的支持目前用到的这些特性了,最后编译完成的代码长这样,很显然现在是普通的es5的代码了。

var name = 'xiao han';
var a = {
name: name
};

var sayName = function () {
var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'xiao han';
console.log(name);
};

preset的使用

显然一个一个添加plugin很痛苦,这个时候就需要preset了,用plugin定制好一个preset使用就好了。

babel提供了一些默认的preset,最常见的就是这四个,但我们一般都会用env。

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript

env是最常用的preset,如果需要react那么再加一个react的env在后边就好了,就像多个plugin一样,添加多个preset也是可以的。所以下边的这个代码就能很好的支持所有的es6特性了。

{
"presets": ["@babel/preset-env"]
}

当然这个并不意味着只要添加了env的preset,就可以“sorry,我就是这么为所欲为”,比如开发者非常喜欢的两个特性,装饰器和class属性简写目前ECMA一直没去支持。

proposal 提案

没有进入ecma标准只是在提案阶段的语法在未安装plugin的时候是没有办法成功编译的,babel无法确定这是对的语法,所以必须要安装插件。不然会直接报错,而不是像上边那样可以成功运行。

比如下边的这种代码,需要装饰器,class属性简写。

function goodBoy(classContructor) {
classContructor.prototype.type = 'good body'
return classContructor
}

@goodBoy
class People {
name = 'xiao han'
sayName = () => {
console.log(this.name)
}
}

安装这两个plugin之后就好了

"plugins": [
"@babel/plugin-proposal-class-properties",
["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }]
]

对于正在提案的特性来说,也有对应的preset

"@babel/preset-stage-1"
"@babel/preset-stage-2"
"@babel/preset-stage-3"
"@babel/preset-stage-4"
"@babel/preset-stage-5"

名字是stage-0到stage-5表示不同的阶段

TC39 将提案分为以下阶段:

Stage 0 - 稻草人: 只是个想法可能会有相关的 Babel 插件。

Stage 1 - 提议: 值得深入。

Stage 2 - 草稿: 初始规范。

Stage 3 - 候选: 完整的规范和初始浏览器实现。

Stage 4 - 结束: 将被添加到下一个年度版本中。

在babel7里貌似不打算很好的支持这部分了,所以如果想要使用这些的话会告诉你用哪些plugin可以达到相同的效果。由于这些东西本身就不是很推荐使用,所以在babel7当中也推荐用plugin去装。

现在我们可以使用提案中的特性,可以使用es6的特性,几乎市面上支持的特性都有了,但是其实上边的东西都是在做transform,并不是polyfill。最开始我们就讲过babel编译代码是通过transform和polyfill共同完成的,所以还需要对新的API做polyfill才能使用api拓展型的特性。

@babel/polyfill

讲到es6的时候说api不兼容处理方法是polyfill,而语法不支持就需要transform和compile了,polyfill是个名词,不是兼容的意思,实际意思是垫片,表示这个浏览器缺某个api,就给他加个垫片就好了,这个单词也很形象的说明了babel-polyfill的一个特征,就是垫。怎么个垫法呢。

比如垫个这段代码之后,Object上就存在is这个静态方法了。

if (!Object.is) {
Object.is = function(v1, v2) {
// 判断NaN
if (v1 !== v1) {
return v2 !== v2
}
//判断-0
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2
}
// 其余情况
return v1 === v2
}
}

@babel/polyfill完全模仿了一个新的es6+的环境,让几乎所有的API都可以很好的被支持。说白了就是用es5实现了es6的所有API和新的数据结构,然后放在文件最前面,这样就模拟了一个完整的es6环境了。

所以babel-polyfill是个很庞大的东西,使用了之后基本上可以很好的跑在ie8+的任何浏览器了。使用babel-polyfill很简单,只是简单的引入就好了。当然需要借助于打包工具才能让这些代码被打包到一个文件里边。因为polyfill是直接打包进入代码中的,所以应该安装在dependencies里而不是devDependencies里。

import '@babel/polyfill'
Array.from({ length: 10 })

如果不用打包工具的话。可以直接把 @babel/polyfill/dist/polyfill.min.js 这个文件取出来,然后script引入就好了。由于这个文件比较大,大概有94kb。所以不介意这么去做。

目前babel的这些东西已经完全可以让开发者在项目中随心所欲的写代码了,想要什么语法,查查官网找找对应的plugin就好了,如果用ECMA已经标准化的东西,也只要简单的加一个preset-env就好了。看起来好像可以胜任任何场景了。但是还有一些瑕疵,比如node中使用babel,开发插件的时候使用babel。

@babel/node

最开始说到巴别塔的人被上帝拆散打落到世界各地,其实不只是各个浏览器,还有一个地方生存着一些

顾名思义,babel-node是用来在node端做babel处理的东西,虽然node10已经可以很好的支持大多数es6的语法,但是如果大家的电脑的node版本层次不起,有的人用node6.x,不支持promise,这就需要用babel转化代码了。

另外es6的语法不一定在node端都支持,比如在node里这么写代码肯定是不可以的,node支持commonJs但不支持es6module。

// a.js
const a = 1
export default a

// index.js
import a from './a.js'
console.log(a)

使用babel-node可以很轻易的解决这个问题

安装@babel/core和@babel/node

cnpm install --save-dev @babel/core @babel/node

然后用babel-node执行node代码,就可以很顺利的执行成功了

⚠️ 这里执行的时候不是@babel/node index.js。就像装了@babel/cli之后执行编译依然是babel index.js这样。

 "scripts": {
"dev": "babel-node index.js"
},

@babel/node意味着前后端可以写一摸一样的代码

@babel/runtime

babel在转化的时候会引入一些工具函数,叫做helper,比如下边的这段代码用到了es6的可变属性。

const key = 'babel'
const obj = {
[key]: 'foo'
}

默认转化出来的结果是这个样子的。

'use strict'

function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
})
} else {
obj[key] = value
}
return obj
}
var key = 'babel'
var obj = _defineProperty({}, key, 'foo')

转化出来的内容比较多,因为有一个新的工具函数_defineProperty,这个就是helper。

由于每次如果这样子的话每次都定义一个新的函数就会导致文件变得很大,比如不同的项目都用了babel进行编译,但是项目之间会有一定的依赖,那么这两个项目的helper其实就都会出现在各自的文件里,导致没用的重复的代码。所以开发插件的时候如果引入一个polyfill或者像上边那样直接编译,代码量会变得很大。

而runtime就是解决这个问题的,runtime可以让这些工具函数不被重新定义,而是从helper文件里去取。

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

var key = 'babel';
var obj = (0, _defineProperty2.default)({}, key, 'foo');

总结

以上就是babel的一些基础知识和简单的用法,总结一下如下

  1. babel主要做的事情就是transform和polyfill,一个是针对语法,一个是针对API。还有一个是转化针对自定义的写法,比如jsx。
  2. @babel/core里的方法才是babel的核心,可以用@babel/core写个node写个脚本编译,也可以用babel-cli执行命令编译。
  3. babel用来transform的东西叫做plugin,即便是preset也只是plugin的合集,env是所有纳入标准的特性,而这些需要写在.babelrc中。如果要用提案状态的特性需要了解一哈proposal的一些特性,当然前提是你需要知道你用到的特性在ECMA中目前处于一个什么状态。
  4. @babel/polyfill没什么可怕的,就是一个简单的用来垫脚的文件,只要引入了就能使用WeakMap,Set,Array.from这样子的api和数据结构了。
  5. @babel/node用来在node环境中使用,可以统一前后端的语法。

嗨,请先登录

加载中...
(๑>ω<๑) 又是元气满满的一天哟