Babel 是一个 JavaScript 编译器,是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
在这里会介绍如何将用es2015+写的JavaScript代码转换为能在当前浏览器正常执行的代码。包括两方面:语法转换、功能补充(这里暂时叫这个名字,之后会详细介绍)。
1、安装这些必要的包
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
2、在根目录创建一个babel.config.js的配置文件
const presets = [
[
"@babel/env",
{
targets: { //targets:表示编译出的代码想要支持的浏览器版本
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: "usage",//useBuiltIns:下面会详细解释
},
],
];
module.exports = { presets };
3、执行命令
./node_modules/.bin/babel src --out-dir lib
然后你用es2015+编写的代码就被转化为能在目标浏览器正常运行的代码了。
--out-dir
代表输出到哪个目录下,你可试试--help
看其他的用法,如果在这里我们没有配置babel.config.js,我们可以通过–plugins 或者 --presets告诉代码应该编译成什么样子。
所有用到的babel包都是被单独发布在@babel作用域下(v7开始),比如@babel/preset-env、@babel/core、 @babel/cli,因为babel是插拔式的,所以用到什么包就安装什么包,每个包各司其职。
它是Babel最核心的包,在使用其它包之前,必须要先安装@babel/core,它的主要作用就是编译。
npm install --save-dev @babel/core
然后你可以在代码里直接使用:
const babel = require("@babel/core");
babel.transform("code", optionsObject);
这里的optionsObject就和之前的babel.config.js是一样的,如何编译代码,编译成什么样子什么标准用什么东西都在这里配置。
它的作用是可以在命令行里直接使用babel。
npm install --save-dev @babel/core @babel/cli
./node_modules/.bin/babel src --out-dir lib
这句话会编译你src目录下的所有js代码,并编译成你想要的那样(babel.config.js配置的),并输出到lib目录下。 如果我们没有配置babel.config.js,那么执行这句话之后src会被原封不动的搬到lib下(格式除外)。
plugins顾名思义就是插件,一个小型的js代码程序告诉Babel,该如何转换你的源码,你可以自己写plugins也可以在github上使用别人写好的。来看下面两个插件:
这个插件的作用就是将es2015的箭头函数转换成普通函数:
npm install --save-dev @babel/plugin-transform-arrow-functions
./node_modules/.bin/babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions
使用这个插件后,会将箭头函数转换成如下:
const fn = () => 1;
// converted to
var fn = function fn() {
return 1;
};
这个插件的主要功能是避免多次编译出helper函数。
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
Babel转移后的代码想要实现和原来代码一样的功能需要借助一些帮助函数,比如:
class Person {}
会被转换为:
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person() {
_classCallCheck(this, Person);
};
这里_classCallCheck
就是一个helper函数,试想下如果我们很多文件里都声明了类,那么就会产生很多个这样的helper函数,积少成多增大了代码体积。
这里的@babel/runtime
包就声明了所有需要用到的帮助函数,而@babel/plugin-transform-runtime
的作用就是将所有需要helper函数的文件,依赖@babel/runtime
包:
"use strict";
var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck");
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
var Person = function Person() {
(0, _classCallCheck3.default)(this, Person);
};
这里就没有再编译出helper函数classCallCheck了,而是直接引用了@babel/runtime
中的helpers/classCallCheck。
当然,es2015有这么多新的语法,我们不可能一一的去引用每个plugins来编译我们的代码吧,于是就又了presets,顾名思义就是预设,它包含了一组我们需要的plugins。就像plugins一样,你也可以编写一组你最需要的plugins成为一个preset。目前这里有一个非常优秀的preset叫env,@babel/preset-env
。
preset-env
是 JS 中的 autoprefixer
根据环境来应用不同的plugins。支持的plugins超过babel-preset-latest(2015-2017)。
npm install --save-dev @babel/preset-env
./node_modules/.bin/babel src --out-dir lib --presets=@babel/env
不需要任何配置,这个preset包含了所有现代js(es2015 es2016等)的所有新特性。
也可以传递一些配置给env,精准实现你想要的编译效果。
{
"presets": [
["env",{
"targets": { // 目标环境
"browsers": [ // 浏览器
"last 2 versions",
"ie >= 10"
],
"node": "current" // node
},
"modules": true, // 是否转译module syntax,默认是 commonjs
"debug": true, // 是否输出启用的plugins列表
"spec": false, // 是否允许more spec compliant,但可能转译出的代码更慢
"loose": false, // 是否允许生成更简单es5的代码,但可能不那么完全符合ES6语义
"useBuiltIns": false, // 怎么运用 polyfill
"include": [], // 总是启用的 plugins
"exclude": [], // 强制不启用的 plugins
"forceAllTransforms": false, // 强制使用所有的plugins,用于只能支持ES5的uglify可以正确压缩代码
}
]
],
}
这里主要需要注意的是useBuiltIns用于指定怎么处理polyfill,可以选值"usage" | "entry" | false
,默认是false
。
useBuiltIns: 'usage'
:它会自动检测语法帮你require你代码中使用到的功能。也就是当每个文件里用到(需要polyfill的特性)时,在文件中添加特定的import语句。这可以保证每个polyfill的特性仅load一次。useBuiltIns: 'entry'
:替换import "@babel/polyfill"
语句为独立的(根据环境)需要引入的polyfill特性的import语句。
useBuiltIns: false
:对@babel/polyfill
不做任何处理。Ba从Babel 7.4.0开始,不推荐使用此软件包。
中文翻译是垫片,之前没有详细了解babel之前,我也很迷茫这个polyfill是啥,因为语法不都给你转换好了,还需要这个东西干啥,后来仔细想了一下,要适应新特性应该从两方面入手:
1、语法转换:
() => {};
for (let i of items) {};
比如箭头函数、for…of,在不支持这些语法的环境下,直接会报语法错误,因为编译器根本不知道 =>
这些是什么鬼符号,要做到让编译器识别,那就要将这样的语法转换成浏览器能识别的代码,那么就需要语法转换。
2、功能补充
'foo'.includes('f');
es2015里不仅只有新的语法,还有实例的扩展,比如String,其实这里只是调用了String实例的一个方法,我们无论怎么语法转换也没有什么用吧,如果我们在不支持String.prototype.includes的编译器里跑这些代码,会得到 ‘foo’.includes is not a function. 这样的一个报错,而不是语法报错。
Polyfill提供的就是一个这样功能的补充,实现了Array、Object等上的新方法,实现了Promise、Symbol这样的新Class等。
然后这里回到我们最开始安装包那里:
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
仔细看我们安装core cli env都是 --save-dev,这是因为我们发布的代码都是编译好的代码,这些都只是开发依赖,发布的代码不需要依赖这些包。而安装 polyfill 没有-dev,因为就算代码发布后,编译后的代码依然会依赖这些新特性来实现功能。
虽然@babel/polyfill提供了我们想要的所有新方法新类,但是这里依然存在一些问题:
@babel/polyfill
,那么像Promise这样的新类就是挂载在全局上的,这样就会污染了全局命名空间。可能在一个团队建立的项目问题不太大,但是如果你是一个工具的开发者,你把全局环境污染了,别人用你的工具,就有可能把别人给坑了。一个解决方案就是引入transform runtime
来替代 @babel/polyfill
。
幸运的是,我们有env这个preset,它又一个useBuiltIns选项,如果设置成"usage"
,那么将会自动检测语法帮你require你代码中使用到的功能。
const presets = [
[
"@babel/env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: "usage",
},
],
];
module.exports = { presets };
比如我在代码中:
Promise.resolve().finally();
如果在edge17不支持这个特性的环境里运行,将会帮你编译成:
require("core-js/modules/es.promise.finally");
Promise.resolve().finally();
配置文件里,以.babelrc
举例:
无选项配置:
{
"plugins": ["@babel/plugin-transform-runtime"]
}
有选项配置(默认值):
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
这个插件的默认配置默认用户已经提供了所有polyfillable APIs,因此想要无缝使用不污染全局环境,需要特别标明corejs。
属性说明:
corejs
:默认false,或者数字:{ corejs: 2 }
,代表需要使用corejs的版本。helpers
:默认是true,是否替换helpers。polyfill
:v7无该属性regenerator
:默认true,generator是否被转译成用regenerator runtime包装不污染全局作用域的代码。useESModules
:默认false,如果是true将不会用@babel/plugin-transform-modules-commonjs
进行转译,这样会减小打包体积,因为不需要保持语义。