2022-10-7 About 10 min

从上节可以看到,如果没有配置,Babel 默认是不会开启对某些语法的语法支持和转译支持的。那么,我们在日常开发时难免会用到各式各样的高级语法,把对应能支持该语法的插件一个个引入是可以的,但太麻烦了。

因此,Babel 提供了 presets,他们作为 plugin 的集合,提供对高级语法的批量支持。

截至本书编写时,Babel 有 4 个内置 preset。

  • babel-preset-env

    内置一系列语法和转换插件。

  • babel-preset-flow

  • babel-preset-react

  • babel-preset-typescript

# 9.1 babel-preset-env

# 9.1.1 基本功能

babel-preset-env 内置了诸多语法插件和转换插件,举例,对于如下的源码:

const source = `
    const a = 1
`;

// 空参数
const optison = {};

const { code } =  babel.transformSync(source, {
    presets: [
        [
            '@babel/preset-env',
            options
        ]
    ]
});

console.log(code)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

执行后的 code 为:

"use strict";

var a = 1;
1
2
3

可以看到,源码被正常转译。

# 9.1.2 辅助工具

babel-preset-env 重度依赖以下第三方工具:

  • browserslist

    browserslist 是一款工具,用于指定要支持的浏览器版本。由于浏览器版本众多、且在快速迭代,开发者很难精确地指定要支持的浏览器版本。

  • caniuse

  • compat-table

  • electron-to-chromium

# 9.1.3 配置项

在此介绍下上例中 babel-preset-env 的配置参数 options

  • targets: string | Array<string> | { [string]: string }

    描述转译的目标环境。该参数会传递给 browserslist 以获取具体的目标浏览器环境版本。

    比如,不同版本的 Chrome 浏览器对 JavaScript 语法的支持是不同的,对某个语法,有的版本能识别,有的不能。

    babel-preset-env 会根据 targets 决定使用哪些插件执行转译。

    写法举例:

    {
        "presets": [
            "@babel/preset-env",
            {
                "targets": "> 0.25%, not dead"
            }
        ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    {
        "presets": [
            "@babel/preset-env",
            {
                "targets": {
                    "chrome": "58",
                    "ie": "11"
                }
            }
        ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    支持的目标环境有:node, chrome, opera, edge, firefox, safari, ie, ios, android, electron, samsung

    如果使用 babel-preset-env 时不设置 "target",默认将会对所有 ES2015~ 最新版本的代码进行转译。而这缺少了针对目标环境的转译能力,Babel 并不推荐。

    不过,babel-preset-env 仍然提供了 "target": "default" 的设置项:TODO

    • targets.esmodules: boolean

      设置 targets.esmodules 属性后,babel-preset-env 会忽略 targets.browsers 属性及 .browserslistrc 文件,将对目标浏览器环境的支持改为支持 es6.module 语法的目标环境。

    • targets.node: string | "current"

      可以具体制定目标 Node 版本,或使用 current(等同于 process.versions.node,即当前的 Node 版本)。

    • targets.safari: string | "tp"

      指定 safari 浏览器的版本,可以为 "tp" 或者自定义版本号。

    • targets.browsers: string | Array<string>

      用 browserslist 工具可识别的 query 语法,描述目标浏览器版本。

  • spec: boolean; 默认 false

    默认是 false。该参数会传递给插件。

  • loose: boolean

    默认是 false。该参数会传递给插件。

  • modules: "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false

    默认是 "auto"

    是否开启把 ES module 语法转换成其它类型的功能。如果该值被设置为 false,则不会将 ES module 转译为其他格式。

    babel-preset-env 源码中针对这些类型引入的转换插件如下:

    auto: "transform-modules-commonjs"
    amd: "transform-modules-amd"
    commonjs: "transform-modules-commonjs"
    cjs: "transform-modules-commonjs"
    systemjs: "transform-modules-systemjs"
    umd: "transform-modules-umd"
    
    1
    2
    3
    4
    5
    6
  • debug: boolean; 默认 false

    是否打印 babel-prese-env 对当前配置所加载的所有插件信息、所有浏览器列表。

  • include: Array<string|RegExp>

    默认是[]

    数组项可以是下面:

    • plugins: 可以是插件的名称(如 @babel/plugin-transform-spread)或简化名称(如 plugin-transform-spread)
    • built-ins

    我们已经知道,设置好目标环境后,如果该环境支持当前语法的话,babel-preset-env 不会加载相应的插件的。但如果用户希望可以强制转译,可以使用 include 参数。

  • exclude: Array<string|RegExp>

    默认 [].

    不需要依赖的插件集合,跟 include 用法一样,但是作用跟 include 相反。

  • useBuiltIns: "usage" | "entry" | false; 默认是 false

    该配置项和 API Polyfill 相关。

    该配置描述了是否引入以及如何全局引入 core-js,它有几种值:

    • "false":不会自动引入 core-js,是默认值

      此时,Babel 并不会自动引入 core-js,并且即使源码有 import "core-js"import "@babel/polyfill",也不会将其转译为适配目标环境的独立的 Polyfill。

    • "entry":需要在源码入口文件中手动引入 core-js,且只能引入一次

      import "core-js";
      
      1

      根据不同的目标环境配置,会转译为不同的代码:

      import "core-js/modules/es.string.pad-start";
      import "core-js/modules/es.string.pad-end";
      
      1
      2
    • "usage":会自动在转译后代码中按需引入 core-js 的子包

      比如:

      var a = new Promise();
      var b = new Map();
      
      1
      2

      对于还不支持 PromiseMap 的目标环境,会转译为:

      import "core-js/modules/es.promise";
      import "core-js/modules/es.map";
      
      var a = new Promise();
      var b = new Map();
      
      1
      2
      3
      4
      5

      对于支持的目标环境,会原样输出:

      var a = new Promise();
      var b = new Map();
      
      1
      2
  • corejs: string | { version: string, proposals: boolean }

    配置 core-js 版本,默认是 2.0。可以设置 core-js 的任意支持的版本,比如 3.8

    useBuiltIns 设置 usage 或者 entry 的时候需要依赖 core-js 的注入,corejs 是设置 core-js 的版本号。

    默认情况下,只有标准化的 ECMAScript 特性才被 core-js 引入代码支持,对于提案中的特性,可以用以下方式提供支持:

    • 如果配置了 useBuiltIns: "entry"

      开发者可手动引入提案特性,如 import "core-js/proposals/string-replace-all"

    • 如果配置了 useBuiltIns: "usage"

      • 可设置 babel-preset-env 的 shippedProposals: true,这样可以开启对提案特性的支持
      • 可设置如 corejs: { version: "3.8", proposals: true }这样的配置开启 core-js@3.8 中支持的所有提案特性
  • forceAllTransforms: boolean; 默认是 false

    是否开启所有转译行为。

    默认为 false 的情况下,babel-preset-env 会根据目标环境对某个特性的支持情况,决定是否执行转译。

    如果 forceAllTransforms: false,目标执行环境支持 ES6中 的 const a = 1写法,则不会转译为 var a = 1

    如果 forceAllTransforms: true,即使目标环境支持上述写法,依然会执行转译。

  • shippedProposals: boolean; 默认是 false

    TODO

  • browserslistEnv: string | undefined; 默认是 undefined

    TODO

  • configPath: string; 默认是 process.cwd()

    告诉 babel-preset-env 从哪里开始寻找 browserslist 的配置文件,一直往上一级递归直到找到配置文件,默认是 process.cwd(),也就是转译的目标目录。

  • ignoreBrowserslistConfig

    是否禁止寻找 browserslist 配置文件,默认是 false

# 9.1.4 内置插件

babel-preset-env 内置了众多语法和转换插件,通过 package.json 中声明的依赖,并与 babel/packages/babel-preset-env/src/available-plugins.js 中定义的插件列表对照,可以判断 babel-preset-env 内置了哪些插件。不过需要注意的是,插件是否最终用于转译是需要根据目标环境确定的。

  • 内置插件列表

    笔者整理了内置插件名称及其功能的列表.

    • 语法开关

      • babel-plugin-syntax-async-generators
      • babel-plugin-syntax-class-properties
      • babel-plugin-syntax-dynamic-import
      • babel-plugin-syntax-export-namespace-from
      • babel-plugin-syntax-json-strings
      • babel-plugin-syntax-logical-assignment-operators
      • babel-plugin-syntax-nullish-coalescing-operator
      • babel-plugin-syntax-numeric-separator
      • babel-plugin-syntax-object-rest-spread
      • babel-plugin-syntax-optional-catch-binding
      • babel-plugin-syntax-optional-chaining
      • babel-plugin-syntax-top-level-await
    • 标准特性转换插件

      • babel-plugin-transform-arrow-functions
      • babel-plugin-transform-async-to-generator
      • babel-plugin-transform-block-scoped-functions
      • babel-plugin-transform-block-scoping
      • babel-plugin-transform-classes
      • babel-plugin-transform-computed-properties
      • babel-plugin-transform-destructuring
      • babel-plugin-transform-dotall-regex
      • babel-plugin-transform-duplicate-keys
      • babel-plugin-transform-exponentiation-operator
      • babel-plugin-transform-for-of
      • babel-plugin-transform-function-name
      • babel-plugin-transform-literals
      • babel-plugin-transform-member-expression-literals
      • babel-plugin-transform-modules-amd
      • babel-plugin-transform-modules-commonjs
      • babel-plugin-transform-modules-systemjs
      • babel-plugin-transform-modules-umd
      • babel-plugin-transform-named-capturing-groups-regex
      • babel-plugin-transform-new-target
      • babel-plugin-transform-object-super
      • babel-plugin-transform-parameters
      • babel-plugin-transform-property-literals
      • babel-plugin-transform-regenerator: workspace:^8.13.15,
      • babel-plugin-transform-reserved-words
      • babel-plugin-transform-shorthand-properties
      • babel-plugin-transform-spread
      • babel-plugin-transform-sticky-regex
      • babel-plugin-transform-template-literals
      • babel-plugin-transform-typeof-symbol
      • babel-plugin-transform-unicode-escapes
      • babel-plugin-transform-unicode-regex
    • 提案特性转换插件

      • babel-plugin-proposal-async-generator-functions
      • babel-plugin-proposal-class-properties
      • babel-plugin-proposal-dynamic-import
      • babel-plugin-proposal-export-namespace-from
      • babel-plugin-proposal-json-strings
      • babel-plugin-proposal-logical-assignment-operators
      • babel-plugin-proposal-nullish-coalescing-operator
      • babel-plugin-proposal-numeric-separator
      • babel-plugin-proposal-object-rest-spread
      • babel-plugin-proposal-optional-catch-binding
      • babel-plugin-proposal-optional-chaining
      • babel-plugin-proposal-private-methods
      • babel-plugin-proposal-unicode-property-regex
  • 目标环境与引入插件的关系

    这里需要解决一个问题,如果 babel-preset-env 设置的 targets: ">0.25% not dead",babel-preset-env 需要引入哪些插件进行转译呢?

    Babel-presets/env-plugins-获取过程

    babel-preset-env 最终对外产出的是插件列表数组,即 export default { plugins },在产出插件列表的过程中,它做了以下操作:

    • 获取最终的 browserslist 目标环境配置

      根据如下配置项:targets / ignoreBrowserslistConfig / configPath / browserslistEnv,内部通过对这些配置项的解析,得到最终的目标环境描述,比如:targets: ">0.25% not dead"

    • 解析目标配置描述

      如果配置的目标环境:targets: ">0.25% not dead",解析出的目标环境版本信息:

      {
          chrome: '83.0.0',
          edge: '18.0.0',
          firefox: '81.0.0',
          ie: '11.0.0',
          ios: '9.3.0',
          opera: '71.0.0',
          safari: '13.1.0',
          samsung: '12.0.0'
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      babel-preset-env 依赖工具 babel/packages/babel-helper-compilation-targets 提供的 getTargets 方法执行上述解析,getTargets 方法内部的实现逻辑如下:

      • 利用 browserslist 提供的 API,获取初步结果

        const result = browserslist(">0.25% not dead", { mobileToDesktop: true })
        
        1

        结果示例:

        [
            'and_chr 86',   'and_uc 12.12',
            'chrome 86',    'chrome 85',
            'chrome 84',    'chrome 83',
            'edge 86',      'edge 85',
            'edge 18',      'firefox 82',
            'firefox 81',   'ie 11',
            'ios_saf 14',   'ios_saf 13.4-13.7',
            'ios_saf 13.3', 'ios_saf 12.2-12.4',
            'ios_saf 10.3', 'ios_saf 9.3',
            'op_mini all',  'opera 71',
            'safari 14',    'safari 13.1',
            'samsung 12.0'
        ]
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
      • 解析上述结果 result,获取每个环境的最低版本信息

        内部实现了 getLowestVersions 方法进行解析,同时使用语义化版本对比工具 semver 进行版本对比,获取每个目标环境的最低版本信息。

    • 获取每个插件可支持的目标环境版本信息

      文件 babel/packages/babel-compat-data/data/plugins.json 中描述了每个插件支持的目标环境,如:

      "proposal-class-properties": {
          "chrome": "74",
          "opera": "62",
          "edge": "79",
          "safari": "14.1",
          "node": "12",
          "samsung": "11",
          "electron": "6.0"
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      babel-plugin-proposal-class-properties 目标转译环境的要求是:

      chrome >= 74.0.0
      opera >= 62.0.0
      edge >= 79.0.0
      safari >= 14.1.0
      node >= 12.0.0
      samsung >= 11.0.0
      electron >= 6.0.0
      
      1
      2
      3
      4
      5
      6
      7
    • 组合最终的插件列表

      有了目标环境的版本信息,也有了每个插件可支持的目标环境版本信息,二者组合即可得到某个目标环境对应有哪些插件了。

      babel-preset-env 内部使用方法 filterItems

# 9.2 babel-preset-flow

babel-preset-flow 的主要文件结构:

src/
    index.js // 主逻辑
    normalize-options.js // 格式化配置项
1
2
3

index.js的内容:

import { declare } from "@babel/helper-plugin-utils";
import transformFlowStripTypes from "@babel/plugin-transform-flow-strip-types";
import normalizeOptions from "./normalize-options";

export default declare((api, opts) => {
  api.assertVersion(7);
  const { all, allowDeclareFields } = normalizeOptions(opts);

  return {
    plugins: [[transformFlowStripTypes, { all, allowDeclareFields }]],
  };
});
1
2
3
4
5
6
7
8
9
10
11
12

可以看到,其对外提供的插件列表就一项:babel-plugin-transform-flow-strip-types

# 9.3 babel-preset-react

babel-preset-react的主要文件结构:

src/
    index.js // 主逻辑
    normalize-options.js // 格式化配置项
1
2
3

index.js 的内容:

import { declare } from "@babel/helper-plugin-utils";
import transformReactJSX from "@babel/plugin-transform-react-jsx";
import transformReactJSXDevelopment from "@babel/plugin-transform-react-jsx-development";
import transformReactDisplayName from "@babel/plugin-transform-react-display-name";
import transformReactPure from "@babel/plugin-transform-react-pure-annotations";
import normalizeOptions from "./normalize-options";

export default declare((api, opts) => {
  api.assertVersion(7);

  // 格式化配置项
  const {
    development,
    importSource,
    pragma,
    pragmaFrag,
    pure,
    runtime,
    throwIfNamespace,
  } = normalizeOptions(opts);

  return {
    plugins: [
      [
        development ? transformReactJSXDevelopment : transformReactJSX,
        process.env.BABEL_8_BREAKING
          ? {
              importSource,
              pragma,
              pragmaFrag,
              runtime,
              throwIfNamespace,
              pure,
            }
          : {
              importSource,
              pragma,
              pragmaFrag,
              runtime,
              throwIfNamespace,
              pure,
              useBuiltIns: !!opts.useBuiltIns,
              useSpread: opts.useSpread,
            },
      ],
      transformReactDisplayName,
      pure !== false && transformReactPure,
    ].filter(Boolean),
  };
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

可以看出,其内置的插件包括:

  • babel-plugin-transform-react-jsx
  • babel-plugin-transform-react-jsx-development
  • babel-plugin-transform-react-display-name
  • babel-plugin-transform-react-pure-annotations

# 9.4 babel-preset-typescript

babel-preset-typescript 的主要文件结构:

src/
    index.js // 主逻辑
    normalize-options.js // 格式化配置项
1
2
3

index.js 的内容:

import { declare } from "@babel/helper-plugin-utils";
import transformTypeScript from "@babel/plugin-transform-typescript";
import normalizeOptions from "./normalize-options";

export default declare((api, opts) => {
  api.assertVersion(7);

  // 格式化配置项
  const {
    allExtensions,
    allowNamespaces,
    isTSX,
    jsxPragma,
    jsxPragmaFrag,
    onlyRemoveTypeImports,
  } = normalizeOptions(opts);

  const pluginOptions = process.env.BABEL_8_BREAKING
    ? isTSX => ({
        allowNamespaces,
        isTSX,
        jsxPragma,
        jsxPragmaFrag,
        onlyRemoveTypeImports,
      })
    : isTSX => ({
        allowDeclareFields: opts.allowDeclareFields,
        allowNamespaces,
        isTSX,
        jsxPragma,
        jsxPragmaFrag,
        onlyRemoveTypeImports,
      });

  return {
    overrides: allExtensions
      ? [
          {
            plugins: [[transformTypeScript, pluginOptions(isTSX)]],
          },
        ]
      : [
          {
            // Only set 'test' if explicitly requested, since it requires that
            // Babel is being called`
            test: /\.ts$/,
            plugins: [[transformTypeScript, pluginOptions(false)]],
          },
          {
            // Only set 'test' if explicitly requested, since it requires that
            // Babel is being called`
            test: /\.tsx$/,
            plugins: [[transformTypeScript, pluginOptions(true)]],
          },
        ],
  };
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

其内置的插件只有一个:babel-plugin-transform-typescript

Last update: October 7, 2022 19:03
Contributors: hoperyy