跳到主要内容

babel使用

babel 是什么

babel就是针对 JavaScript 语言的编译器,它主要负责将ES6+的源代码转换成版本向后兼容不同浏览器的 JS 代码。

plugins

plugins

babel目前的转换功能全部依赖于plugins,如果未使用plugins,那么babel默认不会对代码进行任何处理。例如@babel/plugin-transform-arrow-functions这个插件是将箭头函数转换成普通函数。

plugins的使用具有先后顺序之分,

  • pluginspresets之前执行

  • plugins按照数组配置顺序来执行

  • 但是presets的执行顺序是相反的,按数组配置顺序从最后一个往前执行

  • 最后,plugins支持自定义编写并发布到npm,然后配置使用

presets

presets就是一系列plugins的集合,避免重复配置plugins带来的繁琐开发任务,同时也降低了 TC39 新提案更新以后增加语法特性时维护的难度。

babel团队提供以下四个preset

babel还为那些尚未发布的 TC39(Technical Committee No.39,技术委员会)提案开发了preset。TC39(Technical Committee No.39)是推动 JS 现代化发展最重要的成员(没有之一),它是由各个主流浏览器厂商的代表构成的一个组织,负责提案并起草 ECMAScript 标准。JS 对应的在 ECMA(European Computer Manufacturers Association,欧洲计算机制造联合会)中的标准就是 ECMA-262。

TC39 会定期在 GitHub 更新不同状态提案的进度。

  • Stage 0:想法阶段,可能有部分plugin实现
  • Stage 1:表示 TC39 已经有部分成员开始推动该提案往下发展,
  • Stage 2:表示提案草稿已完成
  • Stage 3:表示提案草稿已敲定,坐等浏览器实现
  • Stage 4:表示下一版本的 ES 规范将正式宣读这一提案,届时只需要polyfill支持不兼容的浏览器即可

polyfill

babel 7.4以前,babel提供@babel/polyfill模块用于为代码降级提供更多的兼容性补丁。其内部包含core-js以及运行时generator补丁,这样在一些老破旧的浏览器(IE)上就可以使用Promisegenerator functionES6+的语法。

但是如果直接使用@babel/polyfill有两个缺陷:

  • 污染全局作用域,使用的ArrayPromiseString等构造函数的原型会被core-js修改;
  • 单独使用@babel/polyfill会将core-js全量导入,造成项目打包体积过大

不过@babel/preset-env提供了一个useBuiltIns属性,将其值设置为usage就只会按需打包,即对使用到的高版本语法才进行转换打包。

babel 7.4以后,polyfill就被废弃了,使用的方式变成在代码需要用到polyfill的地方直接引入core-jsregenerator-runtime/runtime即可

import 'core-js/stable';
import 'regenerator-runtime/runtime';

babel 用法

单独使用babel编译程序需要安装以下 package

yarn add @babel/core @babel/cli @babel/preset-env @babel/polyfill

配置方式

babel支持多种配置方式,不同配置文件其作用范围也不同

  • 项目全局级别的配置,以babel.config.xx开头,后缀可以是.js, .cjs, .mjsjson
  • 文件级别的配置,.babelrc.xxx配置文件,后缀可以是.js, .cjs, .mjsjson,或者直接叫.babelrc;也可以直接在package.json下写入babel字段

这里比较通用且常用的还是babel.config.js

module.exports = function(api) {
return {};
};

这里的参数api还提供以下调用项可以在函数内部使用。

api.cache

api.cachebabel内置用来缓存加载配置项的函数的执行结果,这样可以避免每次在使用babel编译的时候都要重新调用babel.config.js来获取配置项,也就是可以提高编译速度。

这个配置项必须在使用babel的时候指定,旨在提高babel性能,如果不指定可能会有报错提示。

  • api.cache.forever()api.cache(true):永久缓存加载配置项的结果
  • api.cache.never()api.cache(false):不使用缓存
  • api.cache.using(() => process.env.NODE_ENV)
  • api.cache.invalidate(() => process.env.NODE_ENV)根据执行环境来设置是否重新加载配置项

api.env(...)

这个函数用来根据process.env.NODE_ENV判断当前执行环境

  • api.env():直接使用会返回process.env.NODE_ENV

  • api.env("production"):当process.env.NODE_ENV === "production"的时候返回true

  • api.env(["development", "test"]):当["development", "test"]包含process.env.NODE_ENV的时候返回true

  • api.env(envName => envName.startsWith("test-")):传入一个函数,其会接收process.env.NODE_ENV作为参数

在项目根目录新建babel的配置文件babel.config.js

module.exports = function(api) {
api.cache(true);

return {
presets: [
[
'@babel/env',
{
targets: {
edge: '17',
firefox: '60',
chrome: '67',
safari: '11.1',
},
useBuiltIns: 'usage',
corejs: '3.6.4',
},
],
],
};
};

执行以下命令,开始运行babel,这将会把项目src目录下的代码编译输出到lib目录

./node_modules/.bin/babel src --out-dir lib

babel 7开始,babel下的所有 package 都在@babel命名空间下,支持以模块化的方式去导入然后使用

配置项

plugins | Array

一系列plugins的配置数组,plugins会按照数组索引顺序执行。同时也支持嵌套数组的插件配置形式:

plugins: [
'jsx',
[
'flow',
{
...options,
},
],
];

如果plugin的 package 的name属性前缀是babel-plugin-,那么在配置使用这个plugin的时候可以简写其名称,例如下面两种配置是一样的效果。

"plugins": [
"myPlugin",
"babel-plugin-myPlugin"
]

presets | Array

一系列presets的配置数组,presets会按照数组逆向顺序执行

{
"presets": [
[
"@babel/preset-env",
{
"loose": true,
"modules": false
}
]
]
}

模块

sourceType

sourceType用于指定babel解析 JS 模块的类型,其支持以下三种配置值:

  • "script":表示babel会根据远古 JS 文件模块的规则解析源代码的模块,例如不适用“use strict”模式。不允许在 JS 代码中出现 ES 模块的语法 —— importexport
  • “module”:表示babel会根据最新的 ES Modules 的语法解析 JS 模块,自动使用“use strict”模式,允许使用importexport
  • “unambiguous”:视情况而定,如果源代码文件中包含importexport则使用 ES Modules 的语法解析。使用“unambiguous”看起来最方便,但是如果在文件中不包含任何importexport,那么此时可能会出现解析错误的情况。

这个配置项不仅影响babel解析(Parse)源代码文件的过程,也同时影响babel对源代码进行转换(Transform)的过程,例如 @babel/plugin-transform-runtime 需要判断是否在转换源代码的时候对指定的模块导入语法进行转换,将其替换成import或者require

@babel/preset-env同样需要根据sourceType,并将其应用于useBuiltIns配置项。

环境配置项

env

{ [envKey: string]: Options }

这是一个键值对的配置项,其中键与babel的环境变量名对应,其属性值可以是一系列对应环境下的配置,配置项会和顶层的配置项合并。

环境变量名可以由envName指定,或者当使用babel.config.js配置文件时,通过api.anv(x)来指定。

(module.exports = function(api) {
api.env(process.env.NODE_ENV);

return {
"env": {
"dev": {
"presets": ["es2015"],
"plugins": ["x"]
},
"test": {
"presets": ["es2015"]
}
}
};
})

targets

根据Browserslist指定经过babel编译以后需要兼容的浏览器环境。

注意:如果未指定该配置项,babel默认就会尽可能得兼容旧浏览器,例如@babel/preset-env将会转换所有 ES6+ 得代码到 ES5 的版本,这将导致编译过程非常慢,并且代码打包体积也非常大。

  • targets.esmodules:指定babel编译生成支持 ES Modules 的代码
  • targets.node:指定babel编译的代码支持的nodejs环境
  • targets.safari:指定支持safari版本
  • targets.browsers:指定browserlist的浏览器版本,其结果会被直接在targets中指定的覆盖掉
{
"targets": {
"chrome": "58",
"ie": "11"
}
}

文件匹配模式

test | RegExp

指定一个正则表达式来匹配文件名或者文件夹,如果没有匹配上,则当前配置项会被忽略掉。

include | RegExp

这个配置项和test作用一样。

exclude | RegExp

指定要babel忽略的文件名或者文件夹正则表达式。

ignore | RegExp

指定要babel忽略的文件名或者文件夹正则表达式。 例如下面的ignore会忽略相对于babel.config.js相对的./lib文件夹。

ignore: ['./lib'];

only | RegExp

这个配置项和ignore相对,表示指定babel只针对具体的文件名或者文件夹进行编译。

配置项覆盖

extends | string

extends也就是继承的意思,指定当前配置项继承自某一配置文件名,然后当前配置项会和额外的配置文件的配置项合并。

overrides | Array

override也就是重载的意思,表示提供一个数组的配置项用于覆盖当前配置,这种情况常用于覆盖preset的配置。

SourceMap

source map 本质上就是一个以.map为后缀名的 JSON 文件,里面写入了一些源文件和压缩文件的映射关系属性,例如

{
"version": 3, //版本
"file": "script.js.map", //source map文件名
"sources": [
//源文件名
"app.js",
"content.js",
"widget.js"
],
"sourceRoot": "/", //源文件路径
"names": ["slideUp", "slideDown", "save"], //包含源文件中所有变量和函数名称的数组
"mappings": "AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO..." //
}

根据 source map 文件,在压缩后的代码文件底部通过一个注释字段sourceMappingURL写入 source map 文件的路径,告知浏览器我这个压缩文件有一个源代码文件映射可以用,这样就可以在直接在浏览器里打断点调试源文件,例如:

//# sourceMappingURL=/path/to/script.js.map

sourceMaps

boolean | "inline" | "both",默认是false

  • true表示使用babel为编译代码生成 sourcemap 文件,并写入源代码和编译代码的映射关系
  • “inline”:使用babel生成 sourcemap,但是会将其转换成 Base64 格式的 Data URL 然后追加到编译代码的下方,但是并不会写入源代码和编译代码的映射关系
  • "both":和“inline”类似,但是会写入源代码和编译代码的映射关系

sourceRoot

指定生成.map文件的根目录

sourceFileName

指定.map文件内使用的 sourcemap 文件的名称。