babel对我来讲其实一直都是黑盒,我只知道用它能够转换js代码以兼容低版本的浏览器,可是具体是怎么配置的,从未深刻了解过,此次我就记录一下babel的配置过程git
PS: 因为babel 7.4版本较以前变化很大,这里咱们就以最新版为准github
这里咱们新建一个项目,从零开始配置babelweb
mkdir babel-test cd babel-test npm init -y npm install --save-dev @babel/core @babel/cli 复制代码
其中@babel/core
是babel的核心包,这里必装,@babel/cli
是babel提供的命令行工具,能够在终端中直接使用或配合npm scripts
使用,用来生成转换以后的js文件chrome
下面咱们新建src
目录,并添加index.js
文件用来测试代码转换的效果:npm
mkdir src cd src touch index.js 复制代码
在index.js
中写入如下代码:json
const message = "hello world" const say = (message) => { console.log(message) } say(message) 复制代码
这里咱们直接运行是能够的,可是若是在低版本浏览器,好比IE11
中,那么就会报错,由于const
和arrow function
是ES2015
/ES6
标准,IE11
并不支持,因此咱们须要用babel来进行转换promise
这里咱们用npm script
配置转换脚本,打开package.json
,修改以下:浏览器
...
"scripts": {
"build": "babel src --out-dir lib"
},
...
复制代码
其中out-dir
用来指定转换以后的文件输出位置,这里咱们将转换后的文件都放在lib
目录下bash
除了上面的转换指令,咱们还须要一个配置文件,在项目根目录新建.babelrc
,修改以下:babel
{ "presets": [ [ "@babel/preset-env", { "targets": { "ie": 11 } } ] ], "plugins": [] } 复制代码
这里咱们使用了@babel/preset-env
,它是babel官方提供的一个智能预设包,其中包含了不少经常使用的插件并提供了一些配置项,这里咱们使用targets
将代码最终要运行的浏览器指定为了IE11
,那么转换后的代码就能够在IE11
上运行。
PS: @babel/preset-env这个包的更多配置项能够参考官方文档:babeljs.io/docs/en/bab…
这里预设包须要手动安装
npm install --save-dev @babel/preset-env
复制代码
而后咱们执行转换脚本
npm run build
复制代码
转换成功后,咱们会在lib目录下看到index.js文件,其中代码以下:
"use strict"; var message = "hello world"; var say = function say(message) { console.log(message); }; say(message); 复制代码
可见const
和arrow function
语法都转换成了ES5语法,能够在IE11中运行
若是咱们将targets改成chrome,并将版本指定为80,转换后的代码仍是跟原来同样的,由于chrome 80是支持ES6语法的
"targets": {
"chrome": 80
}
复制代码
targets是能够同时指定多个浏览器的,可是最终转换的代码仍是以最低版本为准,好比配置以下:
"targets": {
"ie": 11,
"chrome": 80
}
复制代码
代码会转换为ES5语法
在介绍polyfill以前,咱们将index.js中的代码修改为这样:
const message = "hello world"; const say = message => { console.log(message); }; function delay(time = 1000) { return new Promise(resolve => { setTimeout(() => { console.log(new Date().getSeconds()); resolve(); }, time); }); } console.log(new Date().getSeconds()); delay(3000).then(() => { say(message); }); 复制代码
这里咱们用ES6标准中才存在的Promise
语法写了一个delay
函数,hello world
将在3s后输出
咱们执行npm run build
后,转换后的代码为:
"use strict"; var message = "hello world"; var say = function say(message) { console.log(message); }; function delay() { var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000; return new Promise(function (resolve) { setTimeout(function () { console.log(new Date().getSeconds()); resolve(); }, time); }); } console.log(new Date().getSeconds()); delay(3000).then(function () { say(message); }); 复制代码
这里咱们能够看到const
和arrow function
语法都正常转换为了ES5标准,可是Promise
仍是和以前同样,若是在IE11中运行会提示Promise is not defined
这是由于babel是能够直接转换基础语法的也就是syntax
, 可是ES6+
标准下的新特性也就是features
,babel是不能直接转换的,须要借助polyfill
来实现,polyfill翻译成中文就是垫片的意思,用来垫平不一样浏览器环境以前差别
syntax是指一些基础语法,babel是能够直接转换的,好比:
features是指ES6+
标准推出的一些新特性,babel不能直接转换,好比:
@babel/babel-polyfill
这个包就能够实现上面的features,从而完整的模拟ES2015+
环境。可是在babel 7.4版本中已经明确表示不推荐使用了,官方建议咱们使用core-js
来替代,其实babel-polyfill内部就是用core-js
和regenerator-runtime/runtime
来实现的。
以前咱们应该是直接在入口文件顶部这样使用
import "@babel/polyfill" 复制代码
如今能够直接改为这样
import "core-js/stable"; import "regenerator-runtime/runtime"; 复制代码
core-js
是JavaScript的模块化标准库,它包含了ECMAScript全部标准的polyfill实现
regenerator-runtime
主要是为了生成器函数提供运行时
下面咱们就来实现features的转换,首先咱们须要安装如下包
npm install --save core-js regenerator-runtime
复制代码
修改.babelrc以下
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "entry", "corejs": { "version": 3 }, "targets": { "ie": 11 } } ] ], "plugins": [] } 复制代码
其中useBuildIns
是用来决定如何使用polyfill:
这里咱们先使用entry
,除此以外咱们还指定了corejs的版本,而后咱们在文件顶部手动引入polyfill也就是core-js:
import "core-js/stable"; import "regenerator-runtime/runtime"; const message = "hello world"; ... 复制代码
而后执行npm run build
,会发现转换后的代码里面引入了全部polyfill,包括咱们须要的Promise
:
... require("core-js/modules/es.object.to-string"); require("core-js/modules/es.object.values"); require("core-js/modules/es.promise"); ... 复制代码
这样,咱们的代码就能够在低版本浏览器中使用了。
这两个包是一块儿使用的,主要是为了解决转换以后代码重复使用而形成的包体积较大的问题,由于babel在转换代码时会使用一些helpers辅助函数,好比下面的代码:
async function delay() { console.log(new Date().getSeconds()); await new Promise(resolve => { setTimeout(() => { resolve(); }, 3000); }); console.log(new Date().getSeconds()); } delay(); 复制代码
转换以后,咱们会发现生成的代码除了一些polyfill和实际的代码以外,还有一些helpers代码:
...
function asyncGeneratorStep(gen, resolve, r...
function _asyncToGenerator(fn) { retu...
...
复制代码
若是有不少文件须要转换,那这些代码可能就会重复,为了解决这个问题,咱们可使用plugin-transform-runtime
将这些helpers辅助函数的使用方式改成引用的方式,让它们都去引用runtime
包里的代码,这样他们就是重复引用同一个代码,就不会出现重复的问题了。其中babel-runtime
这个包里面就包含了全部的helpers辅助函数。
咱们须要手动安装:
npm install --save @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
复制代码
.babelrc
修改以下:
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "entry", "corejs": { "version": 3 }, "targets": { "ie": 11 } } ] ], "plugins": [ "@babel/plugin-transform-runtime" ] } 复制代码
这样转换以后,那些helpers代码就变成了require引入的方式:
... var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); ... 复制代码
上面咱们提到了咱们可使用useBuiltIns
的usage
选项来按需加载polyfill,这种主要是在咱们开发第三方库时使用,这里修改index.js中的代码以下:
new Promise(resolve => { resolve(); }); const arr = [1, 2, 3]; console.log(arr.includes(3)); 复制代码
其中.babelrc
改为以下配置:
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage", "corejs": { "version": 3 }, "targets": { "ie": 11 } } ] ], "plugins": [ "@babel/plugin-transform-runtime" ] } 复制代码
转换以后生成的文件就是这样:
"use strict"; require("core-js/modules/es.array.includes"); require("core-js/modules/es.object.to-string"); require("core-js/modules/es.promise"); new Promise(function (resolve) { resolve(); }); var arr = [1, 2, 3]; console.log(arr.includes(3)); 复制代码
这里咱们能够看到,咱们只引用了部分polyfill,可是这里又一个问题,那就是polyfill是注入到全局做用域中的,使用咱们库的开发者不必定愿意污染全局做用域,因此说,合理的解决方案应该是注入到当前做用域中,不影响全局做用域,咱们修改配置以下:
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage", "corejs": { "version": 3 }, "targets": { "ie": 11 } } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": { "version": 3 } } ] ] } 复制代码
转换以后,代码以下:
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); new _promise.default(function (resolve) { resolve(); }); var arr = [1, 2, 3]; console.log((0, _includes.default)(arr).call(arr, 3)); 复制代码
这样咱们就完美解决了做用域污染的问题
PS: @babel/plugin-transform-runtime
这个包是不能读取@babel/preset-env
包的targets
选项的配置的,若是咱们配置了这个包的corejs
选项,它会把咱们代码中全部用到的Features都转化为对corejs提供的polyfill的引用,好比咱们把上面代码中的targets改成chrome 80,转换以后的代码仍是会包含promise这个polyfill,关于这个问题我发了issues: github.com/babel/websi…,官方建议是:这个包的corejs选项主要是为了开发第三方库时使用,由于开发者没法控制库的浏览器运行环境。
在实际开发中,除了使用ECMAScript标准中已存在的语法,咱们还可使用一些在提案中,可是尚未正式发布的语法,好比String.prototype.replaceAll
index.js代码以下:
const queryString = "q=query+string+parameters"; const withSpaces = queryString.replaceAll("+", " "); console.log(withSpaces); 复制代码
转换以后的代码:
"use strict"; var queryString = "q=query+string+parameters"; var withSpaces = queryString.replaceAll("+", " "); console.log(withSpaces); 复制代码
这里咱们发现语法并无转换,这里咱们就须要配置proposals
以转换这些还在提案中的语法:
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage", "corejs": { "version": 3, "proposals": true }, "targets": { "ie": 11 } } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": { "version": 3, "proposals": true } } ] ] } 复制代码
转换以后:
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _replaceAll = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/replace-all")); var queryString = "q=query+string+parameters"; var withSpaces = (0, _replaceAll.default)(queryString).call(queryString, "+", " "); console.log(withSpaces); 复制代码
这样咱们就能够愉快的进行开发了