Babel运行机制

babel的作用

Babel是前端开发中最常见的第三方工具,它的功能有三个:
一是转义ES2015+语法的代码,保证比较新的语法也可以在旧版本的浏览器中运行;
二是可以通过polyfill方式在目标环境中添加缺失的特性;
三是源码转换。

babel的核心组成包

  1. @babel/core
  2. @babel/cli
    — babel/core包是Babel的核心包,@babel/cli包和@babel/polyfill包都需要在核心包上才能正常工作。@babel/cli包是Babel提供的命令行工具,主要提供Babel命令
  3. @babel/preset-env
    — 其次,安装@babel/preset-env和@babel/polyfill。@babel/preset-env会根据配置的目标环境生成插件列表并进行编译。目标环境可以在package.json文件的browserslist中进行配置。Babel默认只转换新的JavaScript语法,但是不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol和Promise等全局对象,以及一些定义在Object对象上的方法等(比如Object.assign)都不会被转换。如果还想正常执行,就需要使用@babel/polyfill了
  4. @babel/runtime插件
  5. @babel/plugin-transform-runtime插件
    ---- 当全局导入polyfill时,会造成全局污染,这显然不是一个很好的解决方案。因为当浏览器支持特性时,polyfill就不是必需的。
    @babel/plugin-transform-runtime可对Babel编译过程中产生的helper方法进行重新利用(聚合),以达到减少打包体积的目的。此外它还有一个作用,即避免全局补丁污染,对打包过的bunler提供“沙箱”式的补丁,生产环境中加入@babel/runtime
  6. @babel/types: 它的作用是创建、修改、初除、查找抽象语法树的节点。抽象语法树的节点可分为多种类型,比如,ExpressionStatement(表达式)、ClassDeclaration(类声明)和VariableDeclaration(变量声明)等
  7. @babel/polyfill: polyfill的中文名称叫作垫片,在计算机中指的是对未能实现的客户端进行的“兜底”操作。对前端开发而言,如果部分JavaScript特性在个别浏览器(特别是IE)上不支持,但是又需要兼容这些浏览器,那么就需要提供一种机制使其能够正常运行。例如,ES6的object.assign方法,即使在IE11中运行也会报错
  8. @babel/traverse: 这个插件可遍历抽象语法树的所有节点,并使用指定的Visitor处理相关节点

babel的配置文件

通常上在根项目中babel基本上有四个配置文件:

  1. babel.config.js ,以command.js格式配置;
  2. .babelrc, 配置文件内容为JSON数据结构;
  3. 第三种是在package.json文件中配置babel字段;
  4. babelrc.js ,该配置与.babelrc相同,但是需要使用JavaScript实现;

babel配置文件的优先级

在这4种配置文件中,最常用的是babel.config.js配置和.babelrc配置,Babel官方推荐babel.config.js配置,因为该配置是项目级别的配置,会影响整个项目中的代码,包括node_modules。有了babel.config.js配置之后,就不会去执行.babelrc了。.babelrc配置只影响本项目中的代码

Babel的工作过程

Babel与大多数编译器一样,它的工作过程可分成三部分:
◎ 解析(parse):将源代码转换成抽象语法树(Abstract Syntax Tree,AST),树上的每个节点都表示源代码中的一种结构。
◎ 转换(transform):对抽象语法树做一些特殊处理,使其符合编译器的期望,在Babel中主要使用转换揑件实现。
◎ 代码生成(generate):将转换过的抽象语法树生成新的代码。

解析过程

下面通过一个简单的例子说明Babel的工作过程

let { parse } = require('@babel/parser')
let { default: generate } = require('@babel/generator');
let code = " compare = (a, b) => a + b";
let ast = parse(code, { sourceType: "module"});

解析过程可分为两部分:词法分析和语法分析。
词法分析:编译器在读取代码之后,会按照预定的规则把分词后的内容合并成一个个标识(tokens)。同时,移除空白符、注释等。最后,整个代码被分割成一个tokens列表。例如,把compare函数分割后形成的tokens列表如下所示

[
	{ "type": "Keyword", "value": "const" },
	{"type":"Identifier", "value": "compare" },
	{"type": "Punctuator", "value": "=" },
	{ "type": "Punctuator", "value": "(" },
	{ "type": "Identifier", "value": "a" },
	{ "type": "Punctuator", "value": "," },
	{"type":"Identifier", "value": "b" },
	{ "type": "Punctuator","value":"}"},
	{ "type": "Punctuator", "value": "=>" },
	{ "type":"Identifier","value": "a" },
	{ "type": "Punctuator","value":">"},
	{"type": "Identifier","value":"b" }
]

下一步则是语法分析:也叫解析器。它会将词法分析出来的数组转换成树状的表达式,同时验证语法。如果语法有错,就抛出语法错误

{
    "type": "Program",
    "start": 0,
    "end": 25,
    "body": [
        "type": "VariableDeclaration",
        "start": 0,
        "end": 24,
        "declarations": [
            "type": "VariableDeclarator",
            "start": 4,
            "end": 24,
            "id": {
                "type": "Identifier",
                "start": 4,
                "end": 11,
                "name": "compare"
            },
            "init": {
                "type": "ArrowFunctionExpression",
                "start": 13,
                "end": 24,
                "id": null,
                "expression": true,
                "generator": false,
                "async": false,
                "params": [{
                        "type": "Identifier",
                        "start": 14,
                        "end": 15,
                        "name": "a"
                    },
                    {
                        "type": "Identifier",
                        "start": 16,
                        "end": 17,
                        "name": "b"
                    }
                ],
                "body": {
                    "type": "BinaryExpression",
                    "start": 21,
                    "end": 24,
                    "left": {
                        "type": "Identifier",
                        "start": 21,
                        "end": 22,
                        "name": "a"
                    },
                    "operator": ">",
                    "right": {
                        "type": "Identifier",
                        "start": 23,
                        "end": 24,
                        "name": "b"
                    }
                }
            }
        }
    ],
    "kind": "let"
}
},
"sourceType": "module"
}

这里,我们需要解释一下抽象语法树中的兲键字段,根节点"type":"VariableDeclaration"表示变量声明,“declarations”:[]表示具体的声明,"kind"表示声明的变量类型。
declarations内部声明了一个变量,并且知道了它的内部属性(id、init、start、end),然后以此访问每个属性及它们的子节点。id是Identifier的简写,name属性表示变量名。

{
	type: 'Identifier',
	name: 'add'
}

以上结构表示一个标识符。
接着看init部分,init由以下几个内部属性组成:
◎ type是ArrowFunctionExpression,表示这是一个箭头函数。
◎ params是这个箭头函数的入参,其中每一个参数都是一个Identifier类型的节点。
◎ body是这个箭头函数的主体,type是BlockStatement,表示这是一个块级声明(BlockStatement)。
◎ 内层的body的type为一个BinaryExpression二项式:lef、operator、right分别表示二项式的左边变量、运算符及右边变量。
下面进行语法转换,Babel的语法转换是通插件件完成的。如果没有插件,则抽象语法树经过生成器生成的代码和源代码一模一样。Babel默认提供了许多插件,语法转换需要用到@babel/types和@babel/traverse插件,@babel/generator插件进行代码合成,生成需要的代码,转换后的代码就可以交付给浏览器执行了。以上过程的核心在于代码转换,转换过程的核心在于插件,Babel的插件配置非常重要。

//babel.config.js
module.exports = function (api) {
  const presets = [];
  const plugins = ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-runtime"];
  return {presets, plugins};
}

如果配置了多个插件,那么执行顺序是按照从前到后依次执行的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值