babel 是一个前端的代码转换工具,目的是为了让开发者使用ECMA最新的标准甚至一些在stage阶段的提案功能,而不用过多考虑运行环境的兼容性。javascript
对代码进行如下步骤:html
根据开发者的设定,将高版本的ES语法转化为浏览器或者node环境支持的ES语法前端
固然,babel相关的工具不少,下面咱们以babel7为例,边用边分析。java
若是你想掌握babel的基础配置,请务必跟上一块儿操做node
若是你实在不想写代码,每一步上都会有一个tag,请务必checkout运行一下对应的代码webpack
git clone github.com/jinggk/babe…git
运行git checkout step-1 && yarn run build查看当前步骤的结果es6
由于涉及到打包,我选择了webpack,首先咱们不使用任何babel相关的转码,尝试打包一次看下效果:github
测试代码以下:web
// src/index.js
const fun = async () => {
const data = await Promise.resolve(123);
console.log(data);
};
fun(); // 123
复制代码
打包后的代码以下:
eval("const fun = async () => {\n const data = await Promise.resolve(123);\n console.log(data);\n};\n\nfun();\n\n\n//# sourceURL=webpack:///./src/index.js?")
复制代码
能够看到默认状况下,咱们的代码不会通过任何的转化,下一步,咱们加入babel
运行git checkout step-2 && yarn run build查看当前步骤的结果
使用babel以前,咱们须要一个配置文件,以便于告诉babel要按照什么配置来转化代码,有4种配置的方式:
想了解具体内容的能够看这里
我采用了官方推荐的方式,建立babel.config.js以下
module.exports = {
presets: ['@babel/env'] // env 所包含的插件将支持全部最新的 JavaScript (ES201五、ES2016 等)特性
};
复制代码
添加babel相关的依赖
yarn add babel-loader @babel/core @babel/preset-env -D
复制代码
@babel/core 包含了转换api的全部核心模块 @babel/preset-env 一组默认的预设插件的集合,所包含的插件将支持全部最新的 JavaScript (ES201五、ES2016 等)特性 babel-loader 经过这个loader来开启babel对js的做用
修改webpack config,增长如下内容
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
复制代码
ok,如今咱们从新尝试打包,能够看到打包后的结果变为:
eval("function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }\n\nfunction _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err); } _next(undefined); }); }; }\n\nvar fun =\n/*#__PURE__*/\nfunction () {\n var _ref = _asyncToGenerator(\n /*#__PURE__*/\n regeneratorRuntime.mark(function _callee() {\n var data;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _context.next = 2;\n return Promise.resolve(123);\n\n case 2:\n data = _context.sent;\n console.log(data);\n\n case 4:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee);\n }));\n\n return function fun() {\n return _ref.apply(this, arguments);\n };\n}();\n\nfun();\n\n//# sourceURL=webpack:///./src/index.js?");
复制代码
能够看到里面加入了不少辅助的函数,好比 asyncGeneratorStep、_asyncToGenerator 等等用于处理async和await的,可是若是你直接运行当前被转化后的代码,你会看到一些报错
ReferenceError: regeneratorRuntime is not defined
复制代码
这是由于默认的预设组件虽然会帮咱们转化代码,可是不会把相关的辅助函数引入,咱们须要经过修改配置的方式来开启一些额外的功能,修改babel.config.js以下:
解决的方式不止一种,咱们先用最简单的尝试
module.exports = {
presets: [
[
'@babel/env',
{
useBuiltIns: 'usage'
}
]
]
};
复制代码
@babel/env的参数说明:
targets:配置转化代码的目标环境,常见配置有:
"targets": "> 0.25%, not dead"
或者"targets": { "chrome": "58", "ie": "11" }
,了解更多选项能够参考browserslist<script type="module"></script>
配合使用current || '8.11.0'
spec: 我的理解就是说转化的过程会更严格和规范,可是付出的代价就是转化时间会比较长
loose:Boolean,是否启用“松散”模式来转化,默认是false 举个例子,定义一个class:
class Person{
say(){
console.log(123)
}
}
复制代码
正常状况下,类的原型方法须要经过Object.defineProperty去定义,以保证不可枚举,因此转化后的代码会是:
var _createClass = (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); // (A)
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();
var Person = (function () {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "say",
value: function say() {
console.log(123)
}
}]);
return Person;
})();
复制代码
可是若是启用了loose模式,代码就会被转为:
var Person = (function () {
function Person() {
_classCallCheck(this, Person);
}
Person.prototype.say = function toString() {
console.log(123)
};
return Person;
})();
复制代码
更像是直接用ES5的形式来模拟类,更详细的内容能够查看loose-mode
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
默认是auto,这个应该都明白转化为不一样模块风格的代码"entry" | "usage" | false
,用来决定babel/preset如何处理代码中的 polyfills
咱们这里使用了useage这个选项,尝试打包,依旧报错了..
Can't resolve 'core-js/modules/es6.object.to-string'
Can't resolve 'core-js/modules/es6.promise
Can't resolve 'regenerator-runtime/runtime'
复制代码
babel7.x 以后把一些依赖所有拆分开了,core-js 和 regenerator-runtime相关的lib函数被放在了@babel/polyfill 这个依赖中,所以咱们须要安装这个依赖,而且为了全部的js都能正常使用,咱们须要在js文件的入口去主动require一下这个包以获取一些工具函数的导入~
yarn add @babel/polyfill
复制代码
注意这是一个生产环境须要的依赖,不要放到devDependencies中~
而后修改 src/index.js 在最顶部加上require
require('@babel/polyfill');
const fun = async () => {
const data = await Promise.resolve(123);
console.log(data);
};
fun();
复制代码
如今运行从新打包后的代码,发现已经能够正常输出123了~
上面咱们介绍了使用usage的时候,是能够不须要polyfill的,下面咱们就来去掉polyfill
运行git checkout step-3 && yarn run build查看当前步骤的结果
加入polyfill会致使默认打包的内容把全部的polyfill内容都带上这其实会形成一些额外的问题
所以咱们须要一个运行时的插件,能够作到按需加载须要的工具函数,同时还能够支持只在使用到的运行的地方能够获取到而且不污染全局,这个插件就是@babel/plugin-transform-runtime,同时须要安装@babel/runtime 这个依赖,@babel/plugin-transform-runtime只适用于开发环境,打包以后真正使用的是从babel/runtime中引入的一些runtime tools,而且在开发环境下有一些transform也是须要借助babel/runtime来实现,回到项目中,执行下面的命令:
yarn remove @babel/polyfill
yarn add @babel/plugin-transform-runtime -D
yarn add @babel/transform
复制代码
而后运行build,发现,报错了,报错信息以下:
Can't resolve 'core-js/modules/es6.object.to-string'
Can't resolve 'core-js/modules/es6.promise'
复制代码
为啥,由于@babel/runtime是不包含一些新的语法和对象的,若是须要对新API和语法的polyfill,则还须要安装@babel/runtime-corejs2这个依赖,并在plugin-transform-runtime中打开对corejs的使用,运行下面命令:
yarn add @babel/runtime-corejs2
复制代码
修改babel.config.js
module.exports = {
presets: [
[
'@babel/env',
{
useBuiltIns: 'usage'
}
]
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 2
}
]
]
};
复制代码
corejs 经常使用2个选项,false或者2,false会污染全局的属性,而指定为2则不会污染全局的属性
设置为false,打包中对Promise的使用方式:
./node_modules/core-js/modules/_promise-resolve.js
复制代码
直接去加载依赖会致使全局的属性被影响,若是设置为2,打包后的结果是:
var _Promise = __webpack_require__(/*! ../core-js/promise */ \"./node_modules/@babel/runtime-corejs2/core-js/promise.js\ 复制代码
是会定义一个当前环境下的变量,以后经过_Promise变量来使用promise,这样就不会影响全局的一些属性,这对于类库的开发是很重要的~
当咱们使用了 @babel/runtime-corejs2 后实际上是能够去掉@babel/runtime了,可是要确保corejs设置为2,由此也能够看出来:
@babel/runtime-corejs2 约等于 @babel/runtime + polyfill
babel-plugin-transform-runtime 的全部参数以下:
更多关于 babel-plugin-transform-runtime,请移步到官网查看
babel的插件有不少,可是 @babel/env 和 @babel/plugin-transform-runtime 是每个前端开发者都应该了解和掌握的插件,对于别的插件,用到的时候去了解和配置一下应该就能够了
到此,咱们了解了babel的一些基础配置,留有一个小问题,我并无说关于babel的最佳实践,由于在每一个不一样的场景和项目中,我以为都应该是不同的,欢迎留言讨论,你认为的最佳实践是什么?
几乎每当有新的标准被制定的同时,都会有对应的babel插件的出现,感谢babel社区,让咱们能开心的使用新语法。
下一篇,咱们一块儿手撸一个简单的babel,拥有词法分析,语法分析,转化和生成代码的功能,从底层原理来了解babel,一块儿期待吧~ 若是当前内容对你有所帮助,请点个赞吧,很是感谢~