客户端视角下的babel(理论篇)

前言

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

parse阶段会对源码进行词法分析语法分析生成AST。

其中词法分析本质上是将源码分割成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 方法的入参是用户配置的所有插件集合,该方法将插件调用分为了四个阶段。

  1. 遍历插件集合,执行插件的 pre 方法。

  2. 遍历插件集合,合并插件的 visitor 方法。

  3. 执行第二步合成的 visitor 方法。

  4. 遍历插件集合,执行插件的 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的应用场景非常广泛。下面是一些常见场景:

  1. ECMAScript标准版本过新,不同环境或浏览器支持的标准版本不同,使用Babel可以将较新的JavaScript代码转换为向下兼容的版本,以确保代码能够在多个环境或浏览器中运行。

例如,在使用ES6的const和let语句的情况下,可以通过Babel将其转换为使用var语句的ES5语法:

// ES6代码
const x = 10;
let y = 20;

// Babel转换后的ES5代码
var x = 10;
var y = 20;
  1. 在使用最新的JavaScript语言特性(如箭头函数、模板字面量和展开运算符)的情况下,使用Babel可以在向下兼容的环境中使用这些特性。
// Babel 接收到的输入是: ES2015 箭头函数
[1, 2, 3].map(n => n + 1);

// Babel 输出: ES5 语法实现的同等功能
[1, 2, 3].map(function(n) {
  return n + 1;
});
  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的依赖读取、解决导入导出的变量名和当前文件变量名冲突的问题等,敬请期待。

参考资料

  1. https://www.babeljs.cn/docs/

  2. https://zhuanlan.zhihu.com/p/486548159

  3. https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-basics

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容