从上节可以看到,如果没有配置,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)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
执行后的 code
为:
"use strict";
var a = 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"
的设置项:TODOtargets.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
6debug: 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对于还不支持
Promise
、Map
的目标环境,会转译为: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 中支持的所有提案特性
- 可设置 babel-preset-env 的
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-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
10babel-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
9babel-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 // 格式化配置项
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 }]],
};
});
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 // 格式化配置项
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),
};
});
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 // 格式化配置项
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)]],
},
],
};
});
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
。