介绍
Babel是一个工具链,主要用于将ECMAScript 2015+版本代码向后兼容 Javascript 语法,以便可以运行到旧版本浏览器或其他环境中。
作为一种语言,JavaScript在不断发展,新的标准/提案和新的特性层出不穷。在得到广泛普及之前,Babel能够让你提前(甚至数年)使用他们。
Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。
- 解析
将代码解析成抽象语法树(AST),每个js引擎(比如Chrome浏览器中的V8引擎)都有自己的AST解析器,而Babel是通过Babylon实现的。在解析过程中有两个阶段:词法分析和语法分析,词法分析阶段把字符串形式的代码转换为令牌(tokens)流,令牌类似于AST中节点;而语法分析阶段则会把一个令牌流转换成 AST的形式,同时这个阶段会把令牌中的信息转换成AST的表述结构。
- 转换
在这个阶段,Babel接受得到AST并通过babel-traverse对其进行深度优先遍历,在此过程中对节点进行添加、更新及移除操作。这部分也是Babel插件介入工作的部分。
- 生成 将经过转换的AST通过babel-generator再转换成js代码,过程就是深度优先遍历整个AST,然后构建可以表示转换后代码的字符串。
例如,Babel能够将新的ES2015箭头函数语法:
const square = n => n * n;
转译为:
const square = function square(n) {
return n * n;
}
babel各个模块介绍
1.babel-core
babel的核心模块,包括一些核心api例如:transform。
/*
* @param {string} code 要转译的代码字符串
* @param {object} options 可选,配置项
* @return {object}
*/
babel.transform(code: string, options?: Object)
//返回一个对象(主要包括三个部分):
{
generated code, //生成码
sources map, //源映射
AST //即abstract syntax tree,抽象语法树
}
更多AST知识点
一些使用babel插件的打包或构建工具都有使用到这个方法,下面是一些引入babel插件中的源码:
//gulp-babel
const babel = require('babel-core');
/*
some codes...
*/
module.exports = function (opts) {
opts = opts || {};
return through.obj(function (file, enc, cb) {
try {
const fileOpts = Object.assign({}, opts, {
filename: file.path,
filenameRelative: file.relative,
sourceMap: Boolean(file.sourceMap),
sourceFileName: file.relative,
sourceMapTarget: file.relative
});
const res = babel.transform(file.contents.toString(), fileOpts);
if (res !== null) {
//some codes
}
} catch (err) {
//some codes
}
}
}
//babel-loader
var babel = require("babel-core");
/*
some codes...
*/
var transpile = function transpile(source, options) {
//some code
try {
result = babel.transform(source, options);
} catch (error) {
//some codes
}
//some codes
}
//rollup-pugin-babel
import { buildExternalHelpers, transform } from 'babel-core';
/*
some codes...
*/
export default function babel ( options ) {
//some codes
return {
// some methods
transform ( code, id ) {
const transformed = transform( code, localOpts );
//some codes
return {
code: transformed.code,
map: transformed.map
};
}
}
}
2.babel-cli
Babel的CLI是一种在命令行下使用Babel编译文件的简单方法。主要用于文件的输入输出。
全局安装
npm install -g babel-cli
我们可以这样编译我们第一个文件:
babel test.js
//编译后的文件输出在终端
babel test.js -o test-out.js
//编译后的文件输出在test-out.js文件中
在项目内运行 Babel CLI
尽管可以把Babel CLI全局安装在你的机器上,但是按项目逐个安装在本地会更好。 有两个主要的原因。
- 1.在同一台机器上的不同项目或许会依赖不同版本的Babel并允许你有选择的更新。
- 2.意味着对工作环境没有隐式依赖,让项目有很好的移植性并易于安装
将Babel CLI安装到本地可以运行:
npm install --save-dev babel-cli
现在可以不直接在命令行运行Babel了,取而代之我们将命令写在package.json的script里。
只需将"scirpts"字段添加到你的package.json文件内。
{
"scripts":{
"build": "babel src -d lib",
...
},
...
}
现在可以在终端里运行:
npm run build
这将以与之前同样的方式运行Babel。
3.babel-node
babel-node是随babel-cli一起安装的,只要安装了babel-cli就会自带babel-node。 在命令行输入babel-node会启动一个REPL(Read-Eval-Print-Loop),这是一个支持ES6的js执行环境。
4.babel-register
babel-register字面意思能看出来,这是babel的一个注册器,它在底层改写了node的require方法,引入babel-register之后所有require并以.es6, .es, .jsx 和 .js为后缀的模块都会经过babel的转译。
//test.js
const name = 'test';
module.exports = () => {
const json = {name};
return json;
};
//main.js
require('babel-register');
var test = require('./test.js'); //test.js中的es6语法将被转译成es5
console.log(test.toString()); //通过toString方法,看看控制台输出的函数是否被转译
/*
function () {
var json = { name: name };
return json;
}
*/
5.babel-polyfill
babel-polyfill在代码中的作用主要是用已经存在的语法和api实现一些浏览器还没有实现的api,对浏览器的一些缺陷做一些修补。例如Array新增了includes方法,我想使用,但是低版本的浏览器上没有,引入babel-polyfill则帮我们添加了这些方法。
项目使用
1. .babelrc
babel所有的操作基本都会来读取这个配置文件,除了一些在回调函数中设置options参数的,如果没有这个配置文件,会从package.json文件的babel属性中读取配置。
2.plugins
babel中的插件,通过配置不同的插件才能告诉babel,我们的代码中有哪些是需要转译的。
3.presets
预设就是一系列插件的集合,就好像修图一样,把上次修图的一些参数保存为一个预设,下次就能直接使用。
// cnpm install -D babel-preset -env
{
"presets": [
["env", {
"targets": { //指定要转译到哪个环境
//浏览器环境
"browsers": ["last 2 versions", "safari >= 7"],
//node环境
"node": "6.10", //"current" 使用当前版本的node
},
//是否将ES6的模块化语法转译成其他类型
//参数:"amd" | "umd" | "systemjs" | "commonjs" | false,默认为'commonjs'
"modules": 'commonjs',
//是否进行debug操作,会在控制台打印出所有插件中的log,已经插件的版本
"debug": false,
//强制开启某些模块,默认为[]
"include": ["transform-es2015-arrow-functions"],
//禁用某些模块,默认为[]
"exclude": ["transform-es2015-for-of"],
//是否自动引入polyfill,开启此选项必须保证已经安装了babel-polyfill
//参数:Boolean,默认为false.
"useBuiltIns": false
}]
]
}
关于最后一个参数useBuiltIns,有两点必须要注意:
- 1.如果useBuiltIns为true,项目中必须引入babel-polyfill。
- 2.babel-polyfill只能被引入一次,如果多次引入会造成全局作用域的冲突。
其他
babel-standalone
通过scirpt标签type属性传入"text/babel",在浏览器中进行高级语法向低级语法的转换。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
const PI = "3.14";
ReactDOM.render(<h1>hi</h1>,document.getElementById("app"))
</script>
</body>
</html>