Babel是一个编译器可以让你使用最新版本的ES规范比如ES2015(ES6),ES2016(ES7),ES2017(ES8)的写法并把它编译成老的ES5的写法
文章中凡是以@babel开头的都是指的Babel 7.x的否则是Babel 6.x,@的意思可参考【npm 官方](docs.npmjs.com/cli/v7/usin… @babel 版本的才会有的参数。
首先创建项目(这里使用babel7)
npm init -y
安装babel-cli
npm i -D @babel/cli @babel/core @babel/preset-env
创建src文件夹,并在src文件夹内创建文件index.js, 创建dist文件
package.json文件加入命令
"scripts": {
"babel": "babel ./src/index.js --out-file ./dist/build.js"
}
@babel/preset-env
index.js写入代码
const arr = [1,2,3,4,5]
arr.forEach((item) => console.log(item))
arr.reduce((accumulator, item) => {
return item + accumulator
}, 10)
class a {
constructor(name, age) {
this.name = name
this.age = age
}
getAge() {
const { age } = this
console.log(age)
}
}
const mp = new Map()
mp.set(arr, 'array')
mp.get(arr)
运行npm run babel后,查看dist的build文件,如下
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var arr = [1, 2, 3, 4, 5];
arr.forEach(function (item) {
return console.log(item);
});
arr.reduce(function (accumulator, item) {
return item + accumulator;
}, 10);
var a = /*#__PURE__*/function () {
function a(name, age) {
_classCallCheck(this, a);
this.name = name;
this.age = age;
}
_createClass(a, [{
key: "getAge",
value: function getAge() {
var age = this.age;
console.log(age);
}
}]);
return a;
}();
var mp = new Map();
mp.set(arr, 'array');
mp.get(arr);
可以看到@babel/preset-env将箭头函数,class声明转生了es5的语法形式,只是进行了语法转换
@bable/polyfill
如果要浏览器兼容新的原生方法,比如Map,Promise,Array.reduce,需要用到@babel/polyfill
npm i @babel/polyfill -S
注意:@bable/polyfill(内部集成了 core-js 和 regenerator)
babel 对一些新的 API 是无法转换,比如 Generator、Set、Proxy、Promise 等全局对象,以及新增的一些方法:includes、Array.form 等。所以这个时候就需要一些工具来为浏览器做这个兼容。
官网的定义:babel-polyfill 是为了模拟一个完整的 ES6+ 环境,旨在用于应用程序而不是库/工具。
插件
Babel 是一个编译器,在高级层面它分三阶段解析,转换,生成代码。
官方 Presets
如果不想自己设置一堆插件的话,官方有env,react,flow三个 Presets。即预安装了 plugins 的配置。
Stage-X(试验性 Presets、已淘汰)
stage-x presets指的是任何未被包括在官方发布版比如(ES6/ES2015)中的草案。任何 stage-3 之前的都应该要谨慎使用, 一共有以下几个试验性的版本:
- Stage 0 - 草稿:只是一个设想可能是一个 Babel 插件
- Stage 1 - 提案:值得去推进的东西
- Stage 2 - 草案:初始化规范
- Stage 3 - 候选:完整规范和初始化浏览器实现
- Stage 4 - 完成:会加入到下一版中 babel7已经淘汰 es201x,删除 stage-x,推荐 env
转换插件
转换插件具体可查看这里。
语法插件
这种插件可以让 Babel 来解析特殊类型的语法。
如果对应的转换插件已经使用了转换插件会自动使用语法插件。
{
"parserOpts": {
"plugins": ["jsx", "flow"]
}
}
Plugins/Presets 路径
如果插件发布到npm上面则可以使用"plugins": ["babel-plugin-myPlugin"],也可以使用"plugins:": ["./node_modules/pluginPath"]。
Plugins/Preset 快捷方式
如果插件前缀是babel-plugin-则可以使用 "plugins:": ["myPlugin"],presets同理
"presets": ["@org/babel-preset-name"]。作用域包也是如此,"presets": ["@org/babel-preset-name"] 快捷方式为:"presets": ["@org/name"]。
Plugins/Presets 顺序
当这两种插件同时处理代码的时候,将以插件或者preset的顺序来进行转换。
- 插件在presets之前运行
- 插件顺序是从第一到最后
- Preset顺序是相反的从最后到第一 比如:
{
"plugins": [
"transform-decorators-legacy",
"transform-class-properties"
]
}
将会先运行 transform-decorators-legacy后 transform-class-properties
Presets的顺序是相反的:
{
"presets": [
"es2015",
"react",
"stage-2"
]
}
将会以 stage-2,react 和 es2015 的顺序运行。这主要是因为向后兼容性,因为大多数用户把es2015 放在 stage-0 之前。
@babel/runtime 和 @babel/plugin-transform-runtime
在这之前,我们先来打包一下引入@babel/polyfill的包
npm i webapck webapck-cli babel-loader -D
然后在package.json中加入命令
"webpack": "webpack --config webpack.config.js"
配置webpack.config.js如下:
const path = require('path')
module.exports = {
entry:'./src/index.js',
mode: 'development',
output:{
filename: 'build.js',
path: path.resolve(__dirname,'dist')
},
module: {
rules:[{
test: /\.js$/,
use: {
loader: 'babel-loader'
}
}]
}
}
运行npm run webpack后,可以看到,打出来的包体积很大,而我们只是用了reduce, Map这两个es6+的语法;而且polyfill重写了对象原型链上面的方法
Hash: 641ca6e440eb86f317ed
Version: webpack 4.46.0
Time: 2187ms
Asset Size Chunks Chunk Names
build.js 482 KiB main [emitted] main
Entrypoint main = build.js
[./node_modules/_@babel_polyfill@7.12.1@@babel/polyfill/lib/index.js] 694 bytes {main} [built]
[./node_modules/_@babel_polyfill@7.12.1@@babel/polyfill/lib/noConflict.js] 567 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/es6/index.js] 5.91 KiB {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/array/flat-map.js] 108 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/array/includes.js] 109 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/object/entries.js] 109 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/object/get-own-property-descriptors.js] 148 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/object/values.js] 107 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/promise/finally.js] 168 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/string/pad-end.js] 108 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/string/pad-start.js] 112 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/string/trim-end.js] 114 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/fn/string/trim-start.js] 112 bytes {main} [built]
[./node_modules/_core-js@2.6.12@core-js/library/fn/global.js] 87 bytes {main} [built]
[./src/index.js] 1.22 KiB {main} [built]
+ 293 hidden modules
所以我们找出了polyfill的两个缺点
- 使用
@babel/polyfill会导致打出来的包非常大,很多其实没有用到,对资源来说是一种浪费。 @babel/polyfill可能会污染全局变量,给很多类的原型链上都作了修改,这就有不可控的因素存在
使用 @babel/runtime @babel-plugin-tranform-runtime
@babel/runtime 会借助 helper function 来实现特性的兼容,并且利用 @babel/plugin-transform-runtime 插件还能以沙箱垫片的方式防止污染全局, 并抽离公共的 helper function , 以节省代码的冗余
也就是说 @babel/runtime 是一个核心, 一种实现方式, 而 @babel/plugin-transform-runtime 就是一个管家, 负责更好的重复使用 @babel/runtime
@babel/plugin-transform-runtime 插件也有一个 corejs 参数需要填写
注释入口文件的polyfill引入
配置 .bablerc,添加plugins字段
"plugins": [
["@babel/transform-runtime", {
"absoluteRuntime": "@babel/runtime"
}]
安装corejs3 npm i @babel/runtime-corejs3 -S
然后 运行 npm run webpack, 打包结果如下:
Hash: 456b0421200450865ba4
Version: webpack 4.46.0
Time: 1142ms
Asset Size Chunks Chunk Names
build.js 164 KiB main [emitted] main
Entrypoint main = build.js
[./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/core-js-stable/instance/for-each.js] 66 bytes {main} [built]
[./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/core-js-stable/instance/reduce.js] 64 bytes {main} [built]
[./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/core-js-stable/map.js] 52 bytes {main} [built]
[./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/core-js/object/define-property.js] 73 bytes {main} [built]
[./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/helpers/classCallCheck.js] 274 bytes {main} [built]
[./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/helpers/createClass.js] 772 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/es/instance/reduce.js] 250 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/es/map/index.js] 254 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/features/object/define-property.js] 82 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/internals/classof.js] 1000 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/modules/web.dom-collections.iterator.js] 820 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/stable/instance/for-each.js] 530 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/stable/instance/reduce.js] 75 bytes {main} [built]
[./node_modules/_core-js-pure@3.9.1@core-js-pure/stable/map/index.js] 63 bytes {main} [built]
[./src/index.js] 1020 bytes {main} [built]
+ 96 hidden modules
打包体积小了很多。 查看webpack打包后的代码,截取部分如下
eval("module.exports = __webpack_require__(/*! core-js-pure/stable/instance/reduce */ \"./node_modules/_core-js-pure@3.9.1@core-js-pure/stable/instance/reduce.js\");\n\n//# sourceURL=webpack:///./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/core-js-stable/instance/reduce.js?");
eval("module.exports = __webpack_require__(/*! core-js-pure/stable/map */ \"./node_modules/_core-js-pure@3.9.1@core-js-pure/stable/map/index.js\");\n\n//# sourceURL=webpack:///./node_modules/_@babel_runtime-corejs3@7.13.10@@babel/runtime-corejs3/core-js-stable/map.js?");
可以看到使用了core-js-pure来防止污染全局变量。
综上,@babel/plugin-transform-runtime优点有:
- 不会污染全局变量
- 多次使用只会打包一次
- 依赖统一按需引入,无重复引入,无多余引入
babel-preset-env,babel-polyfill 的使用
默认 @babel/preset-env 只会转换语法,也就是我们看到的箭头函数、const一类。
如果进一步需要转换内置对象、实例方法,那就得用polyfill, 这就需要你做一点配置了,
这里有一个至关重要的参数 useBuiltIns,他是控制 @babel/preset-env 使用何种方式帮我们导入 polyfill 的核心, 它有三个值可以选
entry
这是一种入口导入方式, 只要我们在打包配置入口 或者 文件入口写入 import "core-js" or import "@babel/polyfill"(引入 @babel/polyfill 在babel7已经过时), 这样一串代码, babel 就会替我们根据当前你所配置的目标浏览器(browserslist)来引入所需要的polyfill 。
先装包npm i core-js -S
入口文件index.js内import 'core-js',同时注释掉 //import '@babel/polyfill';
babelrc加入 "useBuiltIns": "entry" .babelrc配置如下
{
"presets": [["@babel/preset-env", {
"targets": {
"browsers": [
"last 2 versions"
]
},
"useBuiltIns": "entry",
"corejs": "3"
}]]
}
查看build.js ,可以看到babel把我们填写的 import "core-js"替换掉, 转而导入了一大片的polyfill
useage
useBuiltIns = useage 时,会参考目标浏览器(browserslist) 和 代码中所使用到的特性来按需加入 polyfill,因为会自动加载polyfill,并不需要手动引入polyfill, 所以可以代码文件的import 'core-js'注释掉;
打包后的文件小了很多,如下
"use strict";
require("core-js/modules/es.array.reduce.js");
require("core-js/modules/es.function.name.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var arr = [1, 2, 3, 4, 5];
arr.forEach(function (item) {
return console.log(item);
});
arr.reduce(function (accumulator, item) {
return item + accumulator;
}, 10);
var a = /*#__PURE__*/function () {
function a(name, age) {
_classCallCheck(this, a);
this.name = name;
this.age = age;
}
_createClass(a, [{
key: "getAge",
value: function getAge() {
var age = this.age;
console.log(age);
}
}]);
return a;
}();
var mp = new Map();
mp.set(arr, 'array');
console.log(mp.get(arr));
false
剩下最后一个 useBuiltIns = false , 那就简单了, 这也是默认值, 使用这个值时不引入 polyfill 打包后代码
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
// import '@babel/polyfill';
// import 'core-js'
var arr = [1, 2, 3, 4, 5];
arr.forEach(function (item) {
return console.log(item);
});
arr.reduce(function (accumulator, item) {
return item + accumulator;
}, 10);
var a = /*#__PURE__*/function () {
function a(name, age) {
_classCallCheck(this, a);
this.name = name;
this.age = age;
}
_createClass(a, [{
key: "getAge",
value: function getAge() {
var age = this.age;
console.log(age);
}
}]);
return a;
}();
var mp = new Map();
mp.set(arr, 'array');
console.log(mp.get(arr));
可以看到,polyfill并没有引入
entry 和 @babel/runtime
跟 usage 的情况不一样, entry 模式下, 在经过 @babel/runtime 处理后不但有了各种帮助函数还引入了许多polyfill, 这就会导致打包体积无情的增大;
babel 的 plugin 比 prset 要先执行, 所以preset-env 得到了 @babel/runtime 使用帮助函数包装后的代码,而 useage 又是检测代码使用哪些新特性来判断的, 所以它拿到手的只是一堆 帮助函数, 自然没有效果了。
总结
@babel/preset-env拥有根据useBuiltIns参数的多种polyfill实现,优点是覆盖面比较全, 缺点是会污染全局
- entry 的覆盖面积全, 但是打包体积自然就大,
- usage 可以按需引入 polyfill, 打包体积就小, 但如果打包忽略node_modules 时如果第三方包未转译则会出现兼容问题
@babel/runtime在 babel 7.4 之后大放异彩, 利用 corejs 3 也实现了各种内置对象的支持, 并且依靠@babel/plugin-transform-runtime的能力,沙箱垫片和代码复用, 避免帮助函数重复 inject 过多的问题, 该方式的优点是不会污染全局, 适合在类库开发中使用 上面 1, 2 两种方式取其一即可, 同时使用没有意义, 还可能造成重复的 polyfill 文件