前端科普系列(4):Babel —— 把 ES6 送上天的通天塔
一、前言本文首发于 vivo互联网技术 微信公众号
链接:https://mp.weixin.qq.com/s/plJewhUd0xDXh4Ce4CGpHg
作者:Morrain
在上一节 《CommonJS:不是前端却革命了前端》中,我们聊到了 ES6 Module,它是 ES6 中对模块的规范,ES6 是 ECMAScript 6.0 的简称,泛指 JavaScript 语言的下一代标准,它的第一个版本 ES2015 已经在 2015 年 6 月正式发布,本文中提到的 ES6 包括 ES2015、ES2016、ES2017等等。在第一节的《Web:一路前行一路忘川》中也提到过,ES2015 从制定到发布历经了十几年,引入了很多的新特性以及新的机制,浏览器对 ES6 的支持进度远远赶不上前端开发小哥哥们使用 ES6 的热情,于是矛盾就日益显著……
二、Babel 是什么先来看下它在官网上的定义:
Babel is a JavaScript compiler
没错就一句话,Babel 是 JavaScript 的编译器。至于什么是编译器,可以参考the-super-tiny-compiler这个项目,可以找到很好的答案。
本文是以 Babel 7.9.0 版本进行演示和讲解的,另外建议学习者阅读英文官网,中文官网会比原版网站慢一个版本,并且很多依然是英文的。
Babel 就是一套解决方案,用来把 ES6 的代码转化为浏览器或者其它环境支持的代码。注意我的用词哈,我说的不是转化为 ES5 ,因为不同类型以及不同版本的浏览器对 ES6 新特性的支持程度都不一样,对于浏览器已经支持的部分,Babel 可以不转化,所以 Babel 会依赖浏览器的版本,后面会讲到。这里可以先参考browerslist项目。
Babel 的历史在学习任何一门知识前,我都习惯先了解它的历史,这样才能深刻理解它存在意义。
Babel 的作者是 FaceBook 的工程师 Sebastian McKenzie。他在 2014 年发布了一款 JavaScript 的编译器 6to5。从名字就能看出来,它主要的作用就是将 ES6 转化为 ES5。
这里的 ES6 指 ES2015,因为当时还没有正式发布, ES2015 的名字还未被正式确定。
于是很多人评价,6to5 只是 ES6 得到支持前的一个过渡方案,它的作者非常不同意这个观点,认为 6to5 不光会按照标准逐步完善,依然具备非常大的潜力反过来影响并推进标准的制定。正因为如此 6to5 的团队觉得 '6to5' 这个名字并没有准确的传达这个项目的目标。加上 ES6 正式发布后,被命名为 ES2015,对于 6to5 来说更偏离了它的初衷。于是 2015 年 2 月 15 号,6to5 正式更名为 Babel。
(图片来源于网络)
Babel 是巴比伦文化里的通天塔,用来给 6to5 这个项目命名真得太贴切了!羡慕这些牛逼的人,不光代码写得好,还这么有文化,不像我们,起个变量名都得憋上半天,吃了没有文化的亏。这也是为什么我把这篇文章起名为 《Babel:把 ES6 送上天的通天塔》的原因。
三、Babel 怎么用了解了 Babel 是什么后,很明显我们就要开始考虑怎么使用 Babel 来转化 ES6 的代码了,除了 Babel 本身提供的 cli 等工具外,它还支持和其它打包工具配合使用,譬如 webpack、rollup 等等,可以参考官网对不同平台提供的配置说明。
1、构建 Babel 演示的工程本文为了感受 Babel 最原始的用法,不结合其它任何工具,直接使用 Babel 的 cli 来演示。
使用如下命令构建一个 npm 包,并新建 src 目录 和 一个 index.js 文件。
npminit-y
package.json 内容如下:
{"name":"demo","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo\"Error:notestspecified\"&&exit1"},"keywords":[],"author":"","license":"ISC"}2、安装依赖包
npminstall--save-dev@babel/core@babel/cli@babel/preset-env
后面会介绍这些包的作用,先看用法
增加 babel 命令来编译 src 目录下的文件到 dist 目录:
{"name":"demo","version":"1.0.0","description":"","main":"src/index.js","scripts":{"babel":"babelsrc--out-dirdist","test":"echo\"Error:notestspecified\"&&exit1"},"keywords":[],"author":"","license":"ISC","devDependencies":{"@babel/cli":"^7.8.4","@babel/core":"^7.9.0","@babel/preset-env":"^7.9.0"}}3、增加 Babel 配置文件
在工程的根目录添加 babel.config.js 文件,增加 Babel 编译的配置,没有配置是不进行编译的。
constpresets=[['@babel/env',{debug:true}]]constplugins=[]module.exports={presets,plugins}
上例中 debug 配置是为了打印出 Babel 工作时的日志,可以方便的看来,Babel 转化了哪些语法。
presets 主要是配置用来编译的预置,plugins 主要是配置完成编译的插件,具体的含义后面会讲推荐用 Javascript 文件来写配置文件,而不是 JSON 文件,这样可以根据环境来动态配置需要使用的 presets 和 plugins
constpresets=[['@babel/env',{debug:true}]]constplugins=[]if(process.env["ENV"]==="prod"){plugins.push(...)}module.exports={presets,plugins}
编译时就会报如下错误:
根据报错的提示,添加 @babel/plugin-proposal-class-properties 即可。
npminstall--save-dev@babel/plugin-proposal-class-properties点击并拖拽以移动
//babel.config.jsconstpresets=[['@babel/env',{debug:true}]]constplugins=['@babel/plugin-proposal-class-properties']module.exports={presets,plugins}
配置 corejs 的版本
当 useBuiltIns 设置为 'usage' 或者 'entry' 时,还需要设置 @babel/preset-env 的 corejs 参数,用来指定注入 built-in 的实现时,使用 corejs 的版本。否则 Babel 日志输出会有一个警告。
最终的 Babel 配置如下:
//babel.config.jsconstpresets=[['@babel/env',{debug:true,useBuiltIns:'usage',corejs:3,targets:{}}]]constplugins=['@babel/plugin-proposal-class-properties']module.exports={presets,plugins}(3)@babel/plugin-transform-runtime
在介绍 @babel/plugin-transform-runtime 的用途之前,先前一个例子:
//src/index.jsconstadd=(a,b)=>a+bconstarr=[1,2]consthasThreee=arr.includes(3)newPromise(resolve=>resolve(10))classPerson{statica=1;staticb;name='morrain';age=18}//dist/index.js"usestrict";require("core-js/modules/es.array.includes");require("core-js/modules/es.object.define-property");require("core-js/modules/es.object.to-string");require("core-js/modules/es.promise");function_classCallCheck(instance,Constructor){if(!(instanceinstanceofConstructor)){thrownewTypeError("Cannotcallaclassasafunction");}}function_defineProperty(obj,key,value){if(keyinobj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else{obj[key]=value;}returnobj;}varadd=functionadd(a,b){returna+b;};vararr=[1,2];varhasThreee=arr.includes(3);newPromise(function(resolve){returnresolve(10);});varPerson=functionPerson(){_classCallCheck(this,Person);_defineProperty(this,"name",'morrain');_defineProperty(this,"age",18);};_defineProperty(Person,"a",1);_defineProperty(Person,"b",void0);
之前的例子,再次编译后,可以看到,之前的 helper 函数,都变成类似require("@babel/runtime/helpers/classCallCheck")的实现了。
//dist/index.js"usestrict";var_interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");require("core-js/modules/es.array.includes");require("core-js/modules/es.object.to-string");require("core-js/modules/es.promise");var_classCallCheck2=_interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));var_defineProperty2=_interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));varadd=functionadd(a,b){returna+b;};vararr=[1,2];varhasThreee=arr.includes(3);newPromise(function(resolve){returnresolve(10);});varPerson=functionPerson(){(0,_classCallCheck2["default"])(this,Person);(0,_defineProperty2["default"])(this,"name",'morrain');(0,_defineProperty2["default"])(this,"age",18);};(0,_defineProperty2["default"])(Person,"a",1);(0,_defineProperty2["default"])(Person,"b",void0);
//babel.config.jsconstpresets=[['@babel/env',{debug:true,targets:{}}]]constplugins=['@babel/plugin-proposal-class-properties',['@babel/plugin-transform-runtime',{corejs:3}]]module.exports={presets,plugins}//dist/index.js"usestrict";var_interopRequireDefault=require("@babel/runtime-corejs3/helpers/interopRequireDefault");var_classCallCheck2=_interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));var_defineProperty2=_interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty"));var_promise=_interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));var_includes=_interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));varadd=functionadd(a,b){returna+b;};vararr=[1,2];varhasThreee=(0,_includes["default"])(arr).call(arr,3);new_promise["default"](function(resolve){returnresolve(10);});varPerson=functionPerson(){(0,_classCallCheck2["default"])(this,Person);(0,_defineProperty2["default"])(this,"name",'morrain');(0,_defineProperty2["default"])(this,"age",18);};(0,_defineProperty2["default"])(Person,"a",1);(0,_defineProperty2["default"])(Person,"b",void0);配置中的 method 值有 'entry-global'、'usage-global'、'usage-pure' 三种。
'entry-global' 等价于 @babel/preset-env 中的 useBuiltIns: 'entry'
'usage-global' 等价于 @babel/preset-env 中的 useBuiltIns: 'usage'
'usage-pure' 等价于 @babel/plugin-transform-runtime 中的 corejs
本文为了讲解方便,都是用 Babel 原生的 @babel/cli 来编译文件,实际使用中,更多的是结合 webpack、rollup 这样第三方的工具来使用的。
所以下一节,我们聊聊打包工具 webpack。
五、参考文献6to5 JavaScript Transpiler Changes Name to Babel
Babel学习系列2-Babel设计,组成
初学 Babel 工作原理
RFC: Rethink polyfilling story
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。