乐趣区

关于javascript:一文搞懂Babel配置

最近在做一次 Babel6 降级 Babel7 的操作,把降级的过程和对于 babel 的配置进行一次总结。

1 为什么讲 Babel 配置

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

其实目前前端开发,各种我的项目模版,你也不须要关怀 babel 的配置,轻易拉下来一个就能运行,然而要做定制化的解决还是要把 babel 搞懂。

@babel/cli是 Babel 的命令行工具,咱们个别用不到,因为咱们通常都是用 babel-loader,里边应用的是@babel/core 的 api 模式,咱们只须要关怀 Babel 的配置,如果有须要在编译阶段对代码进行解决 也能够写本人的插件,然而大部分场景是须要咱们把 Babel 的配置搞清楚。

2 Babel 的配置文件

Babel6 的阶段 最罕用的是.babelrc, 然而当初 Babel7 反对了更多格局:

const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json"];
package.json files with a "babel" key。

配置文件的格局如下:

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

更具体介绍参见 Babel Config。

2.1 pluginspreset

配置文件中次要有两个配置 pluginspreset,@babel/core自身对代码不做任何的转化,然而提供了对代码的解析,各种插件在解析过程中能够进行代码的转换,比方解决箭头函数的插件 @babel/plugin-transform-arrow-functions 等等,所以比方针对 ES6 语法的解析就须要很多插件,preset预设就是配置的子集,预设的一套配置,能够依据参数动静的返回配置。

2.2 执行程序

程序问题很重要,比方某一个插件是增加 ’use strict’, 一个插件是删除 ’use strict’,如果想要删除胜利,就要保障执行程序。
在一个配置外面

  • 插件在 presets 前运行。
  • 插件程序从前往后排列。
  • preset 程序是颠倒的(从后往前)。

所以在 preset 中的插件,必定比外层的插件要后执行。

2.3 传参数

pluginspreset 的配置是数组的模式,如果不须要传参数,最根本的就是字符串名称,如果须要传参数,把它写成数组的模式,数组第一项是字符串名称,第二项是要传的参数对象。

3 Babel 的降级

3.1 废除的 preset

@babel/preset-env 曾经齐全能够替换

  • babel-preset-es2015
  • babel-preset-es2016
  • babel-preset-es2017
  • babel-preset-latest

所有 stage 的 preset 在 Babel v7.0.0-beta.55 版本都曾经被 废除 了,
stage-x:指处于某一阶段的 js 语言提案

  • Stage 0 – 构想(Strawman):只是一个想法,可能有 Babel 插件。
  • Stage 1 – 倡议(Proposal):这是值得跟进的。
  • Stage 2 – 草案(Draft):初始标准。
  • Stage 3 – 候选(Candidate):实现标准并在浏览器上初步实现。
  • Stage 4 – 实现(Finished):将增加到下一个年度版本公布中。

最开始 stage 的呈现是为了不便开发人员,每个阶段的插件与 TC39 和社区相互作用,同步更新,用户能够间接援用对应 stage 反对的语法个性。对于废除的起因 总结下来是:

  • 1 对用户太黑盒了,当提案产生重大变动和废除时,stage 外部的插件就会变动,用户可能会呈现未编译的语法。
  • 2 当用户想要反对某种语法时,不晓得在某一个 stage 里,所以最好是让用户本人去增加插件,或者你只须要指定浏览器的兼容性,preset 中动静的增加对应插件。
  • 3 第三点举了个例子,很多人都把装璜器个性加做 ES7,其实这只是阶段 0 的实验性倡议,可能永远不会成为 JS 的一部分。不要将其称为“ES7”,咱们要时刻揭示开发者 babel 是怎么工作的。

3.1 废除的 polyfill

先说下曾经有了 Babel 为什么还要 polyfill,Babel 默认只转换新的JavaScript 句法(syntax),而不转换新的 API,比方 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的办法(比方 Object.assign)都不会转码。举个栗子,ES6 在 Array 对象上新增了 Array.from 办法。babel 就不会转码这个办法。所以之前咱们都须要引入 polyfill。

然而从 Babel 7.4.0 开始,不举荐应用此软件包,而间接包含core-js/stable(包含regenerator-runtime/runtimepolyfill ECMAScript 性能)和(须要应用转译的生成器函数)。

import "core-js/stable";
import "regenerator-runtime/runtime";

然而最优的形式也不是间接这样引入,前面讲 @babel/preset-env 的应用时会有更好的形式。

3.3 babel-upgrade

对于降级,官网提供了工具 babel-upgrade 总结关键点如下:

  • 1 node 版本 8 以上 这个应该都不是问题了。
  • 2 npx babel-upgrade --write --install,两个参数,--write会把更新的配置写入 babel 的配置文件中,package.json中也会更新依赖,然而发现没有的依赖没有新增 ,所以我在更新的时候把配置中依赖的 npm 包,在package.json 都 check 了一遍。--install是会进行一次安装操作。

4 @babel/preset-env

@babel/preset-env是 Babel 举荐的最智能的预设,在应用了 babel-upgrade 降级之后你就能够看到配置中会有这个预设,因为设个预设集成了罕用插件和 polyfill 能力,能够依据用户指定的环境寻找对应的插件。

上面对它的要害配置项做阐明。

4.1 target

string | Array<string> | {[string]: string },默认为{}

形容您为我的项目反对 / 指标的环境。

这能够是与浏览器列表兼容的查问:

`{"targets": "> 0.25%, not dead"}` 

或反对最低环境版本的对象:

`{
  "targets": {
    "chrome": "58",
    "ie": "11"
  }
}` 

施行例的环境中:chromeoperaedgefirefoxsafariieiosandroidnodeelectron

如果未指定指标,则旁注 @babel/preset-env 将默认转换所有 ECMAScript 2015+ 代码,所以不倡议。

4.2 useBuiltIns

"usage"| "entry"| false,默认为false

此选项决定 @babel/preset-env 如何解决 polyfill 的引入。

后面将废除 polyfill 时 讲到了 polyfill 当初分为两个 npm 包,是这样引入

import "core-js/stable";
import "regenerator-runtime/runtime";

然而问题是全量引入,减少包体积,所以 useBuiltIns 选项就是对其进行优化。

当取值 "usage" 时,@babel/preset-env 会把全量引入替换为指标环境特定须要的模块。

当指标浏览器是 chrome 72 时,下面的内容将被 @babel/preset-env 转换为

require("core-js/modules/es.array.reduce");
require("core-js/modules/es.array.reduce-right");
require("core-js/modules/es.array.unscopables.flat");
require("core-js/modules/es.array.unscopables.flat-map");
require("core-js/modules/es.math.hypot");
require("core-js/modules/es.object.from-entries");
require("core-js/modules/web.immediate");

当取值 "entry" 时,@babel/preset-env 会把全量引入替换为指标环境特定须要的模块。

当指标浏览器是 chrome 72 时,下面的内容将被 @babel/preset-env 转换为

require("core-js/modules/es.array.reduce");
require("core-js/modules/es.array.reduce-right");
require("core-js/modules/es.array.unscopables.flat");
require("core-js/modules/es.array.unscopables.flat-map");
require("core-js/modules/es.math.hypot");
require("core-js/modules/es.object.from-entries");
require("core-js/modules/web.immediate");

当取值 "entry" 时,咱们无需手动引入 polyfill 文件,@babel/preset-env 在每个文件的结尾引入指标环境不反对、仅在以后文件中应用的 polyfills。

例如,

const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

当指标环境是老的浏览器例如 ie 11,将转换为

import "core-js/modules/es.array.includes";
import "core-js/modules/es.array.iterator";
import "core-js/modules/es.object.to-string";
import "core-js/modules/es.set";

const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

当指标是 chrome 72 时不须要导入,因为这个环境不须要 polyfills:

const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

4.3 core-js

core-js 就是 Javascript 规范库的 polyfill,@babel/preset-env的 polyfill 就依赖于它,所以咱们须要指定应用的 core-js 的版本,目前最新版本是 3。
默认状况下,仅注入稳固 ECMAScript 性能的 polyfill,如果想应用一些提案的语法,能够有三种抉择:

  • 应用 useBuiltIns: "entry" 时,能够间接导入倡议填充工具:import "core-js/proposals/string-replace-all"
  • 应用 useBuiltIns: "usage" 时,您有两种不同的抉择:

    • shippedProposals 选项设置为true。这将启用曾经在浏览器中公布一段时间的招标的 polyfill 和 transforms。
    • 应用corejs: {version: 3, proposals: true}。这样能够对所反对的每个提案进行填充core-js

4.4 exclude

我感觉这个抉择有用,因为 @babel/preset-env 中内置的插件,咱们无奈在其后执行,比方外面内置的 "@babel/plugin-transform-modules-commonjs" 插件会默认的在所有的模块上都增加 use strict 严格模式,尽管有babel-plugin-remove-use-strict 用于移除 use strict 然而因为执行程序的问题,还是无奈移除。
第二个问题就是内置插件无奈传参数的问题。
所以我想到的办法是先 exclude 排除掉这个插件,而后在外层再增加 这样就能够扭转执行程序同时也能够自定义传参数。

5 @babel/plugin-transform-runtime

曾经有了 polyfill,这个包的作用是什么?次要分两类:

  • 1 缩小代码体积,Babel 的编译会在每一个模块都增加一些行内的代码垫片,例如 await_asyncToGeneratorasyncGeneratorStep,应用了它之后会把这些办法通过@babel/runtime/helpers 中的模块进行替换。

例如代码

async function a () {await new Promise(function(resolve, reject) {resolve(1)
  })
} 

没应用之前,编译后果

require("regenerator-runtime/runtime");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {try { var info = gen[key](arg); var value = info.value; } catch (error) {reject(error); return; } if (info.done) {resolve(value); } else {Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) {return function () {var self = this, args = arguments; return new Promise(function (resolve, reject) {var gen = fn.apply(self, args); function _next(value) {asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) {asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }


function _a() {_a = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {return regeneratorRuntime.wrap(function _callee$(_context) {while (1) {switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return new Promise(function (resolve, reject) {resolve(1);
            });

          case 2:
          case "end":
            return _context.stop();}
      }
    }, _callee);
  }));
  return _a.apply(this, arguments);
}

应用之后


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

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

require("regenerator-runtime/runtime");

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
  • 2 部分引入 不影响全局变量

@babel/preset-env中引入的 polyfill 都是间接引入的 core-js 下的模块,它的问题会净化全局变量,比方

"foobar".includes("foo");

编译后的 polyfill 是给 String.prototype 增加了 includes 办法, 所以会影响全局的 String 对象。

require("core-js/modules/es.string.includes");

而应用了 @babel/plugin-transform-runtime 后的编译后果

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

(0, _includes.default)(_context = "foobar").call(_context, "foo");

会把代码中用的到的办法进行包装,而不会对全局变量产生影响。

最初是 @babel/plugin-transform-runtime的配置项,要害的是指定 core-js的版本。

corejs: 2仅反对全局变量(例如 Promise)和动态属性(例如Array.from),corejs: 3 还反对实例属性(例如[].includes)。

默认状况下,@babel/plugin-transform-runtime不填充提案。如果您应用 corejs: 3,则能够通过应用proposals: true 选项启用此性能。

须要装置对应的运行时依赖:
npm install --save @babel/runtime-corejs3

最初 你能够基于以上常识曾经创立了合乎本人团队开发的preset。

如果感觉有播种请关注微信公众号 前端良文 每周都会分享前端开发中的干货知识点。

退出移动版