前言
TN项目为了追求极致的渲染速度,剥离了JS引擎(减少序列化/反序列化,通信的消耗);同时为了保留前端开发的体验和效率,继续采用JSX开发对应的组件业务。换句话说,TN Native需要一定程度上替代JS引擎的角色,可以解释执行JSX打包后的产物。所以,这个产物是什么?产物和我们这期要讲的babel有什么联系吗?
在阐述上述问题之前,我们得先了解下JS引擎是处理JSX的。JS引擎的工作流程大致可以分为三个阶段:解析阶段、编译阶段和执行阶段。在解析阶段,JS引擎会将代码解析为一个抽象语法树(AST);在编译阶段,JavaScript 引擎会将 AST 转换成字节码或者机器码,以获取更好的性能;在执行阶段,JavaScript 引擎会执行生成的字节码或者机器码,并逐条解释执行代码。
了解完JS引擎的执行流程之后,哪个阶段的产物是可以进行二次处理加工的呢?没错,就是我们熟悉的AST。AST包含了所有节点的脉络信息,我们可以根据实际需要生产出对应的数据协议(符合TN引擎可解释执行的产物)
那么AST到底是什么呢?拿到AST之后我们如何使用Babel去数据加工生成我们需要的数据呢?带着疑问我们往下看
AST
AST概述
大家平时总是听到 AST 这个名词,那么到底什么是 AST?
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有三个分支的节点来表示。
和抽象语法树相对的是具体语法树(通常称作分析树)。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树,然后从分析树生成AST。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。
AST结构
ESTree 规范定义了 AST 节点类型及其属性,并提供了描述这些节点如何相互关联的规则。例如,ESTree 规范定义了标识符节点类型和它们可以用于哪些节点上,如变量声明和函数参数等。
ESTree(ECMAScript AST)规范是一种定义 JavaScript AST 的标准化格式,它旨在促进 JavaScript 工具和库之间的互操作性,并提供一种通用的 AST 表示形式。ESTree 已被广泛采用,包括在 Babel、Esprima、Acorn 和其他多个 JavaScript 工具中。
节点类型
类型 | 说明 |
---|---|
File | 文件 (顶层节点包含 Program) |
Program | 整个程序节点 (包含 body 属性代表程序体) |
Comments | 代码注释 |
Statement | 语句 (可独立执行的语句) |
Literal | 字面量 (基本数据类型、复杂数据类型等值类型) |
Identifier | 标识符 (变量名、属性名、函数名、参数名等) |
Declaration | 标识符 (变量名、属性名、函数名、参数名等) |
Specifier | 关键字 (ImportSpecifier、ImportDefaultSpecifier、ImportNamespaceSpecifier、ExportSpecifier) |
Expression | 表达式 |
公共属性
类型 | 说明 |
---|---|
type | 记录该节点代码字符串起始下标 |
start | 记录该节点代码字符串起始下标 |
end | 记录该节点代码字符串结束下标 |
loc | 内含 line、column 属性,分别记录开始结束的行列号 |
leadingComments | 开始的注释 |
innerComments | 中间的注释 |
trailingComments | 结尾的注释 |
为了更加直观认知AST的结构,我们将通过一段示例代码展开
function hello() {
console.log('hello world')
}
生成的AST如下所示(AST EXPLORER是常用的可视化结构工具)
JSON结构如下所示(为了简化的目的省略了部分属性):
{
"type": "Program",
...
"sourceType": "module",
"body": [
{
"type": "FunctionDeclaration",
...
"id": {
"type": "Identifier",
...
"name": "hello"
},
"generator": false,
"async": false,
"expression": false,
"params": [],
"body": {
"type": "BlockStatement",
...
"body": [
{
"type": "ExpressionStatement",
...
"expression": {
"type": "CallExpression",
...
"callee": {
"type": "MemberExpression",
...
"object": {
"type": "Identifier",
...
"name": "console"
},
"computed": false,
"property": {
"type": "Identifier",
...
"name": "log"
},
"optional": false
},
"arguments": [
{
"type": "Literal",
...
"value": "hello world"
}
],
"optional": false
}
}
]
}
}
]
}
你会留意到 AST 的每一层都拥有相同的结构
{
type: "FunctionDeclaration",
id: {...},
generator: ...,
async: ...,
expression: ...,
params: [...],
body: {...}
}
{
type: "Identifier",
name: ...
}
{
type: "BlockStatement",
body:[...]
}
{
type:"ExpressionStatement",
expression:{...}
}
{
type:"CallExpression",
callee:{...},
arguments:[...],
optional:...
}
{
type:"MemberExpression",
object:{...},
compuuted:...,
property:{...},
optional:...
}
这样的每一层结构也被叫做 节点(Node)。 一个 AST 可以由单一的节点或是成百上千个节点构成。 它们组合在一起可以描述用于静态分析的程序语法。
每一个节点都有如下所示的接口(Interface):
interface Node {
type: string;
}
其中type 字段表示节点的类型(如: "FunctionDeclaration","Identifier")。 每一种类型的节点定义了一些附加属性用来进一步描述该节点类型。
此外Babel还为每个节点额外配置了一些属性(如start,end,loc,range),用于描述该节点在原始代码中的位置。
{
type:...,
start: 0,
end: 49,
loc: {
start: {
line: 1,
column: 0
},
end: {
line: 3,
column: 1
}
},
range: [
0,
49
],
...
}
Babel
Babel概述
Babel是什么呢?Babel是一个JavaScript编译器
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的功能(通过引入第三方 polyfill 模块,例如 core-js)
- 源码转换(codemods)
Babel工作流程
babel本质是source to source的转换,总共分为三步:parse,transform,generate
parse:源码转换成AST
transform: 对AST进行遍历,在此过程中对节点进行添加、更新及移除等操作
generate:把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,并生成Source Map(源代码映射)
流程
Babel配置
根据使用的场景不同,babel提供了两种通用的配置方式:babel.config.json,.babellrc.json。
其中.babelrc.json是一个 JSON 格式的文件,用于配置 Babel 的转换规则和插件。它适用于基于文件系统的项目。
而babel.config.json 是 Babel 7.0 以后的新特性,适用于所有类型的项目(包括库、工具、命令行界面等)。它是一个 JSON 格式的文件,用于配置 Babel 的转换规则、插件以及预设。它的作用是在整个项目中统一配置 Babel,保证不同模块中 Babel 的配置一致性。与 .babelrc.json 不同的是,babel.config.json 是基于项目的配置,而非基于文件的,因此它的配置项不会受到文件层次结构等因素的影响。
babel.config.json(官方推荐)
在项目的根目录(package.json 文件所在的目录)下创建一个名为 babel.config.json 的文件,并输入如下内容。
{ "presets": [...], "plugins": [...]}
.babelrc.json
在你的项目中创建名为 .babelrc.json 的文件,并输入以下内容。
{ "presets": [...], "plugins": [...]}
Parse
其中词法分析本质上是将源码分割成tokens,举个例子
var a = 42;
最终会分割成var,a,=,42,;5个token。然后将tokens作为语法分析的输入源进行递归组装,最终生成AST。
Transform
transform会对parse阶段生成的AST进行处理。那么它是如何运作的呢?transform会对AST进行遍历,遍历的过程中遇到不同的节点会调用注册的相应的visitor,visitor可以根据实际诉求对AST节点进行相应的增加、删除、更新等操作,最终生成新的一份抽象语法树。
PS:这是Babe最复杂的过程,同时也是我们在实际开发过程中着手的最核心部分,后面章节会详细讲述
Generate
generate阶段会将最终转换完成的AST生成目标代码,同时生成对应的SourceMap(SourceMap包含了转换前后代码的位置信息、行号、列号等信息,可以为JavaScript开发人员提供更好的调试体验,并有助于减少调试应用程序时的错误和问题。)
Babel工具包
看完上述工作流程之后,想必你对Babel的运行机制大致有了一些了解。那么Babel提供了哪些工具包呢?
工具包 | 说明 |
---|---|
@babel/core | Babel核心库(代码转换期间使用的插件,需将其集成到babel/core) |
@babel/parser | 解析器,将代码解析为 AST |
@babel/traverse | 解析器,将代码解析为 AST |
@babel/generator | 生成器,将AST生成目标代码 |
@babel/types | 包含手动构建AST节点和检查AST节点类型的方法 |
@babel/template | 可将字符串代码片段转换为 AST 节点 |
@babel/code-frame | 生成带有错误代码位置信息的代码片段 |
@babel/runtime | 生成带有错误代码位置信息的代码片段 |
其中@babel/core、@babel/parser、@babel/traverse、@babel/generator、@babel/types、@babel/template是我们重点关注并经常使用的,涉及到插件的集成、AST遍历、手动构建节点及类型检查等
Babel插件
在Babel中,插件是在解析、转换和生成过程中使用的函数。Babel提供了一个插件接口,让开发人员可以为语法转换添加自定义规则,同时也有许多内置的插件可供使用。
插件大致可以分为两种:语法插件,转换插件。如果你想在代码解析期间使用插件(语法插件),可以将其集成到babel/parse模块中。如果你想在代码转换期间使用插件(转换插件),则可以将其集成到babel/core模块中。
那么插件该如何创建和集成呢?关于插件的集成,认真阅读的你应该已经知道,插件的集成是通过配置文件启用的。关于插件如何创建,请往下继续阅读。
另外如果有多个插件,那么插件的执行顺序又如何控制呢?
插件顺序
如果两个转换插件都将处理“程序(Program)”的某个代码片段,则将根据转换插件或 preset 的排列顺序依次执行。
插件在 Presets 前运行。
插件顺序从前往后排列。
Preset 顺序是颠倒的(从后往前)。
例如:
babel.config.json
{
"plugins": ["transform-decorators-legacy", "transform-class-properties"]
}
先执行 transform-decorators-legacy ,在执行 transform-class-properties。
重要的时,preset 的顺序是颠倒的。如下设置:
babel.config.json
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
将按如下顺序执行: 首先是 @babel/preset-react,然后是 @babel/preset-env。
插件的排列顺序很重要
插件参数
插件和 preset 都可以接受参数,参数由插件名和参数对象组成一个数组,可以在配置文件中设置。
如果不指定参数,下面这几种形式都是一样的:
{
"plugins": ["pluginA", ["pluginA"], ["pluginA", {}]]
}
要指定参数,请传递一个以参数名作为键(key)的对象。
{
"plugins": [
[
"transform-async-to-module-method",
{
"module": "bluebird",
"method": "coroutine"
}
]
]
}
preset 的设置参数的工作原理完全相同:
{
"presets": [
[
"env",
{
"loose": true,
"modules": false
}
]
]
}
插件创建
如果你想在代码解析期间使用插件(语法插件),可以将其集成到babel/parse模块中。如果你想在代码转换期间使用插件(转换插件),则可以将其集成到babel/core模块中。
绝大多数场景下我们其实都是在开发插件做AST节点的转换。那么如何创建一个插件呢?
首先我们引用Babel官方的一个例子来阐述一个插件如何创建(一个简单的用于反转名称顺序的插件)
export default function() {
return {
visitor: {
Identifier(path) {
const name = path.node.name;
// reverse the name: JavaScript -> tpircSavaJ
path.node.name = name
.split("")
.reverse()
.join("");
},
},
};
}
上述例子其实非常简单,先从一个接收了当前babel对象作为参数的 function 开始,创建并返回visitor对象,然后遍历AST节点。如果某个节点类型是Identifier,则将其名称进行反转。
值得一提的是,由于我们会经常这样使用,所以直接取出 babel.types 会更方便(@babel/core内部集成了@babel/types)
export default function({ types: t }) {
// plugin contents
}
上述节点插件转换后,我们可以清晰看到前后的变化:JavaScript -> tpircSavaJ
// 执行前
Node {
type: 'Identifier',
name: 'JavaScript',
...
}
// 插件执行后
Node {
type: 'Identifier',
name: 'tpircSavaJ',
...
}
PS:更加详细的内容请参考 babel-handbook 学习如何创建自己的插件。
通过例子,我们窥见了一个插件的大致全貌。那么visitor到底是什么?path又提供了哪些属性和方法呢?(我用不到的不用科普了...不好意思的确用得到,而且是基础概念)另外插件的visitor又是如何执行起来的呢?
基础概念
Visitor(访问者)
visitor 属性是插件的访问者,其中visitor中的每个函数接收2个参数:path 和 state
export default function({ types: t }) {
return {
visitor: {
Identifier(path, state) {
}
...
}
};
};
其中state表示当前的状态,它是一个对象。使用 state 参数可以让我们在 visitor 函数中获取更多关于当前状态的信息,方便我们进行 AST 转换和操作(操作节点时个人基本使用path)
PluginPass {
file: <ref *1> File { // 当前文件的 AST 信息
opts: { // 传给 Babel 的选项
sourceType: 'unambiguous',
ast: true,
plugins: [Array],
presets: [],
...
},
path: NodePath { // 路径
...
},
ast: Node { // 当前的 AST
type: 'File',
program: [Node],
...
},
scope: Scope { // 作用域
},
code: 'function add(a, b) {\n return a + b\n }', // 当前的code
...
}
}
那visitor是如何运作的呢?访问者是一个用于 AST 遍历的跨语言的模式。
当我们向下遍历这颗树的每一个分支时我们最终会走到尽头,于是我们需要往上遍历回去从而获取到下一个节点。 向下遍历这棵树我们进入每个节点,向上遍历回去时我们退出每个节点。
为了更加直观的了解其运作流程,我们还是举个例子来阐述。假设代码片段如下:
function add(a, b) {
return a + b
}
对应的树形结构:
- FunctionDeclaration
- Identifier (id)
- Identifier (params[0])
- Identifier (params[1])
- BlockStatement (body)
- ReturnStatement (body)
- BinaryExpression (argument)
- Identifier (left)
- Identifier (right)
AST遍历方式
-
进入 FunctionDeclaration
进入 Identifier (id)
走到尽头
退出 Identifier (id)
进入 Identifier (params[0])
走到尽头
退出 Identifier (params[0])
进入 Identifier (params[1])
走到尽头
退出 Identifier (params[1])
进入 BlockStatement (body)
-
进入 ReturnStatement (body)
进入 BinaryExpression (argument)
-
进入 Identifier (left)
- 走到尽头
退出 Identifier (left)
-
进入 Identifier (right)
- 走到尽头
退出 Identifier (right)
退出 BinaryExpression (argument)
退出 ReturnStatement (body)
退出 BlockStatement (body)
退出 FunctionDeclaration
Path
AST本身包含大量的节点,那这些节点是如何进行关联的呢?答案是path
path除了是连接两个节点的对象之外,它还是visitor定义的函数中的第一个参数,同时代表了AST 中的一个节点,控制着我们对节点的读取、修改、删除等操作。简单来讲的话,path提供了我们对 AST 进行操作的方法和属性。它包含了父节点、子节点、兄弟节点、当前节点本身等信息,比如可以通过 path.node 访问节点内容。
path的主要属性包括以下几个:
属性 | 说明 |
---|---|
node | 节点的AST属性 |
parent | 父节点 |
parentPath | 父节点路径 |
scope | 节点关联的当前作用域 |
hub | 节点关联的当前作用域 |
Babel的插件注册表
方法 | 说明 |
---|---|
replaceWith | 替换当前路径节点的值 |
getSibling | 获得兄弟path |
insertBefore | 在当前节点前插入一个新节点 |
insertAfter | 在当前节点后插入一个新节点 |
remove | 从AST树中删除此路径的节点 |
get | 获取当前路径的特定子路径 |
stop | 停止遍历 |
下面我们举一个例子方便我们理解上述path的属性和方法:
const visitor = {
Identifier(path) {
if (path.node.name === 'foo') {
path.node.name = 'bar';
}
},
CallExpression(path) {
if (t.isIdentifier(path.node.callee, { name: 'baz' })) {
path.replaceWith(t.identifier('XYZ'));
}
}
};
module.exports = function({ types: t }) {
return {
visitor
};
};
该代码中,我们使用path提供的API在代码中查找‘foo’并替换为‘bar’,并将‘baz’替换为‘XYZ’。visitor对象用于描述代码中哪些节点应该被追踪和转换。
Scope
作用域(scope)是指变量和函数可以在代码中被访问的范围。
在 JavaScript 中,有两种作用域类型:全局作用域和局部作用域。
全局作用域:指的是代码中所有函数、块和语句都可以访问的变量和函数。全局作用域在整个程序中都是有效的,也就是说,在程序的任何地方都可以访问和修改全局变量。
局部作用域:指的是变量和函数仅在特定的代码块或函数内部可以被访问。当在函数内部声明变量时,它们只在函数内部可用,并且在函数外部是无法访问的。
下面我们通过一些实际的例子来帮助我们更好的理解
var global = "global scope";
function scopeOne() {
var one = "scope one";
function scopeTwo() {
var two = "scope two";
}
}
每当你创建了一个引用,不管是变量(variable)、函数(function)、类型(class)、参数(params)等,它都属于当前作用域。
更深的内部作用域代码可以使用外层作用域中的引用。
function scopeOne() {
var one = "scope one";
function scopeTwo() {
one = "scope two";
}
}
内层作用域也可以创建和外层作用域同名的引用。
function scopeOne() {
var one = "scope one";
function scopeTwo() {
var one = "new scope";
}
}
还有一个重要的点,即作用域的嵌套。简单来说,每个函数都拥有它自己的作用域,而作用域链会向上搜索变量的定义,直到找到为止。当函数被调用时,JavaScript 引擎会沿着作用域链寻找变量的定义,直到找到定义为止。因此,内部函数可以访问外部函数的变量和函数,而外部函数无法访问内部函数的变量和函数。
插件执行
了解了插件的集成、参数配置、创建后,我们再来看下插件的执行顺序。
首先Babel 在完成 AST 编译后,会调用插件对 AST 做修改,调用的方法就是 transformFile(babel-core/lib/transformation/index.js)。transformFile 方法的入参是用户配置的所有插件集合,该方法将插件调用分为了四个阶段。
遍历插件集合,执行插件的 pre 方法。
遍历插件集合,合并插件的 visitor 方法。
执行第二步合成的 visitor 方法。
遍历插件集合,执行插件的 post 方法。
function* transformFile(file, pluginPasses) {
for (const pluginPairs of pluginPasses) {
...
// 执行插件的pre方法
for (const [plugin, pass] of passPairs) {
const fn = plugin.pre;
if (fn) {
const result = fn.call(pass, file);
...
}
}
const visitor = _traverse().default.visitors.merge(visitors, passes, file.opts.wrapPluginVisitorMethod);
// 执行插件visitor中定义的方法
(0, _traverse().default)(file.ast, visitor, file.scope);
// 执行插件的post方法
for (const [plugin, pass] of passPairs) {
const fn = plugin.post;
if (fn) {
const result = fn.call(pass, file);
...
}
}
}
}
从源码可以清晰看到,Babel 插件遵循这样的一个执行顺序,pre -> visitor -> post。
其中pre 和 post 的入参只有 state,这也决定了这两个方法是无法改变 AST 结构的。pre 和 post 方法的用法也比较单一,一般在 pre 中创建一些临时对象, post 中再将这些对象销毁掉。
export default function({ types: t }) {
return {
pre(state) {
...
},
visitor: {
...
},
post(state) {
...
}
};
}
Babel Types
transform阶段会执行插件中visitor对象定义的方法,对节点进行新增、删除、更新等操作,其中就涉及到节点检查、节点更新等操作。
而@babel/types模块正好提供了用于手动构建AST和检查AST节点类型的方法。
构建
@babel/types 提供了所有节点的构造方法,其中构建器的方法名称就是您想要的节点类型的名称
declare function arrayExpression(elements?: Array<null | Expression | SpreadElement>): ArrayExpression;
declare function assignmentExpression(operator: string, left: LVal, right: Expression): AssignmentExpression;
declare function binaryExpression(operator: "+" | "-" | "/" | "%" | "*" | "**" | "&" | "|" | ">>" | ">>>" | "<<" | "^" | "==" | "===" | "!=" | "!==" | "in" | "instanceof" | ">" | "<" | ">=" | "<=" | "|>", left: Expression | PrivateName, right: Expression): BinaryExpression;
declare function blockStatement(body: Array<Statement>, directives?: Array<Directive>): BlockStatement;
declare function breakStatement(label?: Identifier | null): BreakStatement;
declare function callExpression(callee: Expression | Super | V8IntrinsicIdentifier, _arguments: Array<Expression | SpreadElement | JSXNamespacedName | ArgumentPlaceholder>): CallExpression;
declare function catchClause(param: Identifier | ArrayPattern | ObjectPattern | null | undefined, body: BlockStatement): CatchClause;
declare function conditionalExpression(test: Expression, consequent: Expression, alternate: Expression): ConditionalExpression;
declare function continueStatement(label?: Identifier | null): ContinueStatement;
declare function doWhileStatement(test: Expression, body: Statement): DoWhileStatement;
declare function expressionStatement(expression: Expression): ExpressionStatement;
declare function forInStatement(left: VariableDeclaration | LVal, right: Expression, body: Statement): ForInStatement;
declare function forStatement(init: VariableDeclaration | Expression | null | undefined, test: Expression | null | undefined, update: Expression | null | undefined, body: Statement): ForStatement;
declare function functionDeclaration(id: Identifier | null | undefined, params: Array<Identifier | Pattern | RestElement>, body: BlockStatement, generator?: boolean, async?: boolean): FunctionDeclaration;
declare function functionExpression(id: Identifier | null | undefined, params: Array<Identifier | Pattern | RestElement>, body: BlockStatement, generator?: boolean, async?: boolean): FunctionExpression;
declare function identifier(name: string): Identifier;
...
类型检查
@babel/types 提供了所有节点类型的判断方法,如下所示:
declare function isArrayExpression(node: Node | null | undefined, opts?: Opts<ArrayExpression> | null): node is ArrayExpression;
declare function isAssignmentExpression(node: Node | null | undefined, opts?: Opts<AssignmentExpression> | null): node is AssignmentExpression;
declare function isBinaryExpression(node: Node | null | undefined, opts?: Opts<BinaryExpression> | null): node is BinaryExpression;
declare function isBlockStatement(node: Node | null | undefined, opts?: Opts<BlockStatement> | null): node is BlockStatement;
declare function isBreakStatement(node: Node | null | undefined, opts?: Opts<BreakStatement> | null): node is BreakStatement;
declare function isCallExpression(node: Node | null | undefined, opts?: Opts<CallExpression> | null): node is CallExpression;
declare function isCatchClause(node: Node | null | undefined, opts?: Opts<CatchClause> | null): node is CatchClause;
...
常见的判断写法有两种:
- 第一种是 isX
t.isIdentifier(node)
- 第二种是提供参数来确保节点包含特定的属性和值。
t.isIdentifier(node, { name: ... })
应用场景
Babel的应用场景非常广泛。下面是一些常见场景:
- ECMAScript标准版本过新,不同环境或浏览器支持的标准版本不同,使用Babel可以将较新的JavaScript代码转换为向下兼容的版本,以确保代码能够在多个环境或浏览器中运行。
例如,在使用ES6的const和let语句的情况下,可以通过Babel将其转换为使用var语句的ES5语法:
// ES6代码
const x = 10;
let y = 20;
// Babel转换后的ES5代码
var x = 10;
var y = 20;
- 在使用最新的JavaScript语言特性(如箭头函数、模板字面量和展开运算符)的情况下,使用Babel可以在向下兼容的环境中使用这些特性。
// Babel 接收到的输入是: ES2015 箭头函数
[1, 2, 3].map(n => n + 1);
// Babel 输出: ES5 语法实现的同等功能
[1, 2, 3].map(function(n) {
return n + 1;
});
- 在使用React等框架时,使用Babel可以将JSX语法转换为普通的JavaScript语法。
例如,在使用React时可以使用JSX语法:
// React组件的JSX代码
const Header = () => (
<div>
<h1>Hello, World!</h1>
</div>
);
通过Babel的转换,可以将上述代码转换为纯JavaScript代码:
// 转换后的JS代码
const Header = () => React.createElement("div", null, React.createElement("h1", null, "Hello, World!"));
总的来说,Babel的主要应用场景是帮助开发者应对不同环境和浏览器的兼容性,使得开发的JavaScript代码更具有通用性和可移植性。
预告
关于Babel的介绍篇到此就结束了,由于本身对前端体系了解粗浅,对Babel的理解也只是停留在使用阶段,文档中描述、理解有错误的请望指出,感谢!
下一篇我会详细的讲解自己在TN项目中有关Babel的一些具体实践,比如enum支持、空行符处理,以及如何解决多文件import的依赖读取、解决导入导出的变量名和当前文件变量名冲突的问题等,敬请期待。