Babel基础学习与简单配置

1,491 阅读9分钟

Babel一词的由来

偶然发现一部电影的名称叫Babel,中文翻译成通天塔。于是百度了一下这一词的由来。

通天塔(Babel Tower)是《圣经·旧约·创世记》第11章故事中人们建造的塔。根据篇章记载,当时人类联合起来兴建希望能通往天堂的高塔;为了阻止人类的计划,上帝让人类说不同的语言,使人类相互之间不能沟通,计划因此失败,人类自此各散东西。

看罢,我理解了Babel一词的深意。人类世界和前端世界有着相同的困境。

  • 人类的困境:人们说不同的语言,因此产生了隔阂,不能联合修建通往天堂的高塔。
  • 前端的困境:前端程序员使用着不同的语言如(ES6、TS、JSX),浏览器种类和版本众多,对ES的支持程度都不一样。我们需要修建一个通往开发天堂的高塔。

一个JavaScript 编译器,将采用 ES6+ 语法编写的代码转换为向后兼容的JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。这个编译器命名为Babel。

@babel/core 核心库

Babel 是一个工具链,由大量的工具包组成,接下来我们逐步了解。

@babel/core核心功能:将源代码转成目标代码。

// 先初始化一个项目,保证有package.json文件
npm init

// 再安装@babel/core
npm install --save-dev @babel/core
import * as babel from "@babel/core";

const sourceCode = 'es代码' // 对babel来说是个字符串
const options = { ... } // 相关配置
const { code, map, ast } = babel.transformSync(sourceCode, options); // 代表方法展示

// 编译得到的结果:
// code: 编译后的代码
// map: Sourcemap,是源代码和生产代码的映射,里面储存着代码转换前后的对应位置信息。
// ast: 抽象语法树,源代码语法结构的一种抽象表示。babel内部就是通过操纵ast做到语法转换。

Sourcemap的作用: Sourcemap 构建了处理前以及处理后的代码之间的一座桥梁,方便定位生产环境中出现 bug 的位置。因为现在的前端开发都是模块化、组件化的方式,在上线前对 js 和 css 文件进行合并压缩容易造成混淆。如果对这样的线上代码进行调试,肯定不切实际,sourceMap 的作用就是能够让浏览器的调试面版将生成后的代码映射到源码文件当中,开发者可以在源码文件中 debug,这样就会让程序员调试轻松、简单很多。

还有很多方法不多赘述,感兴趣看官方文档babel-core

@babel/cli

@babel/cli是一个能够从终端(命令行)使用的工具。

@babel/cli核心功能:支持命令行操纵babel进行编译代码。

// 安装@babel/core和@babel/cli
npm install --save-dev @babel/core @babel/cli

package.json 文件应当包括如下内容:

{
  "devDependencies": {
    "@babel/cli": "^7.0.0",
    "@babel/core": "^7.0.0"
  }
}

我们在项目里写一个script.js文件,写一些es代码进去,然后在命令行中执行

npx babel script.js

就可以命令行中看到babel转换后的输出代码。如果你想将输出内容放到一个新文件里,可以:

npx babel script.js --out-file script-compiled.js

还有很多命令参数不多赘述,感兴趣看官方文档babel-cli

插件

@babel/core的工作过程是源码解析成AST,再用AST生成目标代码,但如果不安装插件,转换前后的代码是没有区别的。具体怎么转换,是通过插件来控制的。

比如,我们想将ES6的箭头函数转换成 ES5 支持的语法。

// 安装一个插件@babel/plugin-transform-arrow-functions
npm install --save-dev @babel/plugin-transform-arrow-functions

// 使用babel-cli操纵babel-core,将src目录下的所有js进行编译,输出到lib目录下,babel编译过程中使用插件@babel/plugin-transform-arrow-functions
npx src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions

代码转换前后:

const fn = () => 1;

// 转换后

var fn = function fn() {
  return 1;
};

预设(preset)

如果我们想使用所有ES6+新语法,那无疑要装非常多的插件,有没有一种插件的集合,装一个就让babel支持多个插件的功能?有,它就是预设(即一组预先设定的插件)。

// 我们安装一个最常用的预设@babel/preset-env
npm install --save-dev @babel/preset-env
// preset-env 所包含的插件将支持所有最新的 JavaScript (ES2015、ES2016 等)特性。
npx src --out-dir lib --presets=@babel/env

@babel/preset-env是一个智能预设,可以根据目标浏览器对js的支持程度转换我们的源代码。

配置

现在想一个问题,虽然@babel/preset-env可以帮助我们进行新语法的转换,但是转换到什么程度呢?怎么精确控制转换后的代码既能正常运行在目标浏览器上,又可以尽量使用到目标浏览器支持的新语法(这样才能保证打出的包尽可能的小一点)?答案是使用配置文件指示@babel/preset-env怎么转换。

// 在项目中创建babel.config.json,babal编译的时候会自动读取此配置。
// 核心的配置也特别简单,targets属性可以明确指示要在哪些浏览器运行目标代码。@babel/preset-env就自动帮我们转了,非常智能。
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        }
      }
    ]
  ]
}

targets配置内容的来龙去脉

关于@babel/preset-env编译的目标targets字段,里面可以写哪些值呢?

除了上面那种执行浏览器版本,还支持查询表达式如下:

"targets": "> 0.25%, not dead"

其中表达式代表的意思:

"> 1%", //全球超过1%人使用的浏览器
"last 2 versions",  //所有浏览器兼容到最后两个版本根据CanIUse.com追踪的版本
"not ie <= 8" , //方向排除部分版本
"since 2013" ,  //2013年之后发布的所有版本
"Firefox ESR" , //火狐最新版本
"Firefox 12.1", //指定浏览器的兼容到指定版本
"Firefox > 20", //Firefox的版本比20更新 >=,< 并且也可以 <= 工作
"cover 99.5%",  //提供覆盖的最流行的浏览器
"unreleased versions" , //alpha和beta版本
"defaults" , //Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)。

@babel/preset-env的targets支持这种格式,是因为内部对Browserslist的集成。

Browserslist:是一个公共配置,它可以让使用它的工具库(如@babel/preset-env,Autoprefixer)根据配置(targets的值)知晓对js、css新语法的支持情况,进而完成工具库自己的功能。Browserslist的底层数据来自网站Can I Use。

Browserslist的配置可以放到上面的targets字段里,也可以放在项目的.browserslistrc文件里作为项目公共配置,这样除了Babel,其他比如Autoprefixer插件也可以读取到配置而完成自己的功能。

问题:如果完全不配置,@babel/preset-env默认的转换行为是什么? 答:默认转换行为相当于配置的是:

"targets":"defaults" //(> 0.5%, last 2 versions, Firefox ESR, not dead)
// 要支持全球超过0.5%人使用的浏览器
// 所有浏览器兼容到最后两个版本
// 火狐最新版本
// 所有未淘汰的浏览器

Polyfill

Polyfill定义:是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。

Babel对于语法的转换,有两种情况需要处理:

  • 语法层: let、const、箭头函数、class等,这些需要在构建时进行转译,是指在语法层面上的转译
  • api方法层:Promise、includes、map等,这些是在全局或者Object、Array等的原型上新增的方法,它们可以由相应es5的方式重新定义。

对于api方法层,需要用到Polyfill。在Babel 7.4.0版本之前,使用的方案是在项目入口处直接引入

先安装:

// 注意这里是安装在dependencies,而不是devDependencies,可见它不是在编译时工作,而是在运行时工作的库
npm install --save @babel/polyfill

在项目入口处引入:

import "@bable/polyfill";
// 引用"@bable/polyfill"相当于引用了下面这两个库,是等价的
import "core-js/stable"; 
import "regenerator-runtime/runtime";

// ...项目代码

这样做可以解决问题,但是这样的方案有两个缺陷:

  • 全量引入了@bable/polyfill。但如果我的源代码只使用了Promise,没有使用includes、map等方法,没必要引入所有api的polyfill代码。
  • 全局变量污染,includes、map等方法都是添加到内置Array对象上的,把浏览器自身的内置对象污染了。

@babel/preset-env为我们提供了解决第一个缺陷的配置:useBuiltIns

useBuiltIns

默认情况下,这个属性是false。

// babel.config.json文件中
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "false" // 默认是false
      }
    ]
  ]
}

// 项目入口处
import "core-js/stable"; 
import "regenerator-runtime/runtime";

将useBuiltIns设置成entry

需安装

npm install core-js@3.8 --save

在配置文件中

{
    useBuiltIns: 'entry'
    corejs: { version: "3.8", proposals: true },
}

根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手动添加 import '@babel/polyfill',会自动根据 browserslist 替换成浏览器不兼容的所有 polyfill。

这种就不需要在项目入口处导入polifill代码了,这个工作已经交给@babel/preset-env。

将useBuiltIns设置成usage

{
    useBuiltIns: 'usage'
    corejs: { version: "3.8" },
}

usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加。

这种同样不需要在项目入口处导入polifill代码,这个工作已经交给@babel/preset-env。

综合讲 useBuiltIns: 'usage'是会让polyfill代码在浏览器端存留最少的一种配置。更多细节请看官方文档useBuiltIns

corejs配置:这里的corejs配置,是告诉@babel/preset-env使用哪个版本的corejs作为polyfill。

@babel/plugin-transform-runtime

useBuiltIns配置解决了按需加载的问题,但没有解决全局污染问题。我们可以借助@babel/plugin-transform-runtime插件来解决问题。

安装:

npm install --save-dev @babel/plugin-transform-runtime

npm install --save @babel/runtime-corejs3

相关配置:

{
  "presets": [
    ["@babel/preset-env", 
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ],
  "plugins": [
    [ 
      "@babel/plugin-transform-runtime", {
        "corejs": 3
      }
    ]
  ]
}

@babel-loader

babel-loader与@babel/cli有共同之处。

  • @babel/cli:让命令行能与babel打交道。
  • babel-loader:让webpack能与babel打交道。

webpack会让所有的js文件都经过babel-loader,进而对源码进行转换。

@babel/eslint-parser

babel/eslint-parser与@babel/cli也是同样道理。是连接eslint和babel的桥梁。

ESLint的默认解析器和核心规则仅支持最新的最终 ECMAScript 标准,不支持 Babel 提供的实验性(例如新功能)和非标准(例如Flow或TypeScript类型)语法。@babel/eslint-parser 是允许 ESLint 在由 Babel 转换的源代码上运行的解析器。

总结

这篇文章总结了Babel基本且核心的知识点。学习@babel/core、@babel/cli、插件、预设、Polyfill等在Babel中重要概念。

最后给出我在项目中的安装与配置

// 安装
npm install --save-dev @babel/core
npm install --save-dev @babel/preset-env
npm install --save-dev @babel/plugin-transform-runtime

npm install core-js@3.8 --save
npm install @babel/runtime-corejs3 --save


// 配置
{
  "presets": [
    ["@babel/preset-env", 
      {
        "targets": "defaults", // 可以不配置,用默认
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ],
  "plugins": [
    [ 
      "@babel/plugin-transform-runtime", {
        "corejs": 3
      }
    ]
  ]
}