<Babel> 前端语言的巴别塔

2023-07-17 00:43:12

在这里插入图片描述

Babel中文站点:https://www.babeljs.cn/
Babel外文站点:https://babeljs.io/

什么是Babel

Babel取自人类语言最早的传说,Tower of Babel。
上帝摧毁了巴别塔上说着共同语言的我们,又被Babel重新带了回来。
如果说巴别塔是人们对混乱到秩序的向往,那Babel在前端领域就带给了工程师放飞语法的所有畅想。

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

以上是官网对于Babel的解释,究其起源是由于前端的宿主容器无法实时的迎合各种语法进化而导致的兼容性问题,当我们无法改变如浏览器的宿主容器的时,Babel可以为我们提供以下服务:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的功能
  • 源码转换(codemods)

Babel提供的服务:

语法转换

一般代指高级语言特性的降级

Polyfill

也叫垫片、补丁(通过引入第三方 polyfill 模块,例如 core-js)

源码转换

JSX等

Babel工程化的概念:

  • 可插拔(Pluggable)
    这就要求Babel需要有一套灵活的插件机制,满足第三方开发的入驻和使用场景。
  • 可调式(Debuggable)
    在Babel编译过程中,需要提供一套Source Map,来帮助使用Babel的开发找到编译前后的映射关系,从而方便前端开发中的调试。
  • 基于协定(Compact)
    需要实现灵活的配置方式,比如你熟悉的 Babelloose 模式,Babel 提供 loose 选项,帮助开发者在“尽量还原规范”和“更小的编译产出体积”之间,找到平衡。

总结

Babel的核心目标:编译

在这里插入图片描述

自身实现基于编译原理,深入AST(抽象语法树)来生成目标代码。同时,Babel 需要工程化协作,需要和各种工具(如 Webpack)相互配合,因此 Babel 一定是庞大复杂的。

Babel Monorepo架构体系

@babel/core

@babel/core 是 Babel 实现转换的核心

它可以根据配置,进行源码的编译转换:

var babel = require("@babel/core");

babel.transform(code, options, function(err, result) {
  result; // => { code, map, ast }
});

@babel/cli

@babel/cli 是 Babel 提供的命令行,它可以在终端中通过命令行方式运行,编译文件或目录。
我们简单说一下它的实现原理:
@babel/cli 使用了 commander 库搭建基本的命令行开发。

以编译文件为例,其关键部分源码如下:

import * as util from "./util";
const results = await Promise.all(
  _filenames.map(async function (filename: string): Promise<Object> {
    let sourceFilename = filename;
    if (cliOptions.outFile) {
      sourceFilename = path.relative(
        path.dirname(cliOptions.outFile),
        sourceFilename,
      );
    }
    // 获取文件名
    sourceFilename = slash(sourceFilename);
    try {
      return await util.compile(filename, {
        ...babelOptions,
        sourceFileName: sourceFilename,
        // 获取 sourceMaps 配置项
        sourceMaps:
          babelOptions.sourceMaps === "inline"
            ? true
            : babelOptions.sourceMaps,
      });
    } catch (err) {
      if (!cliOptions.watch) {
        throw err;
      }
      console.error(err);
      return null;
    }
  }),
);

util.compile

在上述代码中,@babel/cli 使用了util.compile方法执行关键的编译操作,该方法定义在 babel-cli/src/babel/util.js

babel-cli/src/babel/util.js代码解析:

import * as babel from "@babel/core";
// 核心编译方法
export function compile(
  filename: string,
  opts: Object | Function,
): Promise<Object> {
  // 编译配置
  opts = {
    ...opts,
    caller: CALLER,
  };
  return new Promise((resolve, reject) => {
    // 调用 transformFile 方法执行编译过程
    babel.transformFile(filename, opts, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

由此可见,@babel/cli 负责获取配置内容,并最终依赖了 @babel/core 完成编译。

也可以在 @babel/cli 的 package.json 中找到线索:

"peerDependencies": {
    "@babel/core": "^7.0.0-0"
},

@babel/core 是 @babel/cli 的关键依赖,@babel/core 提供了基础的编译能力。

@babel/standalone

可以在非 Node.js 环境(比如浏览器环境)自动编译含有 text/babel 或 text/jsx 的 type 值的 script标签,并进行编译

如下面代码:

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
    const getMessage = () => "Hello World";
    document.getElementById('output').innerHTML = getMessage();
</script>

@babel/standalone的核心代码与@babel/core的关系:

import {
  transformFromAst as babelTransformFromAst,
  transform as babelTransform,
  buildExternalHelpers as babelBuildExternalHelpers,
} from "@babel/core";

所以,@babel/standalone也是基于@babel/core而产生的应用。@babel/standalone可以在浏览器中直接执行,因此这个包对于浏览器环境动态插入高级语言特性的脚本、在线自动解析编译非常有意义。我们知道的 Babel官网也用到了这个包,JSFiddle、JS Bin 也都是 @babel/standalone 的受益者。

以上介绍的@babel/standalone、@babel/cli可以说都是基于@babel/core的能力而衍生的应用,那@babel/core又基于哪些babel基础包搭建的核心能力呢?

@babel/core的组成

  • @babel/parser
  • @babel/code-frame
  • @babel/generator
  • @babel/traverse
  • @babel/types
  • …(等包提供基础的AST处理能力)

@babel/parser

@babel/parser 的实现主要依赖并参考了 acorn 和 acorn-jsx

典型用例:

require("@babel/parser").parse("code", {
  sourceType: "module",
  plugins: [
    "jsx",
    "flow"
  ]
});

parse源码分析:

export function parse(input: string, options?: Options): File {
  if (options?.sourceType === "unambiguous") {
    options = {
      ...options,
    };
    try {
      options.sourceType = "module";
      // 获取相应的编译器
      const parser = getParser(options, input);
      // 使用编译器将源代码转为 ast
      const ast = parser.parse();
      if (parser.sawUnambiguousESM) {
        return ast;
      }
      if (parser.ambiguousScriptDifferentAst) {
        try {
          options.sourceType = "script";
          return getParser(options, input).parse();
        } catch {}
      } else {
        ast.program.sourceType = "script";
      }
      return ast;
    } catch (moduleError) {
      try {
        options.sourceType = "script";
        return getParser(options, input).parse();
      } catch {}
      throw moduleError;
    }
  } else {
    return getParser(options, input).parse();
  }
}

所以说,require(“@babel/parser”).parse()方法可以返回一个针对源码编译得到的 AST,但是这里的AST仍然需要经过我们的修改,才能产生编译后的代码。如何对AST进行二次修改和聚合,@babel/traverse、@babel/types、@babel/generator带给了我们这些。

@babel/traverse、@babel/types

提供了对具体的AST节点的修改能力。

traverse(ast, {
  enter(path) {
    if (path.isIdentifier({ name: "n" })) {
      path.node.name = "x";
    }
  }
});

@babel/generator

对新的 AST 进行聚合并生成 js 代码

const output = generate(
  ast,
  {
    /* options */
  },
  code
);

Babel整体编译底层流程 手绘

在这里插入图片描述

  1. @babel/core利用自身能力将source code解析成AST
  2. @babel/traverse、@babel/types利用自身能力将旧AST转换为新AST
  3. @babel/generator利用自身能力将新AST生成output code

在这里插入图片描述

更多推荐

【MongoDB】docker部署社区版(一)

0、背景介绍项目中使用MongoDB了,服务器挂掉,自己在本地搭一个试试。1、版本选择首先有社区版和和商业版。我选的是社区版。链接:https://hub.docker.com/r/mongodb/mongodb-community-server/tags1.1、标签选择看到标签有两个大类,一个是Ubuntu一个是ub

Spring MongoDB

为什么选择MongoDB,而不是其它的数据库。在SQL和NoSQL之间有一个大的争论。在SQL中,创建Tables。在NoSQL中,我们不维持这个结构。为什么这样重要。因为SQL在19世纪70年代变得流行的时候,在那时存储是昂贵的。我们需要确保使用最小的存储,这样我们可以存储最大量的数据。这是SQL类型表的意义。在当前

网络编程day04(网络属性函数、广播、组播、TCP并发)

今日任务对于newfd的话,最好是另存然后传入给分支线程,避免父子线程操作同一个文件描述符1.广播:接收端代码:#include<stdio.h>#include<string.h>#include<stdlib.h>#include<sys/types.h>/*SeeNOTES*/#include<sys/socke

如何通过一键导出导入数据实现批量重命名文件名称

在日常办公中,我们经常需要对大量的文件进行重命名,以便更好地管理和查找文件。而且,有时候我们还需要将文件名称翻译成其他语言,以适应不同的工作需求。如何高效地完成这项任务呢?接下来,我将介绍一种方法,让你事半功倍。首先,你需要下载并安装一个名为“固乔文件管家”的软件。这是一款功能强大的文件管理工具,可以帮助你批量改名文件

山洪灾害预警方案(山洪预警解决方案的组成)

​随着气候变化的不断加剧,山洪灾害在许多地区成为了极具威胁性的自然灾害之一。为了帮助地方政府和居民更好地预防和应对山洪灾害,我们设计了一套基于星创易联的SR600工业路由器和DTU200的山洪灾害预警方案,并成功在某地区进行了部署。案例背景:我们选择了某山区作为实际部署的案例。这个地区常年受山洪的威胁,由于地处偏远,传

使用 Verilator 进行 Verilog Lint

FPGA设计是无情的,所以我们需要利用能获得的任何软件进行检查。Verilator是一个Verilog仿真器,还支持linting:静态分析设计中的问题。Verilator不仅可以发现综合工具可能忽略的问题,而且运行速度也很快。Verilator也非常适合使用SDL进行图形仿真。安装VerilatorLinuxVeri

C++ Primer Plus 第七章笔记

目录函数基本知识没有返回值的函数:void函数有返回值的函数:函数原型1.为什么需要函数原型?2.函数原型的语法3.函数原型的功能按值传递函数参数形参和实参局部变量参数问题使用const指针参数调用自身的函数(递归)指向函数的指针1.获取函数的地址2.声明函数指针3.使用函数指针来调用函数函数基本知识要使用C++函数必

分布式协议与算法——Raft算法

目录Raft算法领导者选举有哪些成员身份?领导者选举流程选举细节节点之间如何通信什么是任期选举有哪写规则随机超时时间是什么小结日志复制如何理解日志如何复制日志如何实现日志的一致小结节点成员变更成员变更的问题如何通过单节点变更解决成员变更的问题小结Raft小结Raft算法Raft算法属于Multi-Paxos算法,它是在

2023年贵州省职业院校技能大赛高职组信息安全管理与评估竞赛试题

2023年贵州省职业院校技能大赛高职组信息安全管理与评估竞赛试题第一阶段竞赛项目试题根据信息安全管理与评估技术文件要求,第一阶段为网络平台搭建与网络安全设备配置与防护。本文件为信息安全管理与评估项目竞赛-第一阶段试题。介绍竞赛阶段任务阶段竞赛任务第一阶段平台搭建与安全设备配置防护任务1网络平台搭建任务2网络安全设备配置

【SQL】MySQL中的窗口函数(开窗函数)

窗口函数是MYSQL8.0新增的聚合函数:多行变一行,常见的sum,count,max,min窗口函数:行数不变,常见的row_number,rank语法格式:窗口函数(表达式)over(partitionby…orderby…frame_clause)partitionby是分区,类似于groupby,如去掉相当于对

MySQL数据库入门到精通1--基础篇(MySQL概述,SQL)

1.MySQL概述1.1数据库相关概念目前主流的关系型数据库管理系统:Oracle:大型的收费数据库,Oracle公司产品,价格昂贵。MySQL:开源免费的中小型数据库,后来Sun公司收购了MySQL,而Oracle又收购了Sun公司。目前Oracle推出了收费版本的MySQL,也提供了免费的社区版本。SQLServe

热文推荐