🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
&emsp;&emsp;Babel是一个JavaScript编译器,不仅能将当前运行环境不支持的JavaScript语法(例如ES6、ES7等)编译成向下兼容的可用语法(例如ES3或ES5),这其中会涉及新语法的转换和缺失特性的修补;还支持语法扩展,从而能随时随地的使用JSX、TypeScript等语法。目前最新版本是7.4,自从6.0以来,Babel被分解的更加模块化,各种转译功能都以插件的形式分离出来,可按自己的需求,灵活配置。 &emsp;&emsp;在7.0版本中,对Babel的包做了一次大调整,统一改成域级包,将原先以“babel-”为前缀的包迁移到@babel的命名空间,例如@babel/core、@babel/cli等。这种模块化的设计,既能区分是否是官方发布的,也能避免命名冲突。 ## 一、@babel/core &emsp;&emsp;如果要以编程的方式使用Babel,那么可以通过@babel/core实现,安装命令如下所示。 ~~~ npm install --save-dev @babel/core ~~~ &emsp;&emsp;在引入该包后,就能在JavaScript文件中直接编译代码、文件或AST。以编译代码为例,可选择的方法有三个,如下所示。 ~~~ var babel = require("@babel/core"); babel.transform(code, options, function(err, result) { console.log(result);   // => { code, map, ast } }); babel.transformSync(code, options) // => { code, map, ast } babel.transformAsync(code, options) // => Promise<{ code, map, ast }> ~~~ &emsp;&emsp;虽然transform()用于异步编译,transformSync()用于同步编译,但是它们得到的结果相同,都是一个由转换后的代码(code)、源码映射信息(map)和抽象语法树(ast)组成的对象。而transformAsync()得到的结果与前面两个方法不同,它返回一个包含code、map和ast的Promise对象。这些方法的第二个options参数,可用于传递配置信息,具体可参考[官方文档](https://www.babeljs.cn/docs/options)。 &emsp;&emsp;如果要编译文件或AST,那么也有类似的三个方法可供选择。除了能编译之外,利用这个包还能把代码解析成AST(即传入一段代码,得到一个AST)以便其它插件对其进行语法分析,可使用的方法有parse()、parseSync()和parseAsync()。 ## 二、@babel/cli &emsp;&emsp;@babel/cli是一个Babel内置的命令行工具,可通过命令来编译文件,其安装如下所示。 ~~~ npm install --save-dev @babel/cli ~~~ &emsp;&emsp;假设有一个src.js文件,其内容如下代码所示,用到了ES6中的箭头函数。 ~~~ let fn = () => true; ~~~ &emsp;&emsp;在运行下面的命令后,可以看到输出和输入的代码相同,这是因为此时还未指定任何转译插件。 ~~~ ./node_modules/.bin/babel src.js ~~~ &emsp;&emsp;如果当前的npm版本是5.2以上,那么可将./node\_modules/.bin缩短为npx,如下所示。 ~~~ npx babel src.js ~~~ &emsp;&emsp;在babel中,有多个可用的参数,下面只列举其中的四个,其余参数的用法可参考[官方文档](https://www.babeljs.cn/docs/babel-cli)。 ~~~ npx babel src --out-dir dist npx babel src.js --out-file dist.js npx babel src.js --out-file dist.js --plugins=@babel/plugin-transform-classes,@babel/transform-modules-amd npx babel src.js --out-file dist.js --presets=@babel/preset-env,@babel/flow ~~~ &emsp;&emsp;(1)“--out-dir”参数可编译整个src目录下的文件并输出到dist目录中。 &emsp;&emsp;(2)“--out-file”参数可编译src.js文件并输出到dist.js文件中。 &emsp;&emsp;(3)“--plugins”参数可指定编译过程中要使用的插件,多个插件用逗号隔开。 &emsp;&emsp;(4)“--presets”参数可指定编译过程中要使用的预设,多个预设用逗号隔开。 &emsp;&emsp;在package.json文件中,可以通过scripts字段声明脚本命令。如果想要简化babel命令,那么可以将它们迁移到scripts字段中,如下所示。 ~~~ { "scripts": { "compile": "npx babel src.js --out-file dist.js" } } ~~~ &emsp;&emsp;现在要编译目录的话,只要执行下面的这条npm命令即可。 ~~~ npm run compile ~~~ ## 三、配置 &emsp;&emsp;在Babel中,可以将各种命令的参数集中到一个配置文件中,而可配置的文件包括babel.config.js、.babelrc和package.json。 **1)babel.config.js** &emsp;&emsp;这是Babel 7最新引入的配置文件,存在于根目录中(即package.json文件所在的目录)。它不仅能以编程的方式声明全局生效的[配置参数](https://www.babeljs.cn/docs/config-files#project-wide-configuration),还能利用overrides字段对不同的子目录进行针对性的配置,从而就能避免为相关目录创建一个.babelrc文件了。在下面的示例中,为test目录单独配置了预设(presets)。 ~~~ module.exports = { presets: [...], overrides: [{ test: ["./test"], presets: [...] }] }; ~~~ &emsp;&emsp;当运行下面两条命令进行编译时,第一条读取的是最外层的预设,第二条读取的是overrides中的预设。 ~~~ npx babel src.js npx babel ./test/src.js ~~~ **2).babelrc** &emsp;&emsp;babel.config.js并不是.babelrc的替代品,.babelrc文件用于局部配置(如下代码所示),可放置于所有目录中。如果当前目录没有.babelrc文件,那么就会往上查找直至找到为止。 ~~~ { "presets": [...] } ~~~ &emsp;&emsp;注意,如果.babelrc的后缀是“.js”(即.babelrc.js),那么在文件中可以通过JavaScript配置参数。 **3)package.json** &emsp;&emsp;在package.json文件中,可以声明一个babel字段,其值就是.babelrc文件中的[配置参数](https://www.babeljs.cn/docs/config-files#file-relative-configuration),如下所示。 ~~~ { "babel": { "presets": [...] } } ~~~ &emsp;&emsp;注意,不能让.babelrc和声明过babel字段的package.json处在相同的目录中。 **4)配置函数或方法** &emsp;&emsp;前面三种都是用单独的文件来配置Babel的参数,其实还可以通过相关的函数或方法来达到相同地目的。在下面的示例中,引入gulp-babel包后就能通过得到的babel()函数来配置Babel的参数。 ~~~ let babel = require("gulp-babel"); babel({ presets: [...] }); ~~~ ## 四、插件 &emsp;&emsp;Babel的编译可分为三个阶段:解析、转换和生成,而插件(Plugin)在转换过程中起到了至关重要的作用。借助Babel的插件可将解析完成的AST按照特定的要求进行处理,然后再输出生成的代码。 **1)插件类型** &emsp;&emsp;Babel中的插件分为两种类型:语法和转换。语法插件只允许Babel解析成特定类型的语法,例如JSX、Flow等。转换插件常用来编译ES6、ES7、React和Modules等,由于能自动开启相应的语法插件,因此不用显式的指定两种插件。以之前的箭头函数为例,如果要将其编译成所有浏览器都能识别的形式,那么就得安装相应的插件,如下所示。 ~~~ npm install --save-dev @babel/plugin-transform-arrow-functions ~~~ &emsp;&emsp;在.babelrc文件中的配置如下所示。 ~~~ { "plugins": ["@babel/plugin-transform-arrow-functions"] } ~~~ &emsp;&emsp;在配置文件中,不仅能指定插件的相对或绝对路径,还可省略插件名称中的“babel-plugin-”前缀。假设有个插件名叫@org/babel-plugin-name,那么它的相对路径和简写名称如下所示。 ~~~ { "plugins": [ "../@org/babel-plugin-name", "@org/name" ] } ~~~ **2)执行顺序** &emsp;&emsp;插件的执行顺序会受配置时所处的位置的影响,具体规则如下所列,其中预设是指官方预先设计的一组插件集,本质上仍然是插件。 &emsp;&emsp;(1)插件执行在预设之前。 &emsp;&emsp;(2)插件会按顺序从前往后执行。 &emsp;&emsp;(3)预设与插件相反,从后往前执行。 &emsp;&emsp;以下面的配置信息为例,在插件中,先执行@babel/plugin-transform-arrow-functions,后执行@babel/plugin-transform-classes。而在预设中,先执行@babel/preset-react,后执行@babel/preset-env。 ~~~ { "plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-classes"], "presets": ["@babel/preset-env", "@babel/preset-react"] } ~~~ **3)插件参数** &emsp;&emsp;如果要配置插件的参数,那么得在插件名称后增加一个参数对象,并且写成数组的形式,如下所示。 ~~~ { "plugins": [ ["@babel/plugin-transform-arrow-functions", { "spec": true }], ["@babel/plugin-transform-classes", { "loose": true }] ] } ~~~ &emsp;&emsp;预设也能接收参数,其写法与之类似。 ## 五、预设 &emsp;&emsp;如果在配置文件中一个一个的声明插件,那么不仅会让该文件变得巨大,而且还难免会有所遗漏。官方为了避免此类问题,引入了预设(Preset)的概念。预设类似于生活中的套餐,每个套餐会集合不同的插件,从而能够一次性安装各类插件,并且还可共享配置的参数。 **1)@babel/preset-env** &emsp;&emsp;此预设不仅集合了最新的ES语法(即编译指定的ES标准),还能配置所要支持的平台(例如Node、Chrome等)和版本,以及按需加载插件,其安装命令如下所示。 ~~~ npm install --save-dev @babel/preset-env ~~~ &emsp;&emsp;下面是一个预设的配置示例,支持Chrome 58和IE 11,以及超过市场份额5%的浏览器。还有许多其它可供选择的配置参数,具体可翻阅[官方文档](https://www.babeljs.cn/docs/babel-preset-env)。 ~~~ { presets: [ [ "@babel/preset-env", { targets: { chrome: "58", ie: "11", browsers: "> 5%" } } ] ] } ~~~ &emsp;&emsp;除了@babel/preset-env之外,官方还给出了其它实用的预设,例如@babel/preset-flow、@babel/preset-react和@babel/preset-typescript等。 &emsp;&emsp;与插件类似,预设也能指定相对或绝对路径,只不过它省略的前缀是“babel-preset-”。假设有个预设名叫@org/babel-preset-name,那么它的相对路径和简写名称如下所示。 ~~~ { "presets": [ "../@org/babel-preset-name", "@org/name" ] } ~~~ **2)stage-x** &emsp;&emsp;这是一组实验性质的预设,囊括了处于提案阶段的标准,TC39委员会将提案分为5个阶段,如表2所示。 :-: ![](https://img.kancloud.cn/fa/83/fa83507f599e86c1231f1e88bb55cebe_1837x450.png) 表2 提案的五个阶段 &emsp;&emsp;以上4个阶段的预设存在着依赖关系,阶段靠前的依赖阶段靠后的(即数字小的包含数字大的),例如@babel/preset-stage-0依赖@babel/preset-stage-1。 &emsp;&emsp;由于这些提案阶段的预设不太稳定,很有可能会被TC39委员会除名或变更,并且混合使用在配置上容易出错,因此从Babel 7开始,它们都将被废弃。 ## 六、@babel/polyfill &emsp;&emsp;虽然env预设(@babel/preset-env)能统一JavaScript的新语法(即高版本编译成低版本),但是无法支持内置的新方法或新对象,例如Promise、Array.of()等。为此,Babel引入了的Polyfill技术(全部打包在@babel/polyfill中),将所缺的特性添加到全局对象中或内置对象的原型上,弥补env预设的不足,从而模拟出完整的ES6+语法和特性。 &emsp;&emsp;@babel/polyfill包含两个模块:regenerator-runtime和[core-js](https://github.com/zloirock/core-js),前者用于编译生成器与异步函数(async和await),后者用于处理其它兼容性问题。@babel/polyfill的安装命令如下所示,注意,使用的参数是--save而不是--save-dev,因为需要在源码之前先执行Polyfill。 ~~~ npm install --save @babel/polyfill ~~~ **1)useBuiltIns** &emsp;&emsp;在env预设中,存在一个与@babel/polyfill有紧密联系的useBuiltIns参数,它有三个关键字可供选择,分别是false、entry和usage,具体说明如下所列。 &emsp;&emsp;(1)false:默认值,不开启Polyfill,显式的配置如下所示。 ~~~ { presets: [ ["@babel/preset-env", { useBuiltIns: false }] ] } ~~~ &emsp;&emsp;(2)entry:加载运行环境(可在targets参数中声明)所需的Polyfill,下面是一个使用Promise的例子。 ~~~ require("@babel/polyfill"); new Promise(); ~~~ &emsp;&emsp;当useBuiltIns参数的值为entry时,这段代码在编译后,就会引入许多不相干的JavaScript文件,造成资源的浪费,如下所示。 ~~~ require("core-js/modules/es6.promise"); require("core-js/modules/es6.array.fill"); require("core-js/modules/es6.math.trunc"); require("core-js/modules/es6.string.fixed"); ...... new Promise(); ~~~ &emsp;&emsp;(3)usage:自动加载源码所需的Polyfill,仍然以Promise为例,如下代码所示,不用再显式的引入@babel/polyfill。 ~~~ new Promise(); ~~~ &emsp;&emsp;当useBuiltIns参数的值为usage时,这段代码在编译后,就会只引入需要的JavaScript文件,如下所示。 ~~~ require("core-js/modules/es6.promise"); require("core-js/modules/es6.object.to-string"); new Promise(); ~~~ ## 七、@babel/runtime &emsp;&emsp;@babel/runtime与@babel/polyfill类似,也是用来增强env预设的,只是它以非侵入式的辅助函数来填补平台所没有的特性,其安装命令如下所示。 ~~~ npm install --save @babel/runtime ~~~ &emsp;&emsp;在Babel 7中还引入了一个@babel/runtime-corejs2,它比@babel/runtime多包含一个core-js模块,即能够编译Promise、Symbol等。接下来以ES6的类为例,如下所示。 ~~~ class People { name() {} } ~~~ &emsp;&emsp;在将People类编译后,得到的结果如下所示,省略了三个函数中的逻辑代码。 ~~~ "use strict"; function _classCallCheck(instance, Constructor) { } function _defineProperties(target, props) { } function _createClass(Constructor, protoProps, staticProps) { } var People = /*#__PURE__*/ function () { function People() { _classCallCheck(this, People); } _createClass(People, [{ key: "name", value: function name() {} }]); return People; }(); ~~~ **1)@babel/plugin-transform-runtime** &emsp;&emsp;由于编译生成的辅助函数会滞留在所使用的文件中,因此文件越多冗余的函数就越多。如果人工分离,那工作量将巨大,因此Babel提供了能自动将它们分离的@babel/plugin-transform-runtime插件,其安装命令如下所示。 ~~~ npm install --save-dev @babel/plugin-transform-runtime ~~~ &emsp;&emsp;仍然以ES6的People类为例,先在配置文件中将其声明,如下所示。 ~~~ { plugins: ["@babel/plugin-transform-runtime"] } ~~~ &emsp;&emsp;然后再将类编译,三个辅助函数就能通过引入的方式得到,如下所示。 ~~~ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); ~~~ ## 八、动态编译 &emsp;&emsp;在Babel中有两种方式实现动态编译,分别是@babel/register和@babel/node,它们的安装命令如下所示。 ~~~ npm install --save-dev @babel/register npm install --save-dev @babel/node ~~~ &emsp;&emsp;虽然动态编译省去了手动操作,但是会损耗程序的速度和性能,因此不适合生产环境。 **1)@babel/register** &emsp;&emsp;在将其安装后,就会为Node.js的require()函数增加一个钩子。每当通过require()加载后缀为.es6、.es、.jsx、.mjs或.js的文件时,就能自动对其进行Babel编译。接下来用一个例子来演示@babel/register的用法,首先创建一个src.js的文件,其代码如下所示。 ~~~ module.exports = () => true; ~~~ &emsp;&emsp;然后创建一个default.js文件,引入@babel/register和之前的src.js,如下所示。 ~~~ require("@babel/register"); var code = require("./src.js"); console.log(code.toString()); ~~~ &emsp;&emsp;最后在命令行工具中输入“node default.js”,打印出编译后的匿名函数,如下所示。 ~~~ function () { return true; } ~~~ &emsp;&emsp;在@babel/register中,如果需要使用Polyfill,那么得逐个引入。还要注意,它默认不会编译node\_modules目录中的模块,但是可以通过ignore参数修改忽略规则来覆盖其行为。 &emsp;&emsp;ignore参数是一个由正则表达式和函数组成的数组(如下代码所示),如果要让文件不被编译,那么得将其路径与正则表达式匹配或函数返回true,其中函数的参数就是文件路径。 ~~~ require("babel-register")({ ignore: [ /regex/, function(filepath) { return true; } ] }); ~~~ &emsp;&emsp;另外还有一个only参数,其值也是一个数组,同样也用来设置文件不被编译的条件。只是其规则与ignore参数正好相反,即路径与正则表达式不匹配或函数返回false时,才不编译。 &emsp;&emsp;@babel/register还可以接收其它参数,例如extensions、cache等,并且会合并配置文件中的信息,作为其参数传递进来。 **2)@babel/node** &emsp;&emsp;在Babel 7之前,@babel/cli会自带babel-node命令,但之后,官方将其拆成一个单独的包:@babel/node。与@babel/register不同,@babel/node不需要调整源码,直接一个命令就能实现动态编译,如下代码所示,其中src就是之前的src.js文件。 ~~~ npx babel-node src ~~~ ***** > 原文出处: [博客园-前端利器躬行记](https://www.cnblogs.com/strick/category/1472499.html) [知乎专栏-前端利器躬行记](https://zhuanlan.zhihu.com/pwtool) 已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。