babel系列之一(简介与使用)

603 阅读6分钟

通常情况下,babel是作为一个幕后工作者,存在于前端工具链中。webpack, rollup 等构建工具中少不了它的身影,很多的脚手架在生成项目时,已经将需要使用到的babel的相关功能配置好了,但是我们依然有必要去了解和学习babel的基础功能。本文基于babel7,介绍以下几方面内容。

  1. babel简介
  2. babel各个模块介绍
  3. babel的配置和使用

babel简介

babel,最开始叫6to5,顾名思义,就是将es6+的语法转义为es5的语法,使得我们用es6+语法写的代码可以正常的运行在不支持es6语法的浏览器中。随着babel的版本迭代,babel也衍生出来一些新的功能,如将typescript,jsx,flow等转义为js。babel作为一个转义工具,可以将源代码转义为AST(抽象语法树,遵循[ESTree](https://github.com/estree/estree)的规范),并提供修改,转化的功能。因此可以通过babel做很多事情,如静态分析,语法检查,编辑器中的高亮等。

众所周知,babel是一个编译器,主要做的工作就是,编译、转化、生成。

编译:将源代码转化为AST。

转化:对AST进行增删改查,为生成新的代码做准备。

**生成:**根据AST生成新的代码。

在这个过程中,其实可以类比作将高级语言编译生成低级语言,如将c语言代码编译生成汇编语言。这三部分工作,可以对应于babel中的三个核心模块: @babel/parser,@babel/traverse, @babel/generator。这些包提供了编译、转化、生成的能力,但是具体怎么转化,还是需要提供额外的插件才行,不然,经过这三个阶段后,源代码还是会原样输出。

import { parse } from '@babel/parser';import * as generate from '@babel/generator';
const code = `const name = 'hahaha'`
const ast = parse(code);
console.log(ast);
const result = generate.default(ast);
console.log(result);

如上图,可以直观的看到使用babel转义的AST,以及使用AST生成的代码。我们日常用到的@babel/preset-env中集成了大部分es6+语法转义到es5语法功能的插件,除此之外@babel/preset-react、@babel/preset-jsx常见于react开发。一些未正式纳入标注的语法,被放在了@babel/plugin-proposol-xxx中。

babel各个模块介绍

这里可以看到,babel的主要模块如下:

以上,我们可以看到,核心的是四个模块,其中三个我们在第一部分简介中已经做了介绍。@babel/core这个包集成了编译、转化、生成的能力,babel-cli和babel-node等都依赖于它。@babel/cli是提供命令行的方式来使用babel的功能,@babel/types提供在AST解析转化过程中各个节点的类型判断和生成等。@babel/runtime包含需要打入生成产物中的代码,以及处理这些代码的逻辑,主要包括generator, corejs, 和一些helper,类似于polyfill。@babel-registerbabel-register 模块改写 require 命令,为它加上一个钩子。此后,每当使用 require 加载 .js.jsx.es.es6 后缀名的文件,就会先用 babel 进行转码。@babel/template用来批量生成代码。@babel/helpers包含各个模块中需要用到的公共方法。@babel/code-frame用来友好地呈现错误信息。@babel/preset-env预置了将各个版本的es6+语法转化为es5的功能,这样就不用引入不同版本的es语法的转化的包了。

babel的配置和使用

一般来说,我们主要是在打包工具中用到babel, 如webpack, rollup等。webpack中需要使用babel-loader来引入babel (rollup中需要rollup-plugin-babel)。webpack中引入babel方法如下:

module: {
  rules: [
    // 对js文件进行babel-loader处理(将ES6语法转换成ES5)
      {
        test: /.js|.tsx|.jsx|.ts$/,
        exclude: /node_modules/, //node_modules目录下的文件不转义
        loader: "babel-loader"
      }
  ]
}

再通过babel配置文件,或者直接在webpack配置文件中引入options来添加babel的配置

{
    presets: [
        [
            "@babel/preset-env",
            {
                targets: "> 0.25%, not dead",
                corejs: 3,
                useBuiltIns: "usage" //按需注⼊,entry:全量注入,false,不引入。
            }
        ]
    ]
}

如果只配置target@babel/preset-env可以将es6+的语法转化为需要支持的浏览器环境兼容的es5的语法,但是有那么多的浏览器,babel是如何知道每个版本的浏览器环境支持什么样的语法呢,babel中的@babel/compat-data包中维护每种es规范的特性和这种特性在特定环境下的支持情况,再根据brwoserslist来查询出特定的浏览器环境类型,就可以精准的匹配到目标环境支持的语法,并转化相应的代码。语法转化只是转化类似 let, const转化为var, 但是不会转化api, 如Object.keys,Array.from。babel7.4.0之前需要使用@babel/polyfill来转化这些api,@babel/polyfill中包含core-jsregenerator-runtime。但是在7.4.0以上的版本中,废弃了@babel/polyfill,直接引入core-js和regenerator-runtime。

core-js是一个独立的库,里面包含了各种api的polyfill,如果直接引入这个包,会非常大,里面会包含很多我们没有用到的polyfill,因此使用useBuiltIns:“usage”可以帮助我们按需引入,只引入代码中用到的那些api。但是这种引入方式存在一个问题,就是会默认修改全局变量的原型。对于例如 Array.from 等静态方法,直接在 global.Array 上添加;对于例如 includes 等实例方法,直接在 global.Array.prototype 上添加。这样直接修改了全局变量的原型,有可能会带来意想不到的问题,这个问题在开发第三方库的时候尤其重要。另外babel在转义语法的时候,会在每个用到特定语法的文件中,实现一个转义语法的方法,那么将产生大量的冗余代码。@babel/plugin-transformm-runtime插件解决了上述两个问题。引入这个模块后。

  • api 从之前的直接修改原型改为了从一个统一的模块中引入,避免了对全局变量及其原型的污染,解决了第一个问题
  • helpers 从之前的原地定义改为了从一个统一的模块中引入,使得打包的结果中每个 helper 只会存在一个,解决了第二个问题

而这个插件的运行需要依赖于@babel/runtime,提供各种polyfill和helper等。需要注意的是plugin-transform-runtime并不支持targets的配置,使用这个插件将可能会有多余的转化。即目标系统中支持的语法,也可能会被转义更低级的语法,这是多余的操作,造成浪费。

我们在配置preset的同时也可以配置plugin,规则是

  • Plugin 在 Preset 之前。
  • Plugin 会从前到后顺序执行。
  • Preset 的顺序则从后向前。

以上就是babel的简单使用。

参考链接:

1.zhuanlan.zhihu.com/p/147083132

2.github.com/babel/babel

3.babeljs.io/docs/en/

4.zhuanlan.zhihu.com/p/43249121

5.blog.windstone.cc/es6/babel/

6.www.cnblogs.com/jyybeam/p/1…