学习 Babel 肯定要知道什么是 Babel?Babel 能干什么?
关于这个问题,我们在 Babel 的官网也能找到答案!
Babel 中文文档 · 下一代 JavaScript 编译器
image.png
Babel 是一个 JavaScript 编译器,那么到底为啥要编译 JavaScript 呢?

如今,ECMAScript 都已经发展到 ES8 了,但并不是所有的浏览器都能支持新的 ECMAScript 语法,所以我们就需要一个工具来帮我们把一些激进的语法转换为低版本浏览器也能兼容运行的语法,这就是 Babel 需要做的工作!
Babel 可以把 ES5+ 的代码转换为 ES5 的写法,这样我们就不用担心在低版本浏览器中不兼容导致运行报错了。

一个完整的例子

首先我们先看一个 Babel 的案例,要使用 Babel 首先要通过 npm 进行安装:

  1. $ npm install --save-dev @babel/core @babel/cli @babel/preset-env

然后我们需要一个创建一个 Babel 的配置文件:

  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. "targets": {
  7. "ie": "7",
  8. "edge": "17",
  9. "firefox": "60",
  10. "chrome": "67",
  11. "safari": "11.1"
  12. },
  13. "useBuiltIns": "usage",
  14. "corejs": "3.6.5"
  15. }
  16. ]
  17. ]
  18. }

然后我们还需要一个 src/index.js 文件来编写我们的 JS 源码:

  1. const sayHi = () => {
  2. console.log("Hello, Babel!");
  3. };
  4. sayHi();

到这里,我们的目录结构如下:

  1. demo
  2. ├─ README.md
  3. ├─ babel.config.json
  4. ├─ package-lock.json
  5. ├─ package.json
  6. └─ src
  7. └─ index.js

最后我们运行 Babel:

  1. # 告诉 babel 把 src 目录下的文件编译到 dist 文件目录下
  2. $ npx babel src --out-dir dist

不出问题的话,你会看到你的代码目录中多了一个 dist 文件夹,然后我们查看 dist/index.js 文件内容:

  1. "use strict";
  2. var sayHi = function sayHi() {
  3. console.log("Hello, Babel!");
  4. };
  5. sayHi();

可以很明显的看到,Babel 把我们的const()=>都进行了编译转换!所以,你知道 Babel 是干啥的了吗?

Babel 相关的包

上面案例中,我们最开始安装了 3 个模块,下面简单说一下这 3 个包模块都是干啥的:

  • @babel/cli
    • 这个包从名字上也能猜到,这是包主要是让你能在终端中使用 Babel 的命令。
  • @babel/core
    • 这个包是 Babel 的核心模块,它是 Babel 编译过程中不可缺少的一部分。该模块负责解析、转换和生成 JavaScript 代码。
    • 它提供了一组 API,可以通过编程的方式使用 Babel 进行转换。开发者可以使用 @babel/core 来配置和执行各种转换操作,例如将 ES6 语法转换为 ES5、使用插件转换 JSX 等。 ```javascript const babel = require(“@babel/core”);

babel.transformSync(“let a = ‘test’;”, optionsObject);

  1. - @babel/preset-env
  2. - 该模块包含了一组预先设置好的插件,这样我们就不需要一个一个的导入插件了,要理解这句话首先要了解一下插件!
  3. - @babel/polyfill
  4. - 该模块是语法的垫片,我们虽然能通过插件或者预设解决一些语法的转换,但是我们无法使用像 PromiseMapSet 这些构造函数,因为运行的环境中没有它们,这就需要 polyfill 把这些函数填充进去!
  5. <a name="mC6Sc"></a>
  6. ## Babel 中的 plugins
  7. JS 的转换规则会体现为插件的形式,插件是小型 JavaScript 程序,它指示 Babel 如何进行代码转换。你甚至可以编写自己的插件,来应用你想要的任何转换规则。想要将 ES2015+ 语法转换为 ES5,我们可以依赖类似 @babel/plugin-transform-arrow-functions 这样的官方插件,如:<br />1、安装插件
  8. ```bash
  9. $ npm install --save-dev @babel/plugin-transform-arrow-functions

2、删除我们之前创建好的 Babel 配置文件,否则运行 Babel 命令的时候,依然会执行配置文件

  1. demo
  2. ├─ README.md
  3. # ├─ babel.config.json
  4. ├─ package-lock.json
  5. ├─ package.json
  6. └─ src
  7. └─ index.js

3、运行 Babel 命令

  1. # 运行 Babel 把 src 下的文件使用 @babel/plugin-transform-arrow-functions 插件进行转换
  2. $ npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions

4、查看编译结果

  1. const sayHi = function () {
  2. console.log("Hello, Babel!");
  3. };
  4. sayHi();

可以看到,Babel 只把箭头函数进行了转换!
如果你还想转换其他的语法,还需要继续安装插件。
image.png

插件列表详见:
插件列表 · Babel 中文文档

例如我们像把 ES6 的语法全部转换为 ES5 的语法,那么我们就得安装这些插件:
image.png

运行的时候就得这样:

  1. $ npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions @babel/block-scoped-functions @babel/block-scoping

当然,你也可以使用 Babel 的配置文件:

  1. {
  2. "plugins": [
  3. "@babel/plugin-transform-arrow-functions",
  4. "@babel/block-scoped-functions",
  5. "@babel/block-scoping"
  6. ]
  7. }

如果你想支持全部的 ES5+ 语法,那一个插件一个插件的安装,导入不得累死吗?这个时候就要使用 @babel/preset-env 啦

插件的执行顺序是正序的,也就是说先执行@babel/plugin-transform-arrow-functions,其次执行@babel/block-scoped-functions,最后执行@babel/block-scoping

关于更多插件的介绍:
插件 · Babel 中文文档

Babel 中的 presets

preset 就是包含着一组预先设定的「插件」,而不是逐一添加我们想要的所有插件,也就是一个插件的合集。
还是上面的例子,我们看看 preset 如何使用:
1、安装 preset-env,这是一个包含所有 ES5+ 语法转换插件的预设

  1. $ npm install --save-dev @babel/preset-env

2、运行 Babel

  1. # 使用 preset-env 预设,而不是插件
  2. $ npx babel src --out-dir dist --presets=@babel/env

3、查看编译后的文件

  1. "use strict";
  2. var sayHi = function sayHi() {
  3. console.log("Hello, Babel!");
  4. };
  5. sayHi();

可以看打,本次编译除了把箭头函数进行了转换,还可 const 也进行了转换。

当然,你除了使用命令行,还通过 Babel 的配置文件来使用 preset-env:

  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. // targets 表示要兼容的浏览器版本
  7. "targets": {
  8. "ie": "7",
  9. "edge": "17",
  10. "firefox": "60",
  11. "chrome": "67",
  12. "safari": "11.1"
  13. }
  14. }
  15. ]
  16. ]
  17. }

最后直接运行:

  1. $ $ npx babel src --out-dir dist

效果是一样的!

预设的执行顺序是倒序的,例如下面的案例:

  1. {
  2. "presets": ["babel-preset-myPreset", "@babel/preset-env"]
  3. }

Babel 执行的时候,会先执行@babel/preset-env,其次是babel-preset-myPreset

关于预设的更多介绍:
预设 · Babel 中文文档

关于更多配置文件的使用,请看:
配置文件 · Babel 中文文档

Babel 中的 polyfill

@babel/polyfill 模块包括 core-js 和一个自定义 regenerator runtime,用于模拟完整的 ES2015+ 环境。
这意味着你可以使用像 Promise 或 WeakMap 这样的新内置函数,像 Array.from 或 Object.assign 这样的静态方法,像 Array.prototype.includes 这样的实例方法,以及(提供 regenerator 插件后可以使用) generator 函数。
为了做到这一点,polyfill 会在全局作用域和类似 String 这样的内置对象的原型对象上添加对象或方法。

要使用 polifill 肯定也要安装:

  1. $ npm install --save @babel/polyfill

:::warning ⚠️ 注意
注意:这里使用的是 —save 选项,而不是 —save-dev,这是因为 polyfill 需要在运行时中在源代码之前执行。 :::

然后我们需要更改 Babel 的配置文件,我们使用的是 env preset,其中有一个"useBuiltIns"选项,当设置为"usage"时,打包出的代码只会包含你需要的 polyfill。

  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. "targets": {
  7. "edge": "17",
  8. "firefox": "60",
  9. "chrome": "67",
  10. "safari": "11.1"
  11. },
  12. "useBuiltIns": "usage"
  13. }
  14. ]
  15. ]
  16. }

然后,我们需要在 src/index.js 中写一些新的 API 函数:

  1. const sayHi = () => {
  2. console.log("Hello, Babel!");
  3. };
  4. sayHi();
  5. Promise.resolve().finally();

最后运行 Babel 进行打包,Babel 将检查你的所有代码,以查找目标环境中缺少的功能,并仅包含所需的 polyfill:

  1. $ npx babel src --out-dir dist

最后结果如下:

  1. "use strict";
  2. require("core-js/modules/es.object.to-string.js");
  3. require("core-js/modules/es.promise.js");
  4. require("core-js/modules/es.promise.finally.js");
  5. var sayHi = function sayHi() {
  6. console.log("Hello, Babel!");
  7. };
  8. sayHi();
  9. Promise.resolve()["finally"]();

可以看到,Babel 导入了es.promise.finally.js文件,这是因为 Edge 17 没有Promise.prototype.finally

如果没有将 env preset 的"useBuiltIns"选项的设置为"usage" ,我们必须在入口起点的其他代码之前先完整 polyfill 一次。

  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. "targets": {
  7. "edge": "17",
  8. "firefox": "60",
  9. "chrome": "67",
  10. "safari": "11.1"
  11. },
  12. "useBuiltIns": "entry"
  13. }
  14. ]
  15. ]
  16. }

自 Babel 7.4.0 起, @babel/polyfill 已被弃用,而是使用 core-js/stable 代替来模拟完整 ES2015+ 环境!所以,在我们的入口文件中应首先导入 core-js:

  1. import "core-js/stable";
  2. const sayHi = () => {
  3. console.log("Hello, Babel!");
  4. };
  5. sayHi();
  6. Promise.resolve().finally();

最后编译的结果:

  1. "use strict";
  2. require("core-js/modules/es.symbol.js");
  3. require("core-js/modules/es.symbol.description.js");
  4. // ... 中间太长,我省略掉了
  5. require("core-js/modules/web.url-search-params.js");
  6. var sayHi = function sayHi() {
  7. console.log("Hello, Babel!");
  8. };
  9. sayHi();
  10. Promise.resolve()["finally"]();

更多内容请看:
@babel/polyfill · Babel 中文文档

总结

我们使用 @babel/cli 能够从终端运行 Babel,@babel/polyfill 用于 polyfill 所有新的 JavaScript 功能,preset-env 只包含我们使用的功能的转换规则,polyfills 用于填充目标浏览器中缺少的功能。