说实话,我从工作开始就一直在接触babel,然而对于babel并没有一个清晰的认识,只知道babel是用于编译javascript,让开发者能使用超前的ES6+语法进行开发。自己配置babel的时候,总是遇到很多困惑,下面我就以babel@7为例,重新简单认识下babel。
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
babel的配置文件一般是根目录下的.babelrc,babel@7目前已经支持babel.config.js,不妨用babel.config.js试试。
babel提供的基础能力是语法转换,或者叫语法糖转换。比如把箭头函数转为普通的function,而对于ES6新引入的全局对象是默认不做处理的,如Promise, Map, Set, Reflect, Proxy等。对于这些全局对象和新的api,需要用垫片polyfill处理,core-js有提供这些内容。
所以babel做的事情主要是:
根据你的配置做语法糖解析,转换根据你的配置塞入垫片polyfill如果不搞清楚这点,babel的文档看起来会很吃力!
babel默认不做任何处理,需要借助插件来完成语法的解析,转换,输出。
插件分为语法插件Syntax Plugins和转换插件Transform Plugins。
语法插件仅允许babel解析语法,不做转换操作。我们主要关注的是转换插件。
转换插件,顾名思义,负责的是语法转换。
转换插件将启用相应的语法插件,如果启用了某个语法的转换插件,则不必再另行指定相应的语法插件了。
语法转换插件有很多,从ES3到ES2018,甚至是一些实验性的语法和相关框架生态下的语法,都有相关的插件支持。
语法转换插件主要做的事情有:
利用@babel/parser进行词法分析和语法分析,转换为AST --> 利用babel-traverse进行AST转换(涉及添加,更新及移除节点等操作) --> 利用babel-generator生成目标环境js代码
babel@7之前的缩写形式是这样的:
// 完整写法
plugins: [
"babel-plugin-transform-runtime"
]
// 简写形式
plugins: [
"transform-runtime"
]
而在babel@7之后,由于plugins都归到了@babel目录下,所以简写形式也有所改变:
// babel@7插件完整写法
plugins: [
"@babel/plugin-transform-runtime"
]
// 简写形式,需要保留目录
plugins: [
"@babel/transform-runtime"
]
我们自己也可以开发插件,官网上的一个非常简单的小例子:
export default function() {
return {
visitor: {
Identifier(path) {
const name = path.node.name;
// reverse the name: JavaScript -> tpircSavaJ
path.node.name = name
.split("")
.reverse()
.join("");
},
},
};
}
preset,意为“预设”,其实是一组plugin的集合。我的理解是,根据这项配置,babel会为你预设(或称为“内置”)好一些ECMA标准,草案,或提案下的语法或API,甚至是你自己写的一些语法规则。当然,这都是基于plugin实现的。
@babel/preset-env提供了一种智能的预设,根据配置的options来决定支持哪些能力。
我们看看关键的options有哪些。
targets描述你的项目要支持的目标环境。写法源于开源项目browserslist。这项配置应该根据你需要兼容的浏览器而设置,不必与其他人一模一样。示例如下:
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 9"]
}
loose可以直译为“松散模式”,默认为false,即为normal模式。简单地说,就是normal模式转换出来的代码更贴合ES6风格,更严谨;而loose模式更像我们平时的写法。以class写法举例:
我们先写个简单的class:
class TestBabelLoose {
constractor(name) {
this.name = name
}
getName() {
return this.name
}
}
new TestBabelLoose('Tusi')
使用normal模式编译得到结果如下:
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 TestBabelLoose =
/*#__PURE__*/
function () {
function TestBabelLoose() {
_classCallCheck(this, TestBabelLoose);
}
_createClass(TestBabelLoose, [{
key: "constractor",
value: function constractor(name) {
this.name = name;
}
}, {
key: "getName",
value: function getName() {
return this.name;
}
}]);
return TestBabelLoose;
}();
new TestBabelLoose('Tusi');
而使用loose模式编译得到结果是这样的,是不是更符合我们用prototype实现类的写法?
"use strict";
var TestBabelLoose =
/*#__PURE__*/
function () {
function TestBabelLoose() {}
var _proto = TestBabelLoose.prototype;
_proto.constractor = function constractor(name) {
this.name = name;
};
_proto.getName = function getName() {
return this.name;
};
return TestBabelLoose;
}();
new TestBabelLoose('Tusi');
个人推荐配置loose: false,当然也要结合项目实际去考量哪种模式更合适。
modules可选值有:"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认是auto
该配置将决定是否把ES6模块语法转换为其他模块类型。注意,cjs是commonjs的别名。
其实我一直有个疑惑,为什么我看到的开源组件中,基本都是设置的modules: false?后面终于明白了,原来这样做的目的是把转换模块类型的处理权交给了webpack,由webpack去处理这项任务。所以,如果你也使用webpack,那么设置modules: false就没错啦。
useBuiltIns可选值有:"entry" | "usage" | false,默认是false
该配置将决定@babel/preset-env如何去处理polyfill
"entry"
如果useBuiltIns设置为"entry",我们需要安装@babel/polyfill,并且在入口文件引入@babel/polyfill,最终会被转换为core-js模块和regenerator-runtime/runtime。对了,@babel/polyfill也不会处理stage <=3的提案。
我们用一段包含了Promise的代码来做下测试:
import "@babel/polyfill";
class TestBabelLoose {
constractor(name) {
this.name = name
}
getName() {
return this.name
}
testPromise() {
return new Promise(resolve => {
resolve()
})
}
}
new TestBabelLoose('Tusi')
但是编译后,貌似引入了很多polyfill啊,一共149个,怎么不是按需引入呢?嗯...你需要往下看了。
import "core-js/modules/es6.array.map";
import "core-js/modules/es6.map";
import "core-js/modules/es6.promise";
import "core-js/modules/es7.promise.finally";
import "regenerator-runtime/runtime";
// 此处省略了144个包。。。
"usage"
如果useBuiltIns设置为"usage",我们无需安装@babel/polyfill,babel会根据你实际用到的语法特性导入相应的polyfill,有点按需加载的意思。
// 上个例子中,如果改用useBuiltIns: 'usage',最终转换的结果,只有四个模块
import "core-js/modules/es6.object.define-property";
import "core-js/modules/es6.promise";
import "core-js/modules/es6.object.to-string";
import "core-js/modules/es6.function.name";
配置"usage"时,常搭配corejs选项来指定core-js主版本号
useBuiltIns: "usage",
corejs: 3
false
如果useBuiltIns设置为false,babel不会自动为每个文件加上polyfill,也不会把import "@babel/polyfill"转为一个个独立的core-js模块。
@babel/preset-env还有一些配置,自己慢慢去折腾吧......stage-x描述的是ECMA标准相关的内容。根据TC39(ECMA39号技术专家委员会)的提案划分界限,stage-x大致分为以下几个阶段:
stage-0:strawman,还只是一种设想,只能由TC39成员或者TC39贡献者提出。stage-1:proposal,提案阶段,比较正式的提议,只能由TC39成员发起,这个提案要解决的问题须有正式的书面描述,一般会提出一些案例,以及API,语法,算法的雏形。stage-2:draft,草案,有了初始规范,必须对功能的语法和语义进行正式描述,包括一些实验性的实现,也可以提出一些待办事项。stage-3:condidate,候选,该提议基本已经实现,需要等待实践验证,用户反馈及验收测试通过。stage-4:finished,已完成,必须通过Test262验收测试,下一步就是纳入到ECMA标准中。比如一些ES2016,ES2017的语法就是通过这个阶段被合入ECMA标准中了。有兴趣了解的可以关注ecma262。
需要注意的是,babel@7已经移除了stage-x的preset,stage-4部分的功能已经被@babel/preset-env集成了,而如果你需要stage <= 3部分的功能,则需要自行通过plugins组装。
As of v7.0.0-beta.55, we've removed Babel's Stage presets.
Please consider reading our blog post on this decision at
https://babeljs.io/blog/2018/07/27/removing-babels-stage-presets
for more details. TL;DR is that it's more beneficial in the long run to explicitly add which proposals to use.
If you want the same configuration as before:
{
"plugins": [
// Stage 2
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
// Stage 3
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
["@babel/plugin-proposal-class-properties", { "loose": false }],
"@babel/plugin-proposal-json-strings"
]
}
如需创建一个自己的preset,只需导出一份配置即可,主要是通过写plugins来实现preset。此外,我们也可以在自己的preset中包含第三方的preset。
module.exports = function() {
return {
// 增加presets项去包含别人的preset
presets: [
require("@babel/preset-env")
],
// 用插件来包装成自己的preset
plugins: [
"pluginA",
"pluginB",
"pluginC"
]
};
}
babel运行时,很重要的一个东西,它一定程度上决定了你产出的包的大小!一般适合于组件库开发,而不是应用级的产品开发。
这里有两个东西要注意,一个是@babel/runtime,它包含了大量的语法转换包,会根据情况被按需引入。另一个是@babel/plugin-transform-runtime,它是插件,负责在babel转换代码时分析词法语法,分析出你真正用到的ES6+语法,然后在transformed code中引入对应的@babel/runtime中的包,实现按需引入。
举个例子,我用到了展开运算符...,那么经过@babel/plugin-transform-runtime处理后的结果是这样的:
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
var arrayWithoutHoles = __webpack_require__(2);
var iterableToArray = __webpack_require__(3);
var nonIterableSpread = __webpack_require__(4);
function _toConsumableArray(arr) {
return arrayWithoutHoles(arr) || iterableToArray(arr) || nonIterableSpread();
}
module.exports = _toConsumableArray;
// EXTERNAL MODULE: ../node_modules/@babel/runtime/helpers/toConsumableArray.js
var toConsumableArray = __webpack_require__(0);
var toConsumableArray_default = /*#__PURE__*/__webpack_require__.n(toConsumableArray);
@babel/runtime是需要按需引入到生产环境中的,而@babel/plugin-transform-runtime是babel辅助插件。因此安装方式如下:
npm i --save @babel/runtime
npm i --save-dev @babel/plugin-transform-runtime
配置时也挺简单:
const buildConfig = {
presets: [
// ......
],
plugins: [
"@babel/plugin-transform-runtime"
],
// ......
}
两者看起来都实现了按需加载的能力,但是实际上作用是不一样的。@babel/runtime处理的是语法支持,把新的语法糖转为目标环境支持的语法;而useBuiltIns: 'usage'处理的是垫片polyfill,为旧的环境提供新的全局对象,如Promise等,提供新的原型方法支持,如Array.prototype.includes等。如果你开发的是组件库,一般不建议处理polyfill的,应该由调用者去做这些支持,防止重复的polyfill。
开发组件时,如果仅使用@babel/plugin-transform-runtime最后简单地提一下使用babel@7要注意的地方,当然更详细的内容还是要看babel官方。
babel@7相关的包命名都改了,基本是@babel/plugin-xxx, @babel/preset-xxx这种形式。这是开发插件体系时一个比较标准的命名和目录组织规范。建议用babel.config.js代替.babelrc,这在你要支持不同环境时特别有用。babel@7已经移除了stage-x的presets,也不鼓励再使用@babel/polyfill。不要再使用babel-preset-es2015, babel-preset-es2016等preset了,应该用@babel/preset-env代替。......本人只是对babel有个粗略的认识,所以这是一篇babel入门的简单介绍,并没有提到深入的内容,可能也存在错误之处。自己翻来覆去也看过好几遍babel的文档了,一直觉得收获不大,也没理解到什么东西,在与webpack配合使用的过程中,还是有很多疑惑没搞懂的。其实错在自己不该在复杂的项目中直接去实践。在最近重新学习webpack和babel的过程中,我觉得,对于不是很懂的东西,我们不妨从写一个hello world开始,因为不是每个人都是理解能力超群的天才......
原文:https://segmentfault.com/a/1190000021310969
大家知道,将ES6代码编译为ES5时,我们常用到Babel这个编译工具。大家参考一些网上的文章或者官方文档,里面常会建议大家在.babelrc中输入如下代码
文的babel使用场景局限于babel配合webpack来转译输出es5的js代码,babel的命令行、以代码形式调用或node环境下这些统统都不会涉及。Babel使用的难点主要在于理解polyfill、runtime和core-js。
自从 Babel 由版本5升级到版本6后,在安装和使用方式上与之前大相径庭,于是写了这篇入坑须知,以免被新版本所坑。坑一本地安装和全局安装 、坑二编译插件、坑三babel-polyfill插件
babel-preset-env 一个帮你配置babel的preset,根据配置的目标环境自动采用需要的babel插件。babel-preset-env 功能类似 babel-preset-latest,优点是它会根据目标环境选择不支持的新特性来转译
本文主要内容包括:什么是babel-polyfill,如何使用,如何通过按需加载进行性能优化。babel只负责语法转换,比如将ES6的语法转换成ES5。但如果有些对象、方法,浏览器本身不支持,此时需要引入babel-polyfill来模拟实现这些对象、方法。
由于现在前端出现了很多非es5的语法,如jsx,.vue,ts等等的格式和写法。babel其实是一个解释器,它主要讲进行中的代码分为三个阶段执行:解释,转换,生成。
在 JavaScript 中,没有基本类型,创建的所有东西都是对象。例如,创建一个新字符串,与其他语言不同,在 JavaScript 中,字符串或数字的声明会自动创建一个封装值的对象,并提供不同的方法,甚至可以在基本类型上执行这些方法。
在项目根目录下新建一个配置文件—— webpack.config.js 文件:执行编译打包命令,完成后打开 bundle.js 文件发现 isNull 和 unique 两个函数没有被编译,和 webpack 官方说法一致:webpack 默认支持 ES6 模块语法,要编译 ES6 代码依然需要 babel 编译器。
Babel对代码进行转换,会将JS代码转换为AST抽象语法树(解析),对树进行静态分析(转换),然后再将语法树转换为JS代码(生成)。每一层树被称为节点。每一层节点都会有type属性,用来描述节点的类型。其他属性用来进一步描述节点的类型。
Babel 对于前端开发者来说应该是很熟悉了,日常开发中基本上是离不开它的。我们已经能够熟练地使用 es2015+ 的语法。但是对于浏览器来说,可能和它们还不够熟悉,我们得让浏览器理解它们,这就需要 Babel
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!