第四章 babel

92 阅读6分钟

babel的安装和使用

官网:babeljs.io/

民间中文网:www.babeljs.cn/

不同的浏览器或不同版本的浏览器对JS标准的支持程度往往是不一样的,这就给开发者带来了很大的困扰

babel的目标就是把使用不同标准书写的JS代码,编译为统一的、能被各种浏览器识别的JS代码

image.png

babel的工作流程和postcss、webpack十分类似,babel本身仅提供了源码分析的功能,剩下的代码转换工作是交付给具体的插件来完成的

image.png

babel的安装

babel可以和构建工具联合起来使用,也可以单独使用

如果要单独使用babel,就需要安装下面两个库:

  1. @babel/core

    这是babel的核心库,它提供了编译所需要的所有api

  2. @babel/cli

    该库提供了一些cli命令,以便开发者调用核心库中的api来完成编译

npm i -D @babel/core @babel/cli

babel的使用

编译单个文件:

babel 要编译的文件所在位置 -o 编译结果文件防止的位置 [-w]

编译整个目录中的所有文件:

babel 要编译的目录所在位置 -d 编译结果放置的目录的位置 [-w] 

-w是可选参数,当源文件内容发生变化时,babel会自动重新对源文件进行编译

babel的配置

babel本身没有做什么事情,真正的编译要依托于babel插件babel预设来完成

预设是多个插件的集合体,用于解决一系列常见的兼容问题

需要通过在工程根目录中创建一个配置文件.babelrc来应用babel插件和babel预设

// .babelrc

{
    "presets": [],			// 用于应用babel预设
    "plugins": []			// 用于应用babel插件
}

babel预设

最常用的babel预设是@babel/preset-env,它可以让开发者编写新标准的JS语法

经过该预设编译过后,编译结果文件的开头会加入"use strict",即结果代码是在严格模式下运行的

安装:

npm i -D @babel/preset-env

配置:

// .babelrc

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

若有多个预设,则预设的运行先后顺序是presets数组的倒序顺序,位于数组后面的预设会先被运行,和webpack的loader类似

设置兼容范围

@babel/preset-env需要根据兼容的浏览器范围来确定如何编译

和postcss一样,可以使用文件.browserslistrc来定义浏览器的兼容范围

last 3 version
> 1%
not ie <= 8

预设自身的配置

如果需要给预设本身加入配置,则需要写为下面的形式(插件也类似):

// .babelrc

{
    "presets": [
        ["@babel/preset-env", {
            "配置1": "配置内容",
            "配置2": "配置内容",
            ...
        }]
    ]
}
useBuiltIns

常见的预设配置是useBuiltIns

该配置的默认值为false,表示预设仅转换新语法,但不对新的API进行处理

API就是JS运行环境所提供的一些函数或对象,例如Promise构造函数就是一个API

有些API是伴随着新标准一起出现的,因此不支持新标准的浏览器也不会有这些API

babel对API进行的处理,就是将这些API手写出来,让浏览器能够使用,具体的手写工作并不是由babel来完成的,但babel知道哪些库已经手写好了这些API,因此转换时babel会将库导入进来,这样JS就可以使用到这些手写的API了

将该配置设置为"usage"后,预设就可以从core-js库中导入相应的模块来得到功能相同的手写的API

例如"core-js/modules/es6.promise"模块中导出了一个手写的Promise构造函数

在useBuiltIns为true(转换后为true)的情况下,编译结果中就会导入该模块,因此之后使用的Promise构造函数就是该模块所提供的

在useBuiltIns为false的情况下,编译结果中不会导入该模块,文件中所使用的Promise就是js的运行环境所提供的Promise

预设本身并没有包含core-js,因此将useBuiltIns设置为true后,还需要开发者手动安装core-js

npm i core-js

不同版本的core-js中模块的存放位置以及模块名称会有些许变化,而该预设默认使用的是2版本的core-js,因此使用的也是2版本的core-js中模块的路径,如果安装的core-js版本不是2,则需要提供core-js的版本给预设

// .babelrc

{
    "presets": [
        ["@babel/preset-env", {
            "useBuiltIns": "usage",
            "corejs": 3,			// 设置babel所使用的core-js库的版本
            ...
        }]
    ]
}

该预设并不能将所有的语法都进行转换,例如async和await,该预设就处理不了

但预设也可以通过导入另一个库regenerator-runtime中的某个模块来实现类似于async和await的功能

因此使用该预设时,还需要安装regenerator-runtime

npm i regenerator-runtime

babel插件

除了预设可以转换代码外,插件也可以转换代码,插件和预设之间的运行顺序如下:

  • 插件在预设之前运行
  • 插件运行的顺序是注册的顺序
  • 预设运行的顺序是注册顺序的逆序

通常情况下,@babel/preset-env只转换那些已经成为正式标准的语法,对于还没有成为正式标准,仍处于早期阶段的语法,该预设是不做处理的

对于这些处于早期阶段的语法,要想转换就需要使用插件

插件的注册方式如下:

// .babelrc

{
    "plugins": [
        "插件名称1",
        ["插件名称2", 插件配置对象]
    ]
}

常用的插件有以下几个:

  • @babel/plugin-proposal-class-properties

    该插件会将类中的字段初始化代码进行转换

    class A {
        a = 1;					// 字段初始化代码
        constructor(){ }
    }
    
  • @babel/plugin-proposal-optional-chaining

    过去对对象内部的一个深层次属性进行验证时,为了避免代码运行时报错,就需要使用下面的方式:

    var obj = {
        foo: {
            bar: {
                baz: 42
            }
        }
    }
    
    // 判断obj.foo.bar.baz是否等于42
    if(obj && obj.foo && obj.foo.bar && obj.foo.bar.baz === 42){
        ...;
    }
    

    这种写法过于麻烦,因此ES官方提议了下面的语法(目前还未成为正式标准)

    var obj = {
        foo: {
            bar: {
                baz: 42,
            },
        },
    };
    
    if(obj?.foo?.bar?.baz){
        ...;
    }
    

    当?.前面的内容为undefined时,该表达式就会立刻结束,就可以避免做大量的判断

    该插件就能够对这种语法进行转换

  • babel-plugin-transform-remove-console

    该插件用于将源代码中所有的console.xxx(...)语句去除

  • @babel/plugin-transform-runtime

    若多个源代码文件中使用了同一个API时,则这些源代码所编译形成的编译结果中就会出现重复的API定义代码

    该插件就用于提取编译结果中出现的重复的自定义API,让它们全部变为导入runtime库的语句,避免了代码冗余

    由于编译结果中可能会导入runtime库,因此安装该插件时还需要安装runtime库

    npm i @babel/runtime
    

在webpack中使用babel

在webpack中使用babel-loader和@babel/core来应用babel

安装babel-loader和@babel/core:

npm i -D @babel/core babel-loader

在wepback配置中加入babel:

// webpack.config.js

module.exports = {
    module: {
        rules: [{
            test: /\.js$/,
            use: ["babel/loader"]
        }]
    }
}

注意:不要忘记给babel安装代码转换的插件和预设