Babel指南

1,166 阅读6分钟

介绍

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>