前端工程化还包括bable

# Babel

Babel主要做的就是这几点:

  • 语法转换:将新的语法转换成兼容性更好的旧语法。
  • 特性垫片:通过Polyfill 实现目标环境中缺少的特性,将环境的特性差异垫平,说白了还是做兼容。

比如下面这段代码

// Babel Input: ES2015 arrow function
[1, 2, 3].map((n) => n + 1);

// Babel Output: ES5 equivalent
[1, 2, 3].map(function(n) {
  return n + 1;
});
1
2
3
4
5
6
7

babel的使用场景主要分为两种:命令行和构建工具。

babel为命令行提供了@babel/cli工具,支持在命令行直接执行babel命令。

npm install --save-dev @babel/core @babel/cli
1

安装好在项目中执行babel命令就可以进行转换了。

babel src --out-dir lib # 将src下的文件处理后输出到lib
1

在构建工具中使用是我们使用babel更常用的方式。

以webpack为例,babel提供了一个loader:babel-loader,使用这个loader我们就能很轻松实现代码转换。

module: {
  rules: [
    {
      test: /\.js$/,
      loader: 'babel-loader',
      options: {
          // 配置项
      }
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11

bable的运行方式:

Babel的运行过程分为三个阶段:解析 --- 转换 --- 生成

  1. 解析:解析代码,并生成AST抽象语法树
  2. 转换:将上一步的AST转换成目标环境支持的AST
  3. 生成:根据转换后的AST生成目标代码

# bable配置

babel7支持多种方式来进行配置:

除了json文件之外,babel还支持其他格式的配置文件,比如 .js.cjs(Commonjs)和.mjs(ESModule),它们相对于json格式更加灵活,可以在配置中进行编程处理,但是会使配置无法静态分析,失去可缓存性。

// babel.config.js
module.exports = {
  presets: [],
  plugins: []
};
1
2
3
4
5

babel虽然有多种方式来写配置文件,但是配置的内容都是类似的,主要包含pluginspresets两部分,不过这里需要注意几点:

  • 配置格式:plugins和presets配置项均为数组,如果每项不需要配置的话,直接放入数组(值为项的字符串名称);如果要配置的话,则需要把格式改成数组,该数组的第一个元素是该项的字符串名字,第二个元素是该项的配置对象。这里填写的plugins和presets,babel会自动检查是否已被安装在node_modules下面,当然,也可以使用相对路径来使用本地文件。

    "plugins": [
        [
          "component",
          {
            "libraryName": "element-ui",
            "styleLibraryName": "theme-chalk"
          }
        ],
        "@babel/plugin-transform-runtime"
     ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  • 执行顺序:

    plugins会从前到后执行,presets会从后向前执行,且plugins会比presets先执行

    presets逆序执行的目的是为了保证向后兼容,我们在配置的时候按照规范的时间顺序列出即可,如['es2015', 'stage-1'],这样就会先使用stage-1,否则就会出现错误。

  • 短名称:在配置plugin和preset名字的时候,可以省略名字中的babel-plugin-preset-字样,仅使用插件的名字就可以了。

    {
      "plugins": [
        "myPlugin", // 完整名字:"babel-plugin-myPlugin"
        "@org/myPlugin" // 完整名字:"@org/babel-plugin-myPlugin"
      ],
      "presets":[
        "myPreset", // 完整名字:"babel-preset-myPreset"
        "@babel/myPreset" // 完整名字:@babel/preset-myPreset
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# plugin

babel的转换完全依靠插件,babel拥有的能力完全取决于给它配置了哪些插件。

babel的插件分为两种:

  • 语法插件:赋予babel解析特定语法的能力,可以理解一个为babel服务的翻译官,仅做翻译解析工作,工作在AST转换阶段。

  • 转译插件:转译插件即把特定的语法转换成指定标准的语法。

    比如将箭头函数 (x) => x 转换成 function (x) {return x},只需要配置箭头函数转译插件即可。

插件的使用大概分为两步:

  1. 将插件添加到配置文件的plugins属性中,可根据插件文档做相应配置。
  2. 安装插件到项目中,使用npm或者yarn均可

每个插件的功能是单一的,所以一般转换需求要使用的插件数量比较多,这时候可以考虑presets

# 编写插件

和webpack一样,bable可以使用自定义插件实现功能

module.exports = function() {
  return {
    visitor: {
      Identifier(path) {
        const name = path.node.name;
        // reverse the name: JavaScript -> tpircSavaJ
        path.node.name = name
          .split("")
          .reverse()
          .join("");
      },
    },
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# preset

在实际的开发中,我们如果用ES6来进行开发的话,那需要转换的语法是非常多的,难道要一个一个去配置吗?还要不要配陪妹子了?

这个时候,就该presets登场了。presets可以理解为一组插件的集合,就像你去德克士直接点一个全家桶,而不必一个一个给服务员说要鸡腿、鸡翅、薯条...(如果想和服务员小姐姐多待一会,也不是不可以)

  • 配置文件:通过配置文件的targets属性指定目标环境

    {
      "presets": [
        ["env", {
          "targets": {
            "browsers": ["last 2 versions", "safari >= 7"],
            "node": "12.10"
          }
        }]
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    browserslistrc:如果是浏览器或者Electron,官方推荐使用.browserslistrc

    文件来指定目标环境(可以和autoprefixer、stylelint等其他工具共享配置)。

    不过如果在配置文件中设置了 targetsignoreBrowserslistConfig.browserslistrc中配置的内容将不会生效。

presets中的其他配置:

  • modules: 用于指定转换后的模块规范,可设置为"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认为"auto"

  • useBuiltIns:此选项配置如何@babel/preset-env处理polyfill,在后面介绍polyfill我们会详细介绍。

  • corejs:用于指定corejs的版本,仅当使用了useBuiltIns: usage或者

    useBuiltIns: entry此配置才有效,使用时一定要配置正确的版本,否则会报错。

    当前默认的版本是2.0,不过建议使用3.0版本,因为2.0已经不会再添加新的特性了,如果使用了一些较新的语法,将会无法转换。

@babel/preset-env

这个是我们最常用的,也是官方现在推荐的preset,它是一个智能预设,我们无需再关心特定环境下所需的转换插件(只需要指定目标环境),就可以使用最新的语法。除了能够减少使用难度,还能够使转换出来的代码体积更小。

preset-env会根据配置的目标环境中缺少的功能,选择对应的插件进行必要的转换和polyfill,而目标环境支持的特性就不会转换,而不是无脑的一把梭哈。

# polyfill

前面我们的配置其实是不完美的,转换出来的代码直接在浏览器运行是可能会出问题的。这是因为babel默认只转换了语法,而对于一些新的API是并没有处理的,如Generator、Set、Maps、Proxy、Promise 等全局对象及其方法都不会转码,如果我们在代码中使用到了就会报错。

此时,我们就需要做Polyfill,也就是所谓的'垫片',作用就是把浏览器的差异垫平,人话就是通过模拟为这些浏览器补全缺失的全局对象及API。

# 常用插件

@babel/plugin-transform-runtime 是一个可重复使用Babel注入的帮助程序的插件,避免重复注入,以节省代码大小。通常还要搭配@babel/runtime一起使用。

安装

npm install --save-dev @babel/plugin-transform-runtime 
npm install --save @babel/runtime # 提供缺失的特性,须要在生产环境中安装
1
2

配置

// babel.config.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ],
  "plugins": [["@babel/plugin-transform-runtime"]]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Last Updated: 11/7/2021, 2:17:28 PM