前沿:文章起源在于,朋友跟树酱说在解决项目兼容IE11浏览器过程当中,遇到“眼花缭乱”的babel配置和插件等,傻傻分不清配置间的区别、以及不了解如何引用babel插件才能让性能更佳,若是你也有这方面的疑虑,这篇文章可能适合你
babel是个什么玩意? Babel本质上是一个编辑器,也就是个“翻译官”的角色,好比树酱听不懂西班牙语,须要别人帮我翻译成为中文,我才晓得。那么Babel就是帮助浏览器翻译的,让web应用可以运行旧版本的浏览器中,好比IE11浏览器不支持Promise等ES6语法,那这个时候在IE11打开你写的web应用,应用就没法正常运行,这时候就须要Babel来“翻译”成为IE11能读懂的
本质上单独靠Babel是没法完成“翻译”,好比官网的例子
const babel = code => code;
不借助Babel插件的前提,输出是不会把箭头函数“翻译”的,若是想完成就须要用到插件,更多概念点点击
官方文档
Babel工做原理本质上就是三个步骤:解析、转换、输出,以下👇所示,html
👨🎓 啊斌同窗: 上面说到的抽象语法树AST又是什么玩意?
答:咱们上文提到,Babel在解析是时候会经过将code转换为AST抽象语法树,本质上是代码语法结构的一种抽象表示,经过以树🌲形的结构形式表现出它的语法结构,抽象在于它的语言形态不会体如今原始代码code中前端
下面介绍下在前端项目开发中一些AST的应用场景:vue
Vue模版解析
: 咱们平时写的.vue
文件经过vue-template-compiler
解析,.vue文件处理为一个ASTBabel的“翻译”
: 如将ES6转换为ES5过程当中转为ASTwebpack的插件UglifyJS
: uglifyjs-webpack-plugin
用来压缩资源,uglifyjs会遇到须要解析es6语法,这个过程当中本质上也是借助babel-loader
你能够安装经过本地安装babel-cli
作个验证,经过babel-cli编译js文件,玩玩“翻译”webpack
🌲推荐阅读:git
👨🎓 啊可同窗: 树酱,我想本身使用AST开发一个babel插件须要使用到哪些东西呢?
答:咱们上一节中提到babel不借助“外援”的话,本身是没法完成翻译,而一个完整的“翻译”的过程是须要走完解析、转换、输出才能完成整个闭环,而这其中的每一个环节都须要借助babel如下这些APIes6
@babel/parser
: babel解析器将源代码code解析成 AST@babel/generator
: 将AST解码生成js代码 new Code@babel/traverse
: 用来遍历AST树,能够用来改造AST~,如替换或添加AST原始节点@babel/core
:包括了整个babel工做流下面是一个简单“翻译”的demo~github
👦:啊宽同窗:你不是说@babel/parser
是也将源代码code解析成 AST吗?为啥@babel/core
也是?
答:@babel/core包含的是整个babel工做流,在开发插件的过程当中,若是每一个API都单独去引入岂不是蒙蔽了来吧~因而就有了@babel/core插件,顾名思义就是核心插件,他将底层的插件进行封装(包含了parser、generator等),提升原有的插件开发效率,简化过程,好一个“🍟肯德基全家桶”web
🌲推荐阅读:vue-cli
讲完Babel的基本使用,接下来聊聊插件,上文提到单独靠babel是“难成大器”的,须要插件的辅助才能实现霸业,那插件是怎么搞的呢?
经过第一节的学习咱们知道完成第一步骤解析完AST后,接下来是进入转换,插件在这个阶段就起到关键做用了。npm
告诉Babel该作什么以前,咱们须要建立一个配置文件.babelrc或者babel.config.js文件
若是我想把es2015
的语法转化为es5 及支持es2020的链式写法,我能够这样写
上图所示👆,咱们能够看到咱们配置两个东西 present
和plugin
👨🎓 啊可同窗:babel不是只须要plugin来帮忙翻译吗,这个present又是什么玩意?
答:presets是预设,举个例子:有一天树酱要去肯德基买鸡翅、薯条、可乐、汉堡。而后我发现有个套餐A包含了(薯条、可乐、汉堡),那这个present就至关于套餐A,它包含了一些插件集合,一个大套餐,这样我就只须要一个套餐A+鸡翅就搞定了,不用配置不少插件。
就比如上面的es2015“套餐”,其实就是Babel团队将同属ES2015相关的不少个plugins
集合到babel-preset-es2015
一个preset中去
👧 啊琪同窗:@babel/preset-env这个是什么?我看不少babel的配置都有
答:@babel/preset-env这个是一个present预设,换句话说就是“豪华大礼包”,包括一系列插件的集合,包含了咱们经常使用的es2015,es2016, es2017等最新的语法转化插件,容许咱们使用最新的js语法,好比 let,const,箭头函数等等,但不包括stage-x阶段的插件。换句话说,他包含了咱们上文提到了es2015
,是个“全家桶”了,而不只是个套餐了。
👦 啊斌同窗:树酱,那我是否是能够本身搞一个预设present?
答: 能够的,可是你能够以 babel-preset-* 的命名规范来建立一个新项目,而后建立一个packjson并安装好定影的依赖和一个index.js 文件用于导出 .babelrc,最终发布到npm中,以下所示
好比咱们在开发中使用,会使用到一些es6的新特征好比Array.from
等,但不是全部的 JavaScript 环境都支持 Array.from,这个时候咱们可使用 Polyfill(代码填充,也可译做兼容性补丁)的“黑科技”,由于babel只转换新的js语法,如箭头函数等,但不转换新的API,好比Symbol、Promise等全局对象,这时候须要借助@babel/polyfill
,把es的新特性都装进来,使用步骤以下
npm 安装
: npm install --save @babel/polyfill
文件顶部导入 polyfill
: import @babel/polyfilll
🙅♂️:缺点:全局引入整个 polyfill包,如promise会被全局引入,污染全局环境,因此不建议使用,那有没有更好的方式?能够直接使用@babel/preset-env并修改配置,由于@babel/preset-env
包含了@babel/polyfill
插件,看下一节
完成上面的配置,而后用Babel编译代码,咱们会发现有时候打出的包体积很大,由于@babel/polyfill有些会被全局引用,那你要弄清楚@babel/preset-env的配置
@babel/preset-env
中与 @babel/polyfill
的相关参数有两个以下:
targets
: 支持的目标浏览器的列表useBuiltIns
: 参数有 “entry”、”usage”、false 三个值。默认值是false,此参数决定了babel打包时如何处理@babel/polyfilll 语句主要聊聊关于useBuiltIns
的不一样配置以下:
entry
: 去掉目标浏览器已支持的polyfilll 模块,将浏览器不支持的都引入对应的polyfilll 模块。usage
: 打包时会自动根据实际代码的使用状况,结合 targets 引入代码里实际用到部分 polyfilll模块false
: 不会自动引入 polyfilll 模块,对polyfilll模块屏蔽🌲建议:使用 useBuiltIns: usage来根据目标浏览器的支持状况,按需引入用到的 polyfill 文件,这样打包体积也不会过大
对于@babel/core
、@babel/preset-env
、@babel/polyfill
等这些插件,当咱们在使用webpack进行打包的时候,如何让webpack知道按这些规则去编译js。这时就须要babel-loader
了,它至关于一个中间桥梁,经过调用babel/core中的API来告知webpack要如何处理。
👦 啊斌同窗:我开发了一个工具库,也使用了babel,若是引用polyfill,如何避免使用致使的污染环境?
答:在开发工具库或者组件库时,就不能再使用babel-polyfill了,不然可能会形成全局污染,可使用@babel/runtime
。它不会污染你的原有的方法。遇到须要转换的方法它会另起一个名字,不然会直接影响使用库的业务代码,使用@babel/runtime
主要在于
@babel/runtime
以前,库和工具包通常不会直接引入 polyfill。不然像 Promise 这样的全局对象会污染全局命名空间,这就要求库的使用者本身提供 polyfill。这些 polyfill 通常在库和工具的使用说明中会提到,好比不少库都会有要求提供 es5 的 polyfill。在使用 babel-runtime 后,库和工具只要在 package.json 中增长依赖 babel-runtime,交给 babel-runtime 去引入 polyfill就能够了如何使用 @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime npm install --save @babel/runtime
👦:啊呆同窗:babel-core和@babel/core是什么区别?
答;@babel是在babel7中版本提出来的,就相似于 vue-cli 升级后使用@vue/cli同样的道理,因此babel7之后的版本都是使用 @babel
开头声明做用域,