Babel有两大功能,转译和polyfill。转译就是把新的JS的语法,转化成旧的JS的语法。polyfill则是针对JS中新增的一些对象(Map, Set)和实例方法,这些对象和方法,在旧的浏览器中肯定没有,如果使用它们,肯定也会报错,但你又想使用它们,那怎么办? 很简单,既然没有,就手动实现它们,用旧的浏览器支持的语法实现它们,只要把实现引进到我们写的程序中,就相当旧的浏览器也拥有了这些新的对象和方法,在程序中就可以大胆地使用它们了,这些用旧的浏览器支持的语法实现新的对象和方法的实现或模式就是polyfill. 举一个例子,promise对象在ES2019中添加了一个finally()方法,旧的浏览器中肯定没有,如果在旧的浏览器中使用它,就会报错,因此需要为旧的浏览器手动实现这个方法。

if (!Promise.prototype.finally) {
Promise.prototype.finally = function f(fn){
return this.then(
function t(v){
return Promise.resolve( fn() )
.then(function t(){
return v;
});
},
function c(e){
return Promise.resolve( fn() )
.then(function t(){
throw e;
});
}
);
};
}

  这就是finally()方法的polyfill,如果我们在代码中引用这个polyfill,  就相当于旧的浏览器中有了finally()方法,程序中再使用finally()方法,就不会报错了,程序就可以运行中各个浏览器上了。你也注意到了,polyfill 有一个if 条件判断,这个很好理解,如果浏览器中实现了这个方法,我们就没有必要再手动实现了。if 条件判断,保证了,只为旧的浏览器提供polyfiil, 新的浏览器会自动忽略这个polyfill.

  通过以上分析,可以发现polyfill的重要性,实际上,转译和polyfill已经成了现代JS 开发的一部分,ES标准在发展,而旧的浏览器也会一直存在,为了使用新的语法,但又想让它在旧的浏览器中运行,转译和polyfill 正是有效解决这个问题的方法。Babel则是转译和polyfill的工具,学习Babel 对现代JS开发非常重要。

  Babel转译

  Babel转译JS使用的是插件机制,提供给Babel什么插件,Babel 就会转译什么语法。提供转化箭头函数的插件,Babel就会把遇到的箭头函数全部转化成普通函数。新建一个项目尝试一下,建一个文件夹babel-learning,在其中建一个src目录,在src 下建index.js, (mkdir babel-learning && cd babel-learning && mkdir src && cd src && touch index.js),  最好再建一个package.json 文件(cd ..  && npm init -y),管理项目依赖。提供了插件,怎么调用呢? 还要安装两个包,@babel/cli和 @babel/core

  @babel/cli:  babel 命令集合,在命令行中直接调用babel,对文件进行编译。

  @babel/core: babel的核心,调用插件,转译js 语法,要注意的是,单独使用它,不起作用。

  npm i @babel/core @babel/cli @babel/plugin-transform-arrow-functions --save-dev,这里要注意,babel 7 把babel的包名重写了,以前是 babel-,现在是@babel/,安装完成后,在index.js 文件中写一个箭头函数,

const sum = (a, b ) => a + b;

  然后在命令行中,npx babel src  --out-dir dist --plugins=@babel/plugin-transform-arrow-functions,npx可以直接调用node_modules 中的命令,--out-dir 表示转译后的文件输出到什么地方,  --plugins表示使用哪些插件进行转译。看一下转译后dist目录中的index.js ,箭头函数转化成了普通函数。

  这时你再想,转译一下const ,那就需要提供另外一个babel插件了,随着转译的语法越来越多,需要提供的插件也就越来越多,如果一个一个手动添加,那就有点麻烦了,管理起来也不方便。Babel 提供了一个转译语法的插件集合@babel/preset-env,那它包含哪些插件呢?它能转译哪些语法呢?ECMAScript 官方发布的正式版本,如ES2015 ~ES2020 中的新语法和ECMAScript proposals 中stage 4  中的新语法,这也就是下一年要发布的ECMAScript 版本中的新语法。只要官方定稿的语法,@babel/preset-env 都可以进行转译,那就方便多了,只要安装它这一个,你就可以转译一堆新语法。插件集合称为预设(presets)。从管理一个一个的插件,变成了管理一个预设,我们要做的就是跟踪这个预设,而不是一个个的插件,版本管理方便多了。npm i @babel/preset-env  -- save-dev,此时命令行中,就不要使用--plugins了,要用--presets,你会发现,这么调用babel也有点麻烦。为此,Babel提供了配置文件,当在命令行中调用babel时,它会读取配置文件的内容,可以把plugins和presets都放到配置文件中,而不用放到命令行中。现在官方建议配置文件的命名是babel.config.json,而不是原来的 .babelrc, touch babel.config.json

{
"presets": [
"@babel/preset-env"
]
}
  npx babel src  --out-dir dist,const 和箭头函数都转译了。如果新语法实在太新了,比如 decorator,  官方没有定稿,还在stage-2 阶段,@babel/preset-dev 并没有包含它们,还能不能用?能用,不过要单独为这个新语法(decorator)安装一个插件。npm install --save-dev @babel/plugin-proposal-decorators,  然后在babel.config.json 中配置plugins,在stage <=3 阶段的语法,转译插件名称变成了proposal.
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-decorators"
]
}
  如果安装的presets 和 plugins 越来越多,最好注意一下babel 的执行顺序。先执行plugins, 再执行presets, 如果plugins有多个,按照书写顺序从上到下(或从左到右) 依次执行每一个plugin.  但presets 确相反,如果有多个pesets, 按照书写顺序从下到上或从右到左依次执行,一般来说,顺序不会引起问题。
  说完@babel/preset-env的基本功能,再说一下它的几个配置项。
  modules: 要不要把ES module 转化成CommonJS,默认是true, 也就是说,在默认情况下,Babel会把ES模块转化成CommonJS模块,import/export 语句全都变成require 和 exports。 index.js 修改成
export const sum = (a, b) => a + b;

  npx babel src  --out-dir dist,如果觉得npx命令比较麻烦,可以package.json 的script 中写入"babel": "babel src --out-dir dist" ,在命令行中npm run babel。

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sum = void 0; var sum = function sum(a, b) {
return a + b;
}; exports.sum = sum;

  所有ES module的语法,都变成了CommonJS 语法。把module设为false, 则告诉Babel,  不要把ES module 转化成commonJS module了,import/export语句保留,不要动,转化其它ES语法就好了。With  modules  set  to  false  Babel  will  output  the  transpiled  files  with  their  ESModule  syntax  untouched.  The  rest  of  the  code will be transformed, just the module related statements stay the same.  Babel config 文件改为

presets: [
[
"@babel/preset-env",
{
"modules": false
}
]
]

  npm run babel

export var sum = function sum(a, b) {
return a + b;
};

  ES module 语法没有变化,只把箭头函数的语法转化了。为什么要把module设为false 呢? tree shaking,webpack等打包工具都支持ES module, 并且进行tree shaking。那为什么会把箭头函数进行转化呢?这就是target 配置项

  target:默认情况下,babel会把所有语法都转化成ES5 语法。如果你的JS是运行在新的浏览器中, 那就没有必要全部转译了,只转译那些没有支持的语法就好了。那就要设置target, 告诉babel,你支持哪些浏览器,因此它的值是browserlist, bable能够根据指定的浏览器决定要不要编译最新的语法到旧的语法。

"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"chrome": "58"
}
}
]
]
  npm run babel ,任何语法都没有进行转译,因为chrome 58 已经支持const 和箭头函数了。如果把IE 11 加上
"targets": {
"chrome": "58",
"ie": "11"
}
  npm run  babel, 所有的语法都编译成旧的语法。
  但是一般不在@babel/preset-env这里设置,因为,项目中其它配置工具也需要指定一下browserlist,  可以在.browserslistrc 文件或package.json中配置

"browserslist": [
"Chrome 58",
"IE 11"
]
  Polyfill
  原来有@babel/polyfill, 但现在已经被废弃了,它被拆分成两个库,core-js 和 regenerator-runtime. 现在实现polyfiil有两种方式,一种是配置@babel/preset-env 中的useBuiltIns 和 corejs , 一种是配置@babel/plugin-transform-runtime
  配置@babel/preset-env 中的useBuiltIns 和 corejs。useBuiltIns有三个取值
  1, false, 默认值,也就是说,如果不配置useBuiltIns,  @babel/preset-env 是不会进行polyfill的。
  2,   entry:   在项目的入口文件引入 core-js或 regenerator-runtime/runtime, babel 就会把整个的引入变成一个一个小polyfill的引入,引入哪些小polyfill 是根据browserlist 定义的浏览器目标决定的,这就是按需加载polyfill
  3,usage:  它是按文件进行polyfill,  如果一个文件中用到了一个对象,如promise对象,而browserlist 中定义的浏览器又不支持这个对象,它就会在这个文件的顶部引入polyfill.  也是按需加载polyfill
  corejs 的取值有2, 3等,就是指定core-js 的版本。默认是取值2,现在core-js 支持3,项目中最好配置3,使用core-js的3版本,就要安装它,npm i core-js@3 --save。 如果项目中用到了async/await, 还要安装regenerator-runtime,npm i --save regenerator-runtime

"presets": [
[
"@babel/preset-env",
{
          "modules": false,
"useBuiltIns": "entry",
"corejs": "3.0"
}
]
]

  使用entry,那就在项目的入口文件index.js 中 import 'core-js' 和 import "regenerator-runtime/runtime"

import 'core-js';
import "regenerator-runtime/runtime";
const promise = new Promise();
async function request(){}

  npm run babel, 转译后的index.js引入了200多个的polyfill .

import "core-js/modules/es.symbol.js";
import "core-js/modules/es.symbol.description.js";
import "core-js/modules/es.symbol.async-iterator.js";

  这时把package.json中的 browserslist 中 IE: 11 去掉, 重新npm run babel,  index.js只引入了100多个polyfill, 按需加载polyfill。

  把entry 改成 usage, 并且把index.js 中的import 'core-js'  import "regenerator-runtime/runtime"去掉

{
"useBuiltIns": "usage",
"corejs": "3.0"
}

  npm run build, 在编译后的index.js 中开头部分只引入了3个polyfill。再ie:11 加上,那就只引入了4个polyfill,也是按需加载polyfill.

  Babel现在也可以在对proposals 阶段中的方法进行polyfill, 如果useBuiltIns: "entry", 直接可以在入口文件中引入建议的方法。

import "core-js/proposals/string-replace-all";

  如果使用的是useBuiltIns: "usage"只要corejs 选项配置proposals

{
modules: false,
"useBuiltIns": "usage",
"corejs": { version: "3.0", proposals: true }
}

  @babel/preset-env 中配置core-js,会造成全局变量的污染,core-js 下面定义的都是global polyfill.  require("core-js/modules/es.promise"); 最终的结果是一个globle.Promise对象的存在,在浏览器中就是window.Promise. 如果不想全局变量的污染,@babel/plugin-transform-runtime. npm install --save-dev @babel/plugin-transform-runtime 和npm install --save @babel/runtime, babel配置如下

{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}

  同时把index.js 改成

const promise = new Promise();

  npm run babel, dist/index.js

var promise = new Promise();

  发现并没有polyfill ,Babel 7.4 又后,@babe/runtime没有polyfill 的功能了 。babel 重新提供了两个包@babel/runtime-core2, @babel/runtime-corejs3进行polyfill, 它们分别对应 core-js@ 2和core-js@3, 直接使用3 就可以了。 npm install --save @babel/runtime-corejs3, 同时babel 的配置改一下

"plugins": [["@babel/plugin-transform-runtime", {"corejs": 3}]]

  npm run babel, dist/index.js

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var promise = new _promise.default();

  可以看到polyfill, 注意,它的polyfill来自runtime-corejs3, 看一下@babel/runtime-corejs3,在node_modules下找到它,这个包其实不包含任何代码,它只是把core-js 和regenator-runtime 列为了依赖项(package.json中)。再看一下@babel/runtime-corejs3下面的core-js-stable 目录下的 promise.js , module.exports = require("core-js-pure/stable/promise"); 它引用的core-js-pure 下的文件。core-js-pure 是core-js 不污染全局变量的版本,我们只是在index.js引用了这个promise. 如果使用打包工具webpack的话,这个promise 的实现最终会打包到 最终的bundle文件中。简单看一下这个polyfill 的过程,真正起作用的是 @babel/plugin-transform-runtime, 它把@babel/runtime-core3 node_modules中的 polyfill 插入到要需要polyfill的文件。这也就是index.js 中为什么会有 @babel/runtime-corejs3/core-js-stable。现在@babel/plugin-tranform-runtime也可以对proposal阶段的建议进行polyfill, 当然,默认情况下,它是不会开启,需要进行配置proposal: true

["@babel/plugin-transform-runtime",
{ corejs: 3, proposals: true }
]

  但@babel/plugin-transform-runtime 有一个问题,那就是它不能按需polyfill, 它不管你的target 浏览器,它觉得polyfill, 就会进行polyfill,这就会导致你的polyfiil 后的文件代码增大。@babel/runtime还有没有用,去掉了polyfill的功能,它还剩什么功能?有用,剩下了helper函数。什么是helper 函数?举一个例子就知道了,在src 下建立 main.js, index.js 和main.js 都写一个class 类,

class A {
constructor() {
this.a = 1;
}
}

  把babel.config.json 中的plugins删除一下。npm run babel,转译后的代码中都有

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

  一模一样的函数__classCallCheck, 如果代码中使用大量的类,就会存在大量重复的代码,最终会影响文件的体积, 其实这个函数完全可以抽成一个共用的函数, babel中配置 @babel/runtime,看看发生什么?

"plugins": [["@babel/plugin-transform-runtime"]]

  去掉了core: 3的配置,@babel/plugin-transform-runtime 就是去找@babel/runtime, npm run babel

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var fn = function fn() {
(0, _classCallCheck2.default)(this, fn);
};

  转译后的文件中并没有定义__classCallCheck, 而是require("@babel/runtime/helpers/classCallCheck"), babel 把转译过程中需要的函数都抽成公用的,这些公用的函数都放到了@babel/runtime/ helpers 中.所以称之为helper 函数。

  作为一个应用开发者,我们并不关心全局变量污染的问题,可以使用@babel/preset-env 加上 core-js@3, 同时使用@babel/runtime 中的helper 函数。npm i @babel/preset-env @babel/plugin-transform-runtime  -- save-dev,  npm i core-js@3 @babel/runtime --save, 如果使用async/await, 再npm i regenerator-runtime

{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "60",
"ie": 11
},
"useBuiltIns": "entry",
"corejs": 3
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}

  useBuiltIns: "entry",  要在项目的入口文件 import 'core-js'  和 import "regenerator-runtime/runtime";(如果项目中使用async/await).

  如果你是一个库的作者,最好不要污染使用者的全局变量,那就用@babel/plugin-transform-runtime 和 @babel/runtime-core@3 进行polyfill ,使用@babel/preset-env 进行语法转译。 npm i @babel/preset-env @babel/plugin-transform-runtime  -- save-dev,  npm i @babel/runtime-corejs3 --save,

{
"presets": ["@babel/preset-env"],
"plugins": [["@babel/plugin-transform-runtime", {"corejs": 3}]]
}

  对于应用开发者来说,@babel/preset-env的useBuiltIns可不可以设置成"usage",

{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "60",
"ie": 11
},
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}

  绝大数情情况下是可以的,但这里有一种情况需要考虑,把index.js 改成

async function f() {}

  npm run babel, 转译后的文件如下

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

require("regenerator-runtime/runtime");

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

function f() {
return _f.apply(this, arguments);
} function _f() {
_f = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _f.apply(this, arguments);
}

  可以看到有

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

  在node_modules 中找到这个文件,可以发现它依赖Promise,  如果像我们现在这样,Promise 并没有polyfill,  所以对于不支持promise 的浏览器来说,项目运行就会报错。useBuiltIns 设置为"usage",有的时候,不太好配置,但是绝大部分情况下没有问题。

 Babel includes helpers from @babel/runtime! These helpers can depend on some global features to be available. In this case, that feature is Promise. The code in @babel/runtime/helpers/asyncToGenerator uses Promises!. 

Babel 7 初探的更多相关文章

  1. Sublime插件支持Sass编译和Babel解析ES6 & .sublime-build文件初探

    用Sublime Text蛮久了,配置配来配去的,每次换电脑都得重头再配过,奈何人老了脑子不中用了,得好好整理一些,下次换电脑就有得参考了.. 同事说,他的WebStorm简直太方便,自身集成了很多方 ...

  2. 初探babel转换器的安装与使用

    一.配置.babelrc文件(没有名字的文件) Babel的配置文件是.babelrc,存放在项目的根目录下.使用Babel的第一步,就是配置这个文件. 基本格式如下: { "presets ...

  3. Sublime插件支持Sass编译和Babel解析ES6 & .sublime-build文件初探(转载自imwtr)

    原文请看:http://www.cnblogs.com/imwtr/p/6010550.html   用Sublime Text蛮久了,配置配来配去的,每次换电脑都得重头再配过,奈何人老了脑子不中用了 ...

  4. ES6转换器之Babel

    ES6部分功能没有支持,所以想学习ES6,得先有个转换器,就是将ES6的代码转换为ES5. 我这里用的是Gulp + Bable的形式来将ES6转换为ES5的. 前提: (1).Gulp和Bable都 ...

  5. 初探webpack之从零搭建Vue开发环境

    初探webpack之搭建Vue开发环境 平时我们可以用vue-cli很方便地搭建Vue的开发环境,vue-cli确实是个好东西,让我们不需要关心webpack等一些繁杂的配置,然后直接开始写业务代码, ...

  6. 初探webpack之编写loader

    初探webpack之编写loader loader加载器是webpack的核心之一,其用于将不同类型的文件转换为webpack可识别的模块,即用于把模块原内容按照需求转换成新内容,用以加载非js模块, ...

  7. 初探富文本之OT协同实例

    初探富文本之OT协同实例 在前边初探富文本之OT协同算法一文中我们探讨了为什么需要协同.为什么仅有原子化的操作并不能实现协同.为什么要有操作变换.如何进行操作变换.什么时候能够应用操作.服务端如何进行 ...

  8. 初探领域驱动设计(2)Repository在DDD中的应用

    概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...

  9. CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探

    CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码 ...

  10. babel presets stage-x

    在一些新框架的代码中,常基于es6/7标准来书写代码.鉴于这些标准被没有被浏览器广泛支持,我们一般使用babel来将使用e6/7标准书写的代码降级编译(或者说转译)为浏览器可解析的es4/5代码. 以 ...

随机推荐

  1. 1.简单的C语言程序

    简单的C语言程序 什么是计算机程序? 什么是计算机语言? 所谓程序,就是一组计算机能识别和执行的指令. 什么是计算机语言? 机器语言(0,1 '低级语言'),汇编语言(符号化 '低级语言'),高级语言 ...

  2. CSS自适应网页(CSS第一篇)

    ​CSS的属性: 用浏览器自带的审查元素对一些页面进行调整,快捷键是F12. 网页允许宽度自适应: 在代码的头部加入一行viewport元标签. <meta name="viewpor ...

  3. 4G EPS 第四代移动通信系统

    目录 文章目录 目录 4G EPS 4G EPS 4G(the 4th generation mobile communication technology,第四代移动通信技术)提供了 3G 不能满足 ...

  4. tar和zip包加密解密压缩

    1.概述 嗯,最近有些机密文件无处安放,因为太机密了,后来确定加密后放到服务器上.研究一番后发现tar和zip命令都能实现,所以在此记录一下. 压缩:tar -zcvf - ./packageTest ...

  5. 彻底搞懂JavaScript原型和原型链

    基于原型编程 在面向对象的编程语言中,类和对象的关系是铸模和铸件的关系,对象总是从类创建而来,比如Java中,必须先创建类再基于类实例化对象. 而在基于原型编程的思想中,类并不是必须的,对象都是通过克 ...

  6. Rainbond 5.5 发布,支持Istio和扩展第三方Service Mesh框架

    Rainbond 5.5 版本主要优化扩展性.服务治理模式可以扩展第三方 ServiceMesh 架构,兼容kubernetes 管理命令和第三方管理平台. 主要功能点解读: 1. 支持 Istio, ...

  7. NOIP模拟100(多校32)

    T1 饥饿的狐狸 解题思路 贪心签到题. 最小值的做法就是对于温度比水小的从大到小吃,然后喝一口水,然后把剩下的从小到大吃掉. 最大值的做法,几乎就是大的挑一个小的挑一个间隔着吃,可以排完序之后双指针 ...

  8. C#笔记 窗体练习:海康相机SDK二次开发

    第一次写窗体应用程序,太闲了,给自己找点事情做... 1. 最基本的打开关闭 代码:https://gitee.com/yurj0403/hik-camera 强行练习一下用git 2. 加了状态栏 ...

  9. js 判断手机号格式

    大江东去,浪淘尽,千古风流人物.故垒西边,人道是,三国周郎赤壁.乱石穿空,惊涛拍岸,卷起千堆雪.江山如画,一时多少豪杰.遥想公瑾当年,小乔初嫁了,雄姿英发.羽扇纶巾,谈笑间,樯橹灰飞烟灭.故国神游,多 ...

  10. 一文了解JVM(中)

    HotSpot 虚拟机对象探秘 对象的创建 Header 解释 使用 new 关键字 调用了构造函数 使用 Class 的 newInstance 方法 调用了构造函数 使用 Constructor ...