刚复工的时候我司业务太多了,我已不记得咱们连续做战了多少天,最近算是有时间能够学习学习个人babel大宝贝了,上周末看了下babel的一些核心模块以及babel的一些配置,今天继续以博客的形式记录总结下来。react
@babel/core
,@babel/cli
,@babel/plugin*
,@babel/preset-env
,@babel/polyfill
,@babel/runtime
,@babel/plugin-transform-runtime
。@babel/core 这个包里主要都是一些去对代码进行转换的核心方法,具体里面的一些api方法我就不作介绍了;引用官网的一句话:“全部转换将使用本地配置文件”,因此待会儿咱们的babel.config.js配置文件就很重要了;再一个core就是核心的意思嘛,因此咱们话很少说先把它装起来,gogogoios
npm install --save-dev @babel/core
这个 @babel/cli就是babel带有的内置cli,能够用来让咱们从命令行来编译咱们的文件,有了他咱们就很方便的对babel进行学习了,那话很少说,先装起来?git
npm install --save-dev @babel/cli
装完以后你就能够这样来编译你的文件:es6
npx babel study.js --watch --out-file study-compiled.js
简单介绍下上面命令用到的几个参数:--out-file
用来把编译后的目标文件输出到对应的文件;若是但愿在每次更改目标文件时都进行编译,能够加上 --watch
选项;固然还有一些别的选项,不过在我学习babel以及配置的话,这两个选项已经够我用了。github
在操做的过程当中若是改了babel配置发现编译出来的文件并无实时编译的状况,须要注意下,若是改了配置文件那就须要从新执行这段命令,要否则babel读不到新的配置。web
若是你已经建立了study.js文件而且执行了这段命令,你会发现,对应的study-compiled.js还没发生变化,由于咱们还没开始写babel的配置文件,莫慌,立刻开始。
npm
babel插件和babel预设是babel配置的两个主要模块,因此我就放在一块儿说了。json
/* babel.config.js */ module.exports = { presets: [ ], plugins: [ "@babel/plugin-transform-arrow-functions" ] }
而后执行咱们上面那段cli的命令,就会获得这种效果:api
/* study.js */ const study = () => {} /* study-compiled.js */ const study = function () {};
固然,若是咱们想要使用es6给数值新增的指数运算符怎么办,只须要添加相应的 @babel/plugin-transform-exponentiation-operator 插件便可:promise
/* babel.config.js */ module.exports = { presets: [ ], plugins: [ "@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-exponentiation-operator" ] }
对应的es6语法就会变成:
/* study.js */ const exponentiation = 2 ** 2 /* study-compiled.js */ const exponentiation = Math.pow(2, 2);
从上面的插件化咱们就知道须要哪一个插件就去引入就完事儿,那么问题来了,es6新增的语法那么多,我总不能一个插件一个插件去添加吧,这样也太麻烦了,这个时候就要用到babel提供的presets了。
presets也就是预设的意思,大概意思就是能够预先设定好一些东西,就免得咱们一个个的去引入插件了。官方提供了不少presets,好比preset-env(处理es6+规范语法的插件集合)、preset-stage(一些处理尚在提案阶段的语法的插件集合,固然这种预设的方式在 babel 7+ 版本已经被废弃了)、preset-react(处理react语法的插件集合)等等。
咱们主要来介绍下preset-env:preset-env是一个智能预设,配置了它就可让你用es6+去书写你的代码,并且他会按需去加载所须要的插件,让你的生活更加美好。。。接下来咱们记得先install这个 @babel/preset-env一波,不配任何插件,而后咱们再来看看效果如何:
/* babel.config.js */ module.exports = { presets: [ "@babel/preset-env" ], plugins: [ ] }
对应的es6语法就会变成:
/* study.js */ const study = () => {} const arr1 = [1, 2, 33] const arr2 = [...arr1] const exponentiation = 2 ** 2 // 新增API new Promise(() => {}) new Map() /* study-compiled.js */ var study = function study() {}; var arr1 = [1, 2, 33]; var arr2 = [].concat(arr1); var exponentiation = Math.pow(2, 2); // 新增API new Promise(function () {}); new Map();
你会发现es6+的语法都被编译了,咱们并无设置任何插件哦,应该也看到了新增的API方法并无被编译,在这里咱们埋下伏笔,等下文讲到polyfill的时候再治他。
关于preset-env,咱们还能够提供一个targets
配置项指定运行环境,就是咱们能够配置对应目标浏览器环境,那么babel就会编译出对应目标浏览器环境能够运行的代码。相信有同窗遇到过在低版本系统ios手机里本身的项目会白屏,实际上是某些语法在ios低版本系统里不支持,这个时候咱们能够直接配置ios 7浏览器环境均可以支持的代码:
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { 'targets': { 'browsers': ['ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS浏览器版本7 } } ] ], plugins: [ ] }
固然babel的Browserslist集成还支持在package.json文件里或者新建一个 .browserslistrc 文件来指定对应目标环境。browserslist配置源
上文也提到了像Promise这种API我们的babel并无给转义,那是由于babel默认是只会去转义js语法的,不会去转换新的API,好比像Promise、Generator、Symbol这种全局API对象,babel是不会去编译的,这个时候就要掏出 @babel/polyfill 了。用法很简单,先安装一波,而后咱们只须要在入口文件顶部引入 @babel/polyfill 就可使用新增的API了。
/* study.js */ import '@babel/polyfill' // 新增API new Promise(function () {}); /* study-compiled.js */ require("@babel/polyfill"); // 新增API new Promise(function () {});
小细节:import被编译成了require,若是想要编译出来的模块引入规范仍是import,则能够在preset-env的配置项中添加"modules": false便可。
modules的options:"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认为"auto"
可是问题又来了,有时候咱们项目里并无用到那么多的新增API,可是 @babel/polyfill 会把全部浏览器环境的的polyfill都引入,整个包的体积就会很大,咱们想要对目标环境按需引入相应的polyfill应该怎么办呢,这个时候咱们就可使用 preset-env 的配置项中的useBuiltIns
属性来按需引入polyfill。
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { "modules": false, "useBuiltIns": "entry", 'targets': { 'browsers': ['ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS浏览器版本7 } } ] ], plugins: [ ] }
这个时候就会在入口处只把全部ie8以上以及iOS 7浏览器不支持api的polyfill引入进来。
最终效果:
/* study.js */ import '@babel/polyfill' // 新增API new Promise(function () {}); /* study-compiled.js */ import "core-js/modules/es6.array.copy-within"; import "core-js/modules/es6.array.every"; ...//省略若干 import "core-js/modules/web.immediate"; import "core-js/modules/web.dom.iterable"; import "regenerator-runtime/runtime"; // 新增API new Promise(function () {});
此时你会发现,import '@babel/polyfill'没有了,引入的是咱们目标环境相应的polyfill。可是有没有发现引入的都是import 'core-js/...'的内容,标题已经说啦,@babel/polyfil是由core-js2和regenerator-runtime组成的一个集成包。
这个时候你又会想,假如个人项目里面只用到了Promise这个API,能不能只给我引入Promise相应的API呢?答案是必能够!,让咱们先来好好了解下preset-env的配置项中的useBuiltIns
属性。
选项:"usage"| "entry"| false,默认为false。
entry
咱们已经用过了,意义就是在入口处将根据咱们配置的浏览器兼容,将目标浏览器环境全部不支持的API都引入。
usage
就很nb了,当配置成usage的时候,babel会扫描你的每一个文件,而后检查你都用到了哪些新的API,跟进咱们配置的浏览器兼容,只引入相应API的polyfill,咱们把useBuiltIns
属性设置为usage
再来看下编译效果:
/* study.js */ import '@babel/polyfill' // 新增API new Promise(function () {}); /* study-compiled.js */ import "core-js/modules/es.object.to-string"; import "core-js/modules/es.promise"; // 新增API new Promise(function () {});
我就问你帅不帅!彻底的按需引入,牛逼了!
相信你也看到了一个东西,当咱们使用useBuiltIns
选项的时候,你的命令行里面是否是显示了一坨这样的警告,大概是在配置文件中未指定core-js版本时,默认会使用core-js2:
WARNING: We noticed you're using the
useBuiltIns
option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via thecorejs
option.
前面也说到了 @babel/polyfil 是由core-js2和regenerator-runtime组成的一个集成包,如今core-js3已经发布了,并且很稳定。可是core-js2在18年的时候已经再也不维护了;@babel/polyfil引入的是2不是3,而且 @babel/polyfill 在babel7.4.0已经再也不推荐使用了,要废掉(好像是由于@babel/polyfill不支持core-js2平滑的过渡到core-js3)。因此core-js官方如今推荐咱们使用polyfill的时候直接引入core-js和regenerator-runtime/runtime这两个包彻底取代 @babel/polyfil 来为了防止重大更改。
固然,咱们须要在preset-env配置项中指定core-js版本,这样就不会再有警告⚠️了:
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { "modules": false, "useBuiltIns": "entry", "corejs": "3", 'targets': { 'browsers': ['not ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS浏览器版本7 } } ] ], plugins: [ ] }
有的时候一些语法的转换会比较复杂,babel会引入一些helper函数,好比说对es6的class进行转换:
/* study.js */ class Test {} /* study-compiled.js */ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Test = function Test() { _classCallCheck(this, Test); };
能够看到上面引入了helper函数来处理class的转换。可是问题又来了,若是好多文件都使用到了复杂语法的转换,这个仍是简单点的,有些helper函数是很复杂代码量不少的,那岂不是每一个文件都会定义一遍这些个函数,每一个文件的代码会不少?若是说能够把这些helper函数都抽离到一个公共的包里,用到的地方只须要引入对应的函数便可,咱们的编译出来的代码量会大大滴减小,这个时候就须要用到 @babel/plugin-transform-runtime 插件来配合@babel/runtime进行使用。记得先安装一波,而后在插件选项中加入 @babel/plugin-transform-runtime 这个插件,而后咱们来看看编译后的效果:
/* study.js */ class Test {} /* study-compiled.js */ import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; var Test = function Test() { _classCallCheck(this, Test); };
固然若是咱们只是为了减小编译出来的文件中代码量而使用这个插件的话就过小看他了,并且也没有必要。
@babel/plugin-transform-runtime还有一个最重要的做用:好比说像上面咱们说的Promise就须要提供相应的polyfill去解决,这样作会有一个反作用,就是会污染全局变量。若是咱们只是在一个业务项目这样搞还好,也没别人要用到。可是若是咱们是在维护一个公共的东西,好比公共组件库,咱们这样搞,你的一些polyfill可能会把一些全局的api给改掉,反作用就会很明显,别人用你的组件库的时候就可能会出问题。@babel/plugin-transform-runtime插件为咱们提供了一个配置项corejs,他能够给这些polyfill提供一个沙箱环境,这样就不会污染到全局变量,无反作用你说美不美。
记得安装 @babel/runtime-corejs2 这个包(稳定版用2就能够),注意若是不配置的话,是不会提供沙箱环境的。而后在 @babel/plugin-transform-runtime 插件配置corejs:
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { "modules": false, "useBuiltIns": "usage", "corejs": "3", 'targets': { 'browsers': ["ie >= 8", "iOS 7"] // 支持ie8,直接使用iOS浏览器版本7 } } ] ], plugins: [ [ "@babel/plugin-transform-runtime", { "corejs": 2 } ] ] }
咱们来看下编译后的效果:
/* study.js */ new Promise(() => {}) class Test {} /* study-compiled.js */ import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck"; import _Promise from "@babel/runtime-corejs2/core-js/promise"; new _Promise(function () {}); var Test = function Test() { _classCallCheck(this, Test); };
接下来本人会去继续研究babel是如何解析编译的,target:理解babel如何解析编译,可以手写一个babel插件出来。最近需求比较多,下一篇估计得等到Q2了。。。
最近也是疫情期间,你们必定要记得尽可能少出门,出门必带口罩。像白衣天使们致敬!