Babel入门教程

大家都知道js十分依赖浏览器(或Node环境),不同浏览器对js的支持不尽相同。现如今ECMAScrip标准的更新已经到了一年一次的节奏,
Babel 就是为了解决这个问题 ,它可以将使用新标准的JavaScript代码转换为浏览器支持可以运行的JavsScript(ES5)代码。

1
2
3
4
5
6
7
8
//例如babel将这段代码
[1, 2, 3].map(n => n ** 2);
//转换为:
"use strict";

[1, 2, 3].map(function (n) {
return n + 1;
});

在webpack中配置Babel

假设在react项目配置webpack:

一、安装npm包:

1
npm install --save-dev babel-loader babel-core babel-plugin-transform-runtime babel-preset-es2015 babel-preset-react webpack-preset-babel-stage-0

二、在webpack配置文件中添加规则:

1
2
3
4
5
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}

三、 在项目根目录创建名为.babelrc的文件,这是Babel的配置文件

在配置文件中安装插件(plugins)或预设(presets,也就是一组插件)来指示 Babel 去做什么事情。在下面配置中:

babel-preset-es2015 打包了 es6 的特性;
babel-preset-stage-0 打包处于 strawman 阶段的语法;
babel-preset-react 打包react全家桶语法;

Babel 几乎可以编译所有时新的 JavaScript 语法,但对于 APIs 来说却并非如此。例如: Promise、Set、Map 等新增对象,Object.assign、Object.entries等静态方法,babel-plugin-transform-runtime为了达成使用这些新API。

1
2
3
4
5
//.babelrc内容
{
"presets": ["es2015", "stage-0", "react"],
"plugins": ["transform-runtime"]
}

Babel模块

Babel6.0后将功能划分成了不同的模块,在 Babel packages 仓库看到babel现在有哪些模块:

babel-core

参考 babel-core doc

babel-core是作为babel的核心存在,babel的核心api都在这个模块里面。babel-core把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// npm install babel-core
// var babel = require("babel-core");

//用法:

//babel.transform(code: string, options?: Object, callback: Function)
//字符串形式的 JavaScript 代码可以直接使用 babel.transform 来编译
babel.transform("code();", options, function(err, result) {
result.code;
result.map;
result.ast;
});

//注意 :可以在options中指定 preset 和 plugin
// 写法为: babel.transform(
// "code();",
// {presets: ["react"],},
// function(err, result){}

//babel.transformFile(filename: string, options?: Object, callback: Function)
//编译文件
babel.transformFile("filename.js", options, function(err, result) {
result; // => { code, map, ast }
});

//babel.transformFromAst(ast: Object, code?: string, options?: Object, callback: Function): FileNode | null
//将ast进行转译
const { code, map, ast } = babel.transformFromAst(ast, code, options);

其中 options 可以配置多项内容,包括Plugin和Preset配置,SourceMap配置等

babel-cli

参考 babel-cli doc

Babel 的 CLI 是一种在命令行下使用 Babel 编译文件的简单方法。

1
2
3
4
//基本使用 (输出到控制台)
npx babel script.js
//指定输出文件
npx babel script.js --out-file script-compiled.js

假设script.js长如下这样,直接在命令行执行 babel script.js,发现输出的代码好像没有转译……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//script.js
[1, 2, 3].map(n => n ** 2);

var [a,,b] = [1,2,3];

var name = "Guy Fieri";
var place = "Flavortown";
`Hello ${name}, ready for ${place}?`;

let yourTurn = "Type some code in here!";

var obj = {
shorthand,
method() {
return "😀";
}
};

function addAll() {
return Array.from(arguments).reduce((a, b) => a + b);
}

这和前面一样需要 配置 presets 和 plugins 信息。

这时候有两种方式一种是和上文一样使用 .babelrc 配置文件

1
2
3
4
5
6
7
8
//1. 转换 ES2015+ 的 env preset
npm install babel-preset-env --save-dev

//2. 创建一个 .babelrc 文件并启用一些插件
//.babelrc 文件信息:
{
"presets": ["env"]
}

这时候可以使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
npx babel script.js

//输出 可以看到箭头函数等一些新语法已经被转义了。
[1, 2, 3].map(function (n) {
return Math.pow(n, 2);
});

var _ref = [1, 2, 3],
a = _ref[0],
b = _ref[2];


var name = "Guy Fieri";
var place = "Flavortown";
"Hello " + name + ", ready for " + place + "?";

var yourTurn = "Type some code in here!";

var obj = {
shorthand: shorthand,
method: function method() {
return "😀";
}
};

function addAll() {
return Array.from(arguments).reduce(function (a, b) {
return a + b;
});
}

另一种方式是直接在命令中指定presets

1
2
3
4
5
6
//1. 转换 ES2015+ 的 env preset
npm install babel-preset-env --save-dev

//2.指定present
//输出也和上文一样
npx babel script.js --presets=env

babel-runtime / babel-plugin-transform-runtime

Babel 几乎可以编译所有时新的 JavaScript 语法,但对于 APIs 来说却并非如此。例如: Promise、Set、Map 等新增对象,Object.assign、Object.entries等静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
//下列含有箭头函数的需要编译的代码:
function addAll() {
return Array.from(arguments).reduce((a, b) => a + b);
}

//在上文中转义为:
//然而这段代码在浏览器中可能抛出 Uncaught TypeError: Array.from is not a function
//它依然无法随处可用因为不是所有的 JavaScript 环境都支持 Array.from
function addAll() {
return Array.from(arguments).reduce(function (a, b) {
return a + b;
});
}

在实现新的API的基础上有两种方式: babel-polyfillbabel-runtime + babel-plugin-transform-runtime

这两者虽然都是模拟 es6 环境新增API,但实现方法完全不同。

  • babel-polyfill 的做法是将全局对象通通污染一遍,比如想在 node 0.10 上用 Promise,调用 babel-polyfill 就会往 global 对象挂上 Promise 对象。

  • babel-runtime 的做法是自己手动引入 helper 函数, const Promise = require(‘babel-runtime/core-js/promise’) 就可以引入 Promise。为了解决手动引入Helper函数的麻烦,babel 又开发了 babel-plugin-transform-runtime,这个模块会将我们的代码重写,如将 Promise 重写成 _Promise(只是打比方),然后引入_Promise helper 函数。这样就避免了重复打包代码和手动引入模块的痛苦。

@babel/standalone 在浏览器环境/web工程中运行babel

由于 Babel 本身的设计是基于 node.js 环境下运行使用的, 上面的例子都是在 Node.js环境中跑的,但是你头铁 一定要在浏览器环境中跑babel怎么办?

答案就是使用 @babel/standalone

参考 https://github.com/babel/babel/issues/5268 https://babeljs.io/docs/en/next/babel-standalone.html

1
2
3
4
5
6
7
8
9
10
//npm i @babel/standalone @babel/core @babel/preset-react @babel/preset-env @babel/plugin-transform-runtime
//为什么写成这种require样式呢……@babel/standalone集成了很多插件 但是好像兼容性不好……
const babelCore = require('@babel/standalone');
let {code} = babelCore.transform(
'({item}) => <Text>{item.key}</Text>',
{
'presets': [require('@babel/preset-react'),require('@babel/preset-env')],
'plugins': [require('@babel/plugin-transform-runtime')]
});
console.log('code:',code);

或者

1
2
3
4
5
6
7
8
//npm i @babel/standalone 
const babelCore = require('@babel/standalone');
let {code} = babelCore.transform(
'({item}) => <Text>{item.key}</Text>',
{
'presets': ['react'],
});
console.log('code:',code);