织梦网站栏目不能更新,晋江论坛怎么贴图,做网站有自己的服务器,网站建设太金手指六六六Hi#xff0c;大家好#xff0c;我是小庄。 在我们平时写代码的过程中可能没有感觉Babel的存在#xff0c;但其实只要我们写JS代码#xff0c;Babel已经无处不在、无时不刻的在影响着我们的代码。 因此这篇文章#xff0c;我们就对Babel的配置以及使用做一个深入的学习和总…Hi大家好我是小庄。 在我们平时写代码的过程中可能没有感觉Babel的存在但其实只要我们写JS代码Babel已经无处不在、无时不刻的在影响着我们的代码。 因此这篇文章我们就对Babel的配置以及使用做一个深入的学习和总结。
一、Babel是什么
Babel官网对Babel的定义就是 Babel 是一个 JavaScript 编译器。 用通俗的话解释就是它主要用于将高版本的JavaScript代码转为向后兼容的JS代码从而能让我们的代码运行在更低版本的浏览器或者其他的环境中。
比如我们在代码中使用了ES6箭头函数
var fn (num) num 2;但我们如果用IE11浏览器鬼知道用户会用什么浏览器来看运行的话会出现报错但是经过Babel编译之后的代码就可以运行在IE11以及更低版本的浏览器中了
var fn function fn(num) {return num 2;
}Babel就是做了这样的编译转换工作来让我们不用考虑浏览器的兼容性问题只要专心于代码的编写工作。
二、Babel的历史
Babel的前身是从6to5这个库发展而来6to5的作者是Facebook的澳大利亚工程师Sebastian McKenzie在2014年发布的从它的名字我们也能看出来主要的功能就是将ES6转成ES5我们如今也还能在npm官网看到这个包不过作者提示已经迁移到Babel了
在2015年1月份6to5和Esnext的团队决定一起开发6to5并且改名为Babel解析引擎改名为Babylon。
三、Babel和Babylon的含义 Babylon的翻译是巴比伦意指巴比伦文明。 Babel的翻译是巴别塔又名通天塔当时地上的人们都说同一种语言当人们离开东方之后他们来到了示拿之地。在那里人们想方设法烧砖好让他们能够造出一座城和一座高耸入云的塔来传播自己的名声以免他们分散到世界各地。上帝来到人间后看到了这座城和这座塔说一群只说一种语言的人以后便没有他们做不成的事了于是上帝将他们的语言打乱这样他们就不能听懂对方说什么了还把他们分散到了世界各地这座塔也停止了修建这座塔就被称为“巴别塔”。
四、Babel版本及区别 2015-02-156to5重命名为babel 2015-03-31babel 5.0发布 2015-10-30babel 6.0发布 2018-08-27babel 7.0发布
babel5及之前是一个包含CLI工具编译器转换器的集合工具包babel6之后进行了拆分集合包被分成多个包 babel-cli其中包含babel命令行界面 babel-core包括了Node有关的API和require钩子 babel-polyfill可以建立一个完整的ES2015环境
babel6默认情况下不携带任何转换器需要自行安装所需的插件和转换器通过babel-xxx来安装对应的工具包。
而Babel7用了npm的private scope把所有的包都挂载babel下通过babel/xxx来安装不用在node_modules下看到一堆的babel-xxx包。
本文主要以Babel7作为开发工具并进行详解。
五、Babel7详解
babel/core
babel/core我们在很多地方都看到它是Babel进行转码的核心依赖包。
我们常用的babel/cli和babel/node都依赖于它即当用户要安装和使用babel/cli、babel/node时就必须先安装babel/core。
babel/core中包含多个模块而实现转码功能的主要模块为以下三个 babel/parser 、 babel/traverse 、 babel/generator 如下我们查看下babel/core源码中的package.json 而那三个模块的功能分别如下
babel/parser: babel/core中parse阶段的主要功能模块。用于接受源码进⾏词法分析、语法分析⽣成AST 补充babel/parser已经内置支持很多语法. 例如JSX、Typescript、Flow、以及最新的ECMAScript规范。目前为了执行效率babel/parser是不支持扩展的由官方进行维护。如果你要支持自定义语法可以 fork 它不过这种场景非常少。 babel/traversebabel/core中transform阶段的主要功能模块。用于接受⼀个AST并对其遍历根据babel配置中的preset、plugin进⾏逻辑处理进⾏替换、删除、添加节点生成最终的AST babel/generatorbabel/core中gernerate阶段的主要功能模块。用于接受最终⽣成的AST并将其转换为代码字符串同时此过程也可以创建source map
三个模块相互配合其转码流程为
input string - babel/parser parser - AST - transformer[s] - AST - babel/generator - output string下面我们通过实践简单例子来看一下babel/core是如何来进行解析
var babelCore require(babel/core);
var sourceCode let fn (num) num 2;
var options {//是否生成解析的代码code: true,//是否生成抽象语法树ast: true,//是否生成sourceMapsourceMaps: true,plugins: [],presets: [],
};
babelCore.transform(sourceCode, options, function (err, result) {console.log(sourceCode);console.log(result.code);console.log(result.map);console.log(result.ast);
});可以发现原来的es6箭头函数在结果中几乎原封不动的返回出来了。
为什么会这样呢
正如上面所说babel/core中的第二个模块babel/traverse它需要根据babel配置中的preset、plugin进⾏逻辑处理然后进⾏替换、删除、添加节点生成最终的AST。因此当我们不添加任何插件的时候输入输出代码是相同的。
同时我们可以看到在babel/core转换时还有几个副产物code、ast和map我们可以通过options配置根据需要对这几个副产物进行选择性的输出。 Options配置文档https://www.babeljs.cn/docs/options 除了transform这个转换方法还有transformSync、transformAsync和transformFileSync等同步异步API都可以在babel官方文档找到。 babel官方文档https://www.babeljs.cn/docs/ babel/cli
babel/cli是Babel自带了一个内置的CLI命令行工具我们就可以通过命令行来编译文件它有两种调用方式可以通过全局安装或者本地安装调用选用一种即可推荐在项目本地安装。
//全局安装调用
npm install --g babel/cli
babel index.js -o output.js
//本地安装调用
npm install --save-dev babel/cli
npx babel index.js -o output.jsbabel/cli还可以使用以下命令参数
配置文件
我们虽然可以在命令行中配置各种插件plugins或者预设presets也就是一组插件但是这样并不利于后期的查看或者维护而且大多时候babel都是结合webpack或者gulp等打包工具开发不会直接通过命令行的方式因此Babel推荐通过配置文件的方式来进行管理。
Babel的配置文件主要有.babelrc、.babelrc.js、babel.config.js和package.json他们的配置选项都是相同的作用也是一样主要区别在于格式语法的不同因此我们在项目中只需要选择其中一种即可。
对于.babelrc它的配置主要是JSON格式的像这样
{presets: [...],plugins: [...]
}而.babelrc.js和babel.config.js同样都是JS语法通过module.exports输出配置
module.exports function (api) {api.cache(true);const presets [ ... ];const plugins [ ... ];if (process.env[ENV] prod) {plugins.push(...);}return {presets,plugins};
}我们还可以根据环境来进行动态的配置。而在package.json中需要增加babel的属性
{name: demo,version: 1.0.0,babel: {presets: [ ... ],plugins: [ ... ],}
}我们可以在配置文件中加入一些插件或者预设来扩展babel/core的转换功能只需要将对应的插件或预设名字加入数组即可比如我们常用的ES6箭头函数就是通过babel/plugin-transform-arrow-functions这个插件来转换
//.babelrc
{plugins: [babel/plugin-transform-arrow-functions]
}但有时候我们需要对插件和预设设置参数就不能直接使用字符串的形式了而应再包裹一层数组数组的第一项是名称第二项是设置的参数对象
//.babelrc
{plugins: [[babel/plugin-transform-arrow-functions, { spec: true }]]
}这样我们的箭头函数就能正常转换了。
Babel插件和预设
Babel的插件大致可以分为语法插件和转换插件 语法插件作用于babel/core中的parse阶段使得babel能够解析更多的语法官方的语法插件以babel/plugin-syntax开头 转换插件作用于babel/core中的transform阶段负责转换 AST 的形态官方的转换插件以babel/plugin-transform正式或者 babel/plugin-proposal提案开头。 补充1转换插件将启用相应的语法插件因此不必同时指定这两种插件。 补充2语法插件虽名为插件但其本身并不具有功能性。正如上面说了 babel/parser 已经支持了很多 JavaScript语法特性同时babel/parser也不支持扩展。因此babel/plugin-syntax-*实际上只是用于开启或者配置babel/parser的某个功能特性。但一般用户不需要关心这个用户也可以通过parserOpts配置项来直接配置babel/parser。 Babel官网提供了近一百个插件但是如果我们的代码中一个一个的配置插件就需要对每一个插件有所了解这样必然会耗费大量的时间精力为此Babel提供了预设presets的概念意思就是预先设置好的一系列插件包这就相当于肯德基中的套餐将众多产品进行搭配组合适合不同的人群需要总有一款适合我们的套餐。
比如babel/preset-es2015就是用来将部分ES6语法转换成ES5语法babel/preset-stage-x可以将处于某一阶段的js语法编译为正式版本的js代码而babel/preset-stage-x也已经被Babel废弃了有兴趣的童鞋可以看这篇官方的文章。
我们实际会用到的预设有以下 babel/preset-env babel/preset-flow babel/preset-react babel/preset-typescript
根据名字我们可以大致猜出每个预设的使用场景我们重点了解一下babel/preset-env它的作用是根据环境来转换代码。
执行顺序
插件和预设都是通过数组的形式在配置文件中配置如果插件和预设都要处理同一个代码片段那么会根据一下执行规则来判定 插件比预设先执行 插件执行顺序是插件数组从前向后执行 预设执行顺序是预设数组从后向前执行
babel/preset-env
我们来看一下官网对它的描述 babel/preset-env是一个智能预设可让您使用最新的JavaScript而无需微观管理目标环境所需的语法转换以及可选的浏览器polyfill。这都使您的生活更轻松JavaScript包更小 我们在项目中不会关心Babel用了哪些插件支持哪些ES6语法我们更多关心的是支持哪些浏览器版本这个层面比如我们在项目中使用了箭头函数、Class、Const和模板字符串
let fun () console.log(hello babel.js);
class Person {constructor(name) {this.name name;}say() {console.log(my name is${this.name});}
}
const tom new Person(tom);
tom.say();但是假如我们的项目需要支持IE10因此我们需要修改.babelrc
{presets: [babel/preset-env]
}或者对它进行缩写
{presets: [babel/env]
}通过Babel编译后输出
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 fun function fun() {return console.log(hello babel.js);
};
var Person /*#__PURE__*/function () {function Person(name) {_classCallCheck(this, Person);
this.name name;}
_createClass(Person, [{key: say,value: function say() {console.log(my name is\uFF1A.concat(this.name));}}]);
return Person;
}();
var tom new Person(tom);
tom.say();可以发现虽然我们没有配置任何转换插件但是上面写的的箭头函数、Class、Const和模板字符串语法都已经被转换了默认情况下babel/env等于babel/preset-es2015、babel/preset-es2016和babel/preset-es2017三个套餐的叠加。
那如果我们只需要支持最新的Chrome了可以继续修改.babelrc
{presets: [[babel/env,{targets: last 2 Chrome versions}]]
}targets中的含义是最新的两个Chrome版本Babel再次编译输出
use strict;
let fun () console.log(hello babel.js);
class Person {constructor(name) {this.name name;}
say() {console.log(my name is${this.name});}
}
const tom new Person(tom);
tom.say();而最新版本的Chrome已经支持箭头函数、Class、Const和模板字符串所以在编译时不会在进行转换。
上面的target字段不少同学肯定看着很眼熟这个工具能够根据项目中指定的目标浏览器自动来进行配置这里我们就不展开深入讨论了它也可以单独在项目中配置一个.browserslistrc文件
last 2 Chrome versions这样和targets字段的使用效果是一样的正常情况下推荐使用browserslist的配置而很少单独配置babel/preset-env的targetsbabel/preset-env有一些常用的配置项让我们来看一下
targets
虽然targets不推荐使用但是我们还是来了解一下它的用法它是用来描述我们在项目中想要支持的目标浏览器环境它可以是Browserslist格式的查询
{targets: 0.25%, not dead
}或者可以是一个对象用来描述支持的最低版本的浏览器
{targets: {chrome: 58,ie: 11}
}其他的浏览器版本还可以是opera、edge、firefox、safari、ios、android、node、electron等。
spec
这个属性主要是给其他插件传递参数比如babel/plugin-transform-arrow-functions默认是false设为true后我们的箭头函数会有以下改变
将箭头函数生成的函数用.bind(this)包裹一下以便在函数内部继续使用this而不是重命名this。
加一个检查防止函数被实例化
给箭头函数加一个名字
loose
这个属性也主要是给其他插件传递参数比如babel/plugin-transform-classes默认是false类的方法直接定义在构造函数上而设置为true后类的方法被定义到了原型上面这样在类的继承时可能会引起问题。
include
转换时总是会启用插件的数组格式是Arraystring|RegExp它可以是一下两种值 Babel插件 内置的core-js比如es.mapes.set等
比如我们在last 2 Chrome versions目标浏览器环境下不会转换箭头函数和Class但是我们可以将转换箭头函数的插件配置到include中这样不管我们的目标浏览器怎么更换箭头函数语法总是会转换
{presets: [[babel/env,{targets: last 2 Chrome versions,include: [babel/plugin-transform-arrow-functions]}]]
}useBuiltIns和corejs
useBuiltIns这个属性决定是否引入polyfill可以配置三个值false不引入、usage按需引入和entry项目入口处引入corejs表示引入哪个版本的core-js可以选择2默认或者3只有当useBuiltIns不为false时才会生效。
babel/polyfill
虽然babel/preset-env可以转换大多高版本的JS语法但是一些ES6原型链上的函数比如数组实例上的的filter、fill、find等函数以及新增的内置对象比如Promise、Proxy等对象是低版本浏览器本身内核就不支持因此babel/preset-env面对他们时也无能为力。
比如我们常用的filter函数在IE浏览器上就会出现兼容性问题因此我们通过polyfill垫片的方式来解决下面是filter函数简单的兼容代码
if (!Array.prototype.filter) {Array.prototype.filter function (fun /*, thisp*/ ) {var len this.length;if (typeof fun ! function) {throw new TypeError();}var res new Array();var thisp arguments[1];for (var i 0; i len; i) {if (i in this) {var val this[i];if (fun.call(thisp, val, i, this)) {res.push(val);}}}return res;};
} 但是ES有那么多函数和内置对象我们不可能一个一个都手写来解决这就到了babel/polyfill用武之处了首先我们需要在项目中安装它
npm install --save babel/polyfill安装完成后在需要转换的文件入口加入引用代码
import babel/polyfill或者我们也可以在Webpack入口处进行引入
module.exports {entry: [babel/polyfill, ./src/index.js],
};然后通过webpack来打包这样就能看到在我们的代码中加入了很多的兼容代码。
发现我们数组的fill、filter和findIndex等方法都打包进去了但是看到这么多密密麻麻的兼容代码眼尖的童鞋肯定会发现以下两个问题
打包出来生成的文件非常的大有一些语法特性可能是我们没用到的但是Webpack不管三七二十一全都引用进去了导致打包出来的文件非常庞大。
污染全局变量polyfill给很多类的原型链上添加函数如果我们开发的是一个类库给其他开发者使用这种情况会非常不可控。
因此从Babel7.4开始babel/polyfill就不推荐使用了而是直接引入core-js与regenerator-runtime两个包而babel/polyfill本身也是这两个包的集合在上面webpack打包出来的dist文件我们也可以看到引用的也是这两个包。那core-js到底是什么呢 它是JavaScript标准库的polyfill 它尽可能的进行模块化让你能选择你需要的功能 它和babel高度集成可以对core-js的引入进行最大程度的优化
目前我们使用的默认都是core-js2但它已经封锁了分支在此之后的特性都只会添加到core-js3因此也是推荐使用最新的core-js3。
babel/preset-env与core-js
在上面babel/preset-env配置中有useBuiltIns和corejs两个属性是用来控制所需的core-js版本我们以Object.assign、filter和Promise为例
Object.assign({}, {});
[(1, 5, 10, 15)].filter(function (value) {return value 9;
});
let promise new Promise((resolve, reject) {resolve(1);
});然后修改配置文件如果我们将useBuiltIns配置为非false而没有指定corejs的版本Babel会提示我们需要配置corejs的版本 秉承着用新不用旧的原则毅然选择core-js3
{presets: [[babel/preset-env,{useBuiltIns: usage,corejs: 3}]]
}可以看到我们的打包的文件自动引入了core-js中的模块
use strict;
require(core-js/modules/es.array.filter);
require(core-js/modules/es.object.assign);
require(core-js/modules/es.object.to-string);
require(core-js/modules/es.promise);
Object.assign({}, {});
[(1, 5, 10, 15)].filter(function (value) {return value 9;
});
var promise new Promise(function (resolve, reject) {resolve(1);
});而且我们发现它只引入了部分模块这就比较厉害了它不仅会考虑到代码中用到的新特性还会参考目标浏览器的环境来进行按需引入而useBuiltIns设置为entry的情况则会将core-js中的模块在入口处全部引入这里就不再演示。
babel/runtime
我们在上面通过babel/preset-env转换Class类时发现输出文件的头部多了_classCallCheck、_defineProperties和_createClass三个函数声明这就是注入的函数称为辅助函数babel/preset-env在转换时注入了函数声明以便语法转换后使用。
但是我们开发项目时文件少则几十个多个上百个如果每个文件都注入了函数声明再通过打包工具打包后输出文件又会非常庞大影响性能。
因此Babel提供的解决思路是把这些辅助函数都放到一个npm包里面在每次需要使用的时候就从这个包里把函数require出来这样即使有几千个文件也都是对函数进行引用而不是复制代码最后通过webpack等工具打包时只会将npm包中引用到的函数打包一次这样就复用了代码减少打包文件的大小。
babel/runtime就是这些辅助函数的集合包我们查看babel/runtime下面的helpers可以发现导出了很多函数以及我们上面提及到的_classCallCheck函数
首先当然是需要安装babel/runtime这个包除此之外还需要安装babel/plugin-transform-runtime这个插件的作用是移除babel/preset-env注入的辅助函数将其替换为babel/runtime/helpers中函数的引用。
npm install --save babel/runtime
npm install --save-dev babel/plugin-transform-runtime然后修改我们的配置文件
{presets: [babel/env],plugins: [babel/transform-runtime]
}再次打包发现我们的辅助函数已经变成下面的引用方式了
var _interopRequireDefault require(babel/runtime/helpers/interopRequireDefault);
var _classCallCheck2 _interopRequireDefault(require(babel/runtime/helpers/classCallCheck));
var _createClass2 _interopRequireDefault(require(babel/runtime/helpers/createClass));babel/plugin-transform-runtime
上面我们说到babel/polyfill会建立一个完整的ES2015环境因此造成了全局变量的污染虽然使用core-js不会引入全部模块但是也会污染部分全局变量。
而babel/plugin-transform-runtime除了能够转换上面的辅助函数还能对代码中的新特性API进行一个转换还是以我们的filter函数和Promise对象为例
[1, 5, 10, 15].filter((value) {return value 9;
});
let promise new Promise((resolve, reject) {resolve(1);
});然后修改我们的配置文件.babelrc
{presets: [babel/env],plugins: [[babel/transform-runtime,{corejs: 3}]]
}再次查看打包出来的文件发现filter和Promise已经转换成了引用的方式
use strict;
var _interopRequireDefault require(babel/runtime-corejs3/helpers/interopRequireDefault);
var _promise _interopRequireDefault(require(babel/runtime-corejs3/core-js-stable/promise));
var _filter _interopRequireDefault(require(babel/runtime-corejs3/core-js-stable/instance/filter));
var _context;
(0, _filter[default])(_context [1, 5, 10, 15]).call(_context, function (value) {return value 9;
});
var promise new _promise[default](function (resolve, reject) {resolve(1);
});我们发现打包出来的模块是从babel/runtime-corejs3这个包里面引用的同时我们可以发现 babel/runtime-corejs2 ≈ babel/runtimecore-jsregenerator ≈ babel/runtimebabel/polyfill babel/polyfill和babel/runtime-corejs的区别
经过下面这么多例子总结一下babel/polyfill和babel/runtime-corejs的区别前者改造目标浏览器让你的浏览器拥有本来不支持的特性后者改造你的代码让你的代码能在所有目标浏览器上运行但不改造浏览器。
一个显而易见的区别就是打开IE11浏览器如果引入了babel/polyfill在控制台我们可以执行Object.assign({}, {})而如果引入了babel/runtime-corejs会提示你报错因为Object上没有assign函数。
六、参考文档
https://www.babeljs.cn/docs/
https://zhuanlan.zhihu.com/p/326824078
七、补充部分
关注公众号【深漂程序员小庄】 内含丰富的学习资源和面试经验不限前端、java、算法还有学习交流群可加并且还有各大厂大佬可一起交流学习一起进步添加小庄微信回复【加群】可加入互联网技术交流群