Day11 【语法转译】全球使用最广的 JavaScript 转译器 :Babel

95 阅读10分钟

Babel介绍

Babel 是一个编译器,主要用于将最新的 JavaScript 代码转化为向后兼容的代码,以便在老版本的浏览器或环境中运行。

例如,你可能在开发时使用了 ES6、ES7 或者更高级的 JavaScript 特性,但是有些浏览器可能并不支持这些新特性,这时就可以用 Babel 来将代码转化为 ES5 或者更早版本的 JavaScript,以确保代码能在多数浏览器中正常运行。

其次,Babel 更像是一个平台,它本身的核心功能就是解析代码到抽象语法树(AST),然后再将 AST 转回 JavaScript 代码。所有的语法转换(例如将 ES6 转化为 ES5)和功能添加(例如 polyfills)都是通过各种插件来实现的。这一点有点类似于前面我们学习 CSS 工具链时介绍的 PostCSS

16898300520841

Babel 官网:babeljs.io/

以下是 Babel 的一些主要功能:

  • 语法转换:将新的 JavaScript 语法(如 JSX,TypeScript,ES2015+ 特性等)转换为旧的 ES5 语法。

  • 源码映射:在编译后的代码中添加源码映射,以方便调试。

  • Polyfills:添加缺失的特性,如 Promise,Symbol 等,这称为 polyfillBabel 提供了一个 Polyfill 功能,能自动引入所需的 Polyfill。这个功能通过 core-js 模块实现(Babel v7.4.0 之前使用的是 @babel/polyfill),可以模拟整个 ES2015+ 环境。

Array.prototype.includes 这个 API 是 ES2016 的新特性,但是一些旧的浏览器是不支持,像这种情况就需要通过 polyfill 天补充缺失的特性,polyfill 就是一段 JS 代码而已,polyfill 这段代码会去检查当前的浏览器是否支持该 API,如果不支持,polyfill 里面提供了该 API 的实现

if(!Array.prototype.includes){
  Array.prototype.includes = function() {...}
}
  • 插件和预设:Babel 提供了大量的插件支持,你可以通过插件来使用特定的 JavaScript 特性。预设是一组插件的集合,例如,@babel/preset-env 会根据你的环境自动决定需要使用哪些插件。

在前端开发中,Babel 被广泛用于现代 JavaScript 项目,它能确保你的代码能在各种环境中运行,而不需要你手动处理各种浏览器和 JavaScript 版本的兼容性问题。

Babel快速入门

新建一个项目 babel-demo,使用 pnpm init 进行一个初始化,之后安装依赖:

pnpm add --save-dev @babel/core @babel/cli @babel/preset-env
  • @babel/core: 这个是 Babel 的核心包,提供了核心 API
  • @babel/cli:该依赖提供 CLI 命令行工具
  • @babel/preset-env:预设环境,Babel 在做代码转换的时候,是需要依赖插件的,但是会有一种情况,就需要的插件很多。所谓预设,指的就是内置了一组插件,这样我们只需要引入一个预设即可,不需要再挨着挨着引入众多的插件

在 src/index.js 中书写我们的测试代码:

const greet = (name) => `Hello, ${name}!`;
console.log(greet("World"));

接下来在项目的根目录下创建 .babelrc 配置文件,书写如下的配置:

{
  "presets": ["@babel/preset-env"]
}

该配置就是指定我们的预设是什么。

之后在 package.json 里面添加 script 脚本命令

"scripts": {
    // ...
    "babel": "babel src --out-dir lib"
},

编译 src 目录下的文件,输出到 lib 目录下面。

编译结果如下:

"use strict";

var greet = function greet(name) {
  return "Hello, ".concat(name, "!");
};
console.log(greet("World"));

之后我们修改配置文件,指定了浏览器范围:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage",
        "corejs": "3.6.5"
      }
    ]
  ]
}

这一次编译出来的结果如下:

"use strict";

const greet = name => "Hello, ".concat(name, "!");
console.log(greet("World"));

为什么两次不一样呢?原因很简单,第二次我们指定了浏览器版本范围,那么在指定的浏览器版本范围里面的这些浏览器,某一些特性已经支持了,所以就不需要再做转换了。

配置文件

  • 配置文件格式
  • 配置文件选项

配置文件的格式

在 babel 中,配置文件本身又可以分为两种:

  • 项目范围的配置文件
  • 文件相关配置文件

项目范围配置文件

顾名思义,就是该配置文件针对整个项目生效的一个配置,这种类型的配置文件一般放在项目根目录下面,babel 对项目范围级别的配置文件是有格式要求的,一般是指 babel.config.* 这种格式的配置文件,后面的 * 支持各种类型的扩展名:.json .js .cjs .mjs .cts ...

  • babel.config.js ✅
  • babel.config.json ✅
  • .babelrc ❌

文件相关配置文件

这种类型的配置文件就是对特定的文件或者特定的目录以及子目录生效。在 babel 中,如下格式的配置文件是文件级别:

  • .babelrc.* (.json .js .cjs .mjs .cts)
    • .babelrc.js
    • .babelrc.json
  • .babelrc
  • package.json 文件里面的 babel 键

接下来我们来看一个结构示例:

/my-project
|-- frontend
|   |-- .babelrc.json
|   |-- src
|-- backend
|   |-- .babelrc.json
|   |-- src
|-- babel.config.json

假设 babel.config.json 配置如下:

{
  "presets": [
    "@babel/preset-env"
  ]
}

上面配置文件中所指定的预设,会在整个项目中国都被用到。

假设 frontend/.babelrc.json 有如下的配置:

{
  "plugins": [
    "@babel/plugin-transform-react-jsx"
  ]
}

该配置就只会在 frontend 目录范围内生效。babel 在对 frontend 目录下的文件进行编译的时候,会自动的去合并多个 babel 配置文件,最终 frontend 目录下的文件在进行编译的时候,就会使用 @babel/preset-env 预设以及 @babel/plugin-transform-react-jsx 这个插件。

配置文件选项

有关 babel 配置文件所支持的配置项有哪些,可以在官网的 babeljs.io/docs/option… 看到。

所支持的配置项还是比较多,官方进行一个简单的分类:

  • 主要选项
  • 配置加载选项
  • 插件和预设配置
  • 输出目标选项
  • 配置合并选项
  • 源码映射选项
  • 其他选项
  • 代码生成器选项
  • AMD / UMD / SystemJS 选项
  • 选项概念

这里我们不需要一开始就对所有的配置项完全掌握,下面我们就介绍一些常见的配置项。

插件和预设配置

  • plugins:对应的值为一个数组,配置要使用的插件,可以配置多个,注意在配置文件中配置的插件需要提前进行安装
{
  "plugins": [["@babel/plugin-transform-arrow-functions", {}]]
}
  • presets:配置一个预设,对应的值也是一个数组,表示可以配置多个
{
  "presets": ["@babel/preset-env"]
}

输出目标选项

  • targets: 该配置项目用于指定要兼容的浏览器版本范围
{
  "targets": "> 0.25%, not dead"
}

关于指定浏览器范围,有多种多样的形式,例如可以在项目根目录下创建一个 .browserslistrc 配置文件来指定范围,也可以在 package.json 中通过 browserslist 这个键来指定范围。

优先级顺序如下:

  1. targets
  2. .browserslistrc
  3. package.json
  • browserlistConfigFile:默认值是 true,表示允许 babel 去搜寻项目中和 browserlist 相关的配置。例如 babel 配置文件中没有 targets 的配置,但是项目中有 .browserslistrc 这个文件,里面指定了浏览器范围,那么 babel 在进行编译的时候,会去搜索和 browserlist 相关的配置,并在编译的时候应用对应的浏览器范围配置。这个配置对应的值还可以是一个字符串形式的路径,该路径就指定了具体的 browserlist 文件的位置
{
  "presets": [
    ["@babel/preset-env", {
      "browserslistConfigFile": "./.browserslistrc"
    }]
  ]
}

配置合并选项

  • extends:允许你扩展其他的 babel 配置文件,你可以提供一个路径,该路径对应的 babel 配置文件就会作为基础的配置
{
  "extends": "./base.babelrc.json"
}
  • env:为你不同的环境提供不同的配置,例如在开发环境或者生成环境需要使用不同的插件或者预设,那么就可以通过 env 来指定环境。
{
  "env": {
    "development": {
      "plugins": ["pluginA"]
    },
    "production": {
      "plugins": ["pluginB"]
    }
  }
}
  • overrides :该配置项用于对匹配上的特定文件或者目录应用不同的配置
    • test:做匹配
    • include:包含哪些目录
    • exclude:排除哪些目录
{
  "overrides": [
    {
      "test": ["*.ts", "*.tsx"],
      "exclude": "node_modules",
      "presets": ["@babel/preset-typescript"]
    }
  ]
}
  • ignore 和 only :ignore 控制忽略文件,only 指定特有文件
{
  "ignore": ["node_modules"],
  "only": ["src"]
}

源码映射选项

  • sourceMaps:告诉 babel 是否要生成 source map
{
 "sourceMaps": true
}
  • sourceFileName:指定 source map 文件的文件名
{
 "sourceFileName": "customFileName.js"
}
  • sourceRoot:source map 文件对应的 URL 前缀
{
  "sourceMaps": true,
  "sourceRoot": "/root/path/to/source/files/" // 前缀
}

其他选项

  • sourceType:指定 babel 应该如何去解析 js 代码

    • module:如果你的代码使用的 ESM 模块化,里面涉及到了 export 、import,那么应该指定为这个值
    • script:普通的 JS 脚本,没有使用模块化
    • unambiguous:让 babel 自己来判断,babel 检查到你的代码使用了 export 、 import,就会视为模块文件,否则就会视为普通的 script 脚本
  • assumptions:从 babel 7.13.0 开始引入的一项配置项,让开发者对自己的代码做一个假定(更像是对 babel 的一个承诺)

{
  "assumptions": {
    "noClassCalls": true
  }
}

上面配置表示我的代码中不会直接调用类(不会像调用函数一样去调用类),babel 就可以省略生成检查类是否被正确调用的代码。

CLI

关于 babel 所提供的 CLI,你可以在 babeljs.io/docs/babel-… 看到所有所支持的 CLI 命令。

要使用 CLI 命令,首先第一步是安装:

pnpm add --save-dev @babel/core @babel/cli

注意在安装 @babel/cli 这个包的时候,需要同时安装 @babel/core 这个包,这个包是提供 babel 核心 API 的。CLI 背后实际上就是使用的 API 来实现的。

编译文件相关的 CLI

在使用 babel 的 CLI 命令的时候,有一个基本的格式:

babel [file | dir | glob] --out-[file | dir]

如果你没有指定 --out,那么 babel 会将编译后的结果输出到控制台。

常见的格式如下:

# 编译结果输出到控制台
babel script.js 
# 编译结果输出到指定文件
babel script.js --out-file script-compiled.js 
# 编译整个目录到指定目录下
babel src --out-dir lib
# 编译整个目录下的文件,输出到一个文件里面
babel src --out-file script-compiled.js
# 监视文件,当文件发生变化时自动重新编译
babel script.js --watch --out-file script-compiled.js 

我们在进行编译的时候,可以指定是否要生成 source map:

babel script.js --out-file script-compiled.js --source-maps
babel script.js --out-file script-compiled.js --source-maps inline

忽略文件和拷贝文件

有些时候我们在进行编译的时候,想要忽略某些文件

# 忽略 src 目录下面的所有测试文件
babel src --out-dir lib --ignore "src/**/*.spec.js","src/**/*.test.js"

有些文件我们想要原封不动的进行拷贝,不需要 babel 进行编译

# 将 src 目录下的文件原封不动的复制到 lib 目录下
babel src --out-dir lib --copy-files 
# 进行拷贝的时候忽略文件中匹配的文件不要拷贝
babel src --out-dir lib --copy-files --no-copy-ignored

使用插件和预设

在 CLI 命令行里面也是可以指定插件和预设的

# 指定插件
babel script.js --out-file script-compiled.js --plugins=@babel/transform-class-properties,@babel/transform-modules-amd
# 指定预设
babel script.js --out-file script-compiled.js --presets=@babel/preset-env,@babel/flow

使用配置文件

通过 --config-file 可以指定配置文件的位置

babel --config-file /path/to/my/babel.config.json --out-dir dist ./src

如果想要忽略已经有了的配置文件中的配置,可以使用 --no-babelrc

babel --no-babelrc script.js --out-file script-compiled.js --presets=@babel/preset-env,@babel/preset-react