很是深刻的理解一下babel

什么是babel

这里借用一下官方的定义javascript

Babel是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便可以运行在当前和旧版本的浏览器或其余环境中。前端

简单的理解就是,对较高版本的语法作一次向下兼容,使得较低版本的浏览器可以识别并运行这些代码java

为何会出现babel

先来看一下这个词babel(bāb(ə)l,音译为 掰bou,中文译为巴别塔。node

个人理解是:react

  • es6之类的只是语言规范,而浏览器要实现这一规范可能就须要至关漫长的一个时间了。
  • 各类前端技术形成了各类js方言,尤为是react的jsx

因此急需一个工具去解决这些问题,而后babel就应运而生。webpack

再来看一下百度上对巴别塔的解释git

巴别塔;是《圣经·旧约·创世记》第11章故事中人们建造的塔。根据篇章记载,当时人类联合起来兴建但愿能通往天堂的高塔;为了阻止人类的计划,上帝让人类说不一样的语言,令人类相互之间不能沟通,计划所以失败,人类自此各散东西。此事件,为世上出现不一样语言和种族提供解释。es6

是否是感受很是🐂🍺github

这里想到了我司的一个项目,这个项目的主要做用就是使用函数计算在移动端截图,我司名字是moka,因此这个项目起名为mokapture,我我的感受仍是挺有意思的web

重点:presets

babel会经过具体的某个插件对相应的代码进行转码,好比箭头函数对应的插件就是@babel/plugin-transform-arrow-functions,链式调用对应的插件就是@babel/plugin-proposal-optional-chaining,咱们不可能一个一个的安装并一个一个引入使用这些插件,那么presets就为咱们提供了一组插件的集合。

官方 Preset,包括:env,flow,react,typescript等

这里重点介绍一下env,这也是咱们平时用的最多的

env 的核心目的是经过配置(browserslist, compat-table, and electron-to-chromium)得知目标环境的特色,而后只作必要的转换。例如目标浏览器支持 es2015,那么 es2015 这个 preset 实际上是不须要的,因而代码就能够小一点(通常转化后的代码老是更长),构建时间也能够缩短一些。若是不写任何配置项,env默认使用最新的JS语法(不包括Stage-X阶段)。

基本的配置以及本人认为比较重要的options以下:

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: "> 0.25%, not dead",
        modules: false,
        useBuiltIns: "entry",
        corejs: "3",
      },
    ],
  ],
};
复制代码
  • targets: 支持babel转换的浏览器环境,示例代码的意思是仅包含浏览器市场份额超过0.25%的用户所需的polyfill和代码转换(忽略没有安全更新的浏览器,如IE10和BlackBerry)。具体的语法能够参考browserslist
  • modules:让 babel 以特定的模块化格式来输出代码,可选值为"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false。默认为auto
    1. auto会根据caller的配置数据决定是否模块化处理,通常状况下不须要咱们配置。
    2. false不进行模块化处理。
  • useBuiltIns: "usage" | "entry" | false, 默认为false,若是使用此配置项,须要指定corejs版本,否则会有WARNING
    1. usage会自动根据咱们的环境自动引入对应的core-jsregenerator-runtime插件对咱们的代码进行模块化解析
    2. entry确保咱们的每一个文件只引入一次polyfill代码
    3. false则不自动给文件引入polyfill,也不会给core-js或者polyfill作按需加载处理

Stage-X (实验性质的 Presets)

TC39 将提案分为如下几个阶段:

  • Stage 0 - 设想(Strawman):只是一个想法,可能有 Babel插件。
  • Stage 1 - 建议(Proposal):这是值得跟进的。
  • Stage 2 - 草案(Draft):初始规范。
  • Stage 3 - 候选(Candidate):完成规范并在浏览器上初步实现。
  • Stage 4 - 完成(Finished):将添加到下一个年度版本发布中。

注意:这些提案可能会有变化,所以,特别是处于 stage-3 以前的任何提案,请务必谨慎使用。咱们计划在每次 TC39 会议以后,若是有可能,在提案变动时更新 stage-x 的 preset。

注意Preset的排列顺序

Preset 是逆序排列的(从后往前)。

{
  "presets": ["a", "b", "c"]
}
复制代码

将按以下顺序执行: c、b 而后是 a。

这主要是为了确保向后兼容,因为大多数用户将 "es2015" 放在 "stage-0" 以前,这样必须先执行 stage-0 才能确保 babel 不报错。另外:Plugin 会运行在 Preset 以前,从前向后执行。

重点:babel插件

Babel 是一个编译器(输入源码 => 输出编译后的代码)。就像其余编译器同样,编译过程分为三个阶段:解析、转换和打印输出。如今,Babel 虽然开箱即用,可是什么动做都不作。它基本上相似于 const babel = code => code; ,将代码解析以后再输出一样的代码。若是想要 Babel 作一些实际的工做,就须要为其添加插件。

咱们能够看到babel主要仍是依靠各类插件来对咱们的代码进行编译。下面咱们来看一下babel的插件到底有多强大吧。先来看一段代码:

// index.js
const study = (a, b) => a + b;
复制代码

这是一段很常见的使用箭头函数的代码,那么咱们如何使用babel去编译这段代码呢,让咱们先依次来执行下列代码

npm install --save-dev @babel/core
npm install --save-dev @babel/cli
npm install --save-dev @babel/plugin-transform-arrow-functions
复制代码

安装完这几个模块以后咱们再配置一下babel

// babel.config.js
module.exports = {
  presets: [],
  plugins: ["@babel/plugin-transform-arrow-functions"],
};
复制代码

而后在终端里执行

npx babel index.js --out-file index-compiled.js
复制代码

咱们打开index-compiled.js看一下

// index-compiled.js
const study = function (a, b) {
  return a + b;
};
复制代码

babel把咱们的箭头函数给转换成了function形式

babel-core

All transformations will use your local configuration files.

全部的转换都将用本地的配置文件(.babelrc、babel.config.js或者package.json),core即便核心嘛,咱们也能够看到core的仓库里的目录结构 babel-core 主要是将代码转成ast,方便各个插件分析语法进行相应的处理

babel-cli

用于命令行使用babel,好比咱们上面👆代码的npx babel index.js --out-file index-compiled.js

babel-node

babel-node is a CLI that works exactly the same as the Node.js CLI, with the added benefit of compiling with Babel presets and plugins before running it.

babel-nodebabel附带的第二个CLI,工做原理与Node.js的CLI彻底相同,只是它会在运行以前编译ES6代码,node环境下能够直接运行代码,而不须要转码。babel-node通常不用于生产环境,由于运行前动态编译,因此内存的开销很是的大。babel-node至关于babel-polyfill + babel-register

babel-register

babel-register会在node环境下,给require绑定一个钩子函数,这个钩子会讲全部以.es6, .es, .jsx, .mjs, and .js为后缀的文件都将用babel进行转码。因此babel-register只会对require的文件转码,并不会对自身文件转码,由于是实时转码,因此不适用于生产环境。而且若是你想用babel-register的话,还要一并引入babel-polyfill

babel-polyfill

babel 默认只转换 js 语法,而不转换新的 API,好比 IteratorGeneratorSetMapsProxyReflectPromise 这种全局Api或者对象,以及一些定义在全局对象上的方法(好比Object.assign)都不会转码。因此在使用这些方法时就必需要使用babel-polyfill(包含core-jsregenerator-runtime)。

这里咱们改造一下代码,npm install --save-dev @babel/preset-env ,而后修改一下babel.config.js

// babel.config.js
module.exports = {
  presets: ["@babel/preset-env"],
  plugins: [],
};
复制代码

这里再看一下babel-polyfill

// index.js
import "@babel/polyfill";
new Promise((resolve, reject) => {
  resolve(1);
});
复制代码
// index-compiled.js
"use strict";

require("@babel/polyfill");

new Promise(function (resolve, reject) {
  resolve(1);
});
复制代码

咱们注意到@babel/polyfill的引用方式变成require了,怎么变成import呢, 在babel.config.js中,将preset-env的配置项中添加modules: false便可。因此此时的babel.config.js文件变成了:

// babel.config.js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        modules: false,
        useBuiltIns: "entry",
        corejs: "3",
      },
    ],
  ],
  plugins: [],
};
复制代码

咱们再来执行一下,会发现@babel/polyfill的引用方式变回import了。

通常状况下咱们在使用polyfill时都是经过webpack的入口文件配置,好比:

module.exports = {
  entry: ["babel-polyfill", "./app/js"]
};
复制代码

因此,babel-polyfill要安装在生产环境dependencies中。

由于babel-polyfill会把全部的方法都加在原型上,好比Array.isArray这个方法,babel-polyfill会在Arrayprotorype上挂载这个方法,即Array.prototype.isArray,这将致使:

  • babel-polyfill打包出来的体积很是大,由于全部的原型上都挂载的有兼容的方法,我只想用Array.isArray,可是Object.assign也会被挂载兼容方法。固然也有优化的方法,配置useBuiltIns: usage
  • 在原型上挂载方法污染全局变量,若是咱们是在维护一个公共组件库,就十分的危险了

因此plugin-transform-runtime会是一个比较不错的选择

babel-runtime 和 babel-plugin-transform-runtime

当咱们在使用一些稍复杂的语法时,babel会借用一些函数进行转换,好比说es6的class

// index.js
class Circle {}
复制代码

通过babel的默认转换后:

// index-compiled.js
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};
复制代码

咱们能够看到,babel会使用一些函数来帮助处理这些复杂的转换,那咱们能够预想到,每一个文件都这么转换以后代码量将会是多么的庞大,而且还重复了不少的这些helper函数,因此babel-plugin-transform-runtime其实就是把这些helper函数统一收集起来,下次直接在文件中引用便可,咱们安装一下babel-plugin-transform-runtime

npm install --save-dev @babel/plugin-transform-runtime
复制代码

配置babel.config.js

// babel.config.js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        modules: false,
        useBuiltIns: "entry",
        corejs: "3",
      },
    ],
  ],
  plugins: ["@babel/plugin-transform-runtime"],
};
复制代码

此时运行再看咱们编译后的class

import _classCallCheck from "@babel/runtime/helpers/classCallCheck";

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};
复制代码

已经变成了引入的方式。咱们注意到@babel/plugin-transform-runtime实际上是引用的@babel/runtime的代码。

此外@babel/plugin-transform-runtime还有一个最重要的做用就是为咱们提供了一个配置项corejs,他能够给babel-polyfill提供一个沙箱环境,这样就不会污染到全局变量,并且无反作用。可是这项要开启@babel/plugin-transform-runtime的沙箱模式,必需要配置corejs corejs 须要注意的是corejs2只支持全局变量(promise。。。)和静态属性(Array.from。。。),而不支持实例属性(includes。。。),若是要用实例属性,就要使用corejs3。可是corejs3目前仍是提案阶段,因此还需配置proposals: true,因此此时babel.config.js为:

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        modules: false,
        useBuiltIns: "entry",
        corejs: "3",
      },
    ],
  ],
  plugins: [
    [
      "@babel/plugin-transform-runtime",
      {
        corejs: { version: 3, proposals: true },
      },
    ],
  ],
};
复制代码

参考连接

相关文章
相关标签/搜索