基于实践探寻 babel7 最佳配置方案
本文适用版本
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/plugin-transform-runtime": "^7.15.8",
"@babel/preset-env": "^7.15.8",
"babel-loader": "^8.2.3",
"webpack": "^5.59.1",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"@babel/runtime": "^7.15.4",
"@babel/runtime-corejs2": "^7.15.4",
"@babel/runtime-corejs3": "^7.15.4",
"core-js": "^3.18.3"
}
什么是 babel
babel 用于转换 ECMAScript 2015+
为向后兼容的 JavaScript
版本:
- 转换语法(通过
@babel/preset-env
里集合的插件,会用到helper
函数) - 目标环境中缺少的
Polyfill
功能(通过第三方polyfill
,例如core-js
) - 源代码转换(
codemods
)
babel 运行方式
babel
总共分为三个阶段:
- 解析(
parser
):通过babel-parser(babylon)
解析成AST
- 转换(
transform
):All the plugins/presets
,AST
转换 - 生成(
generator
):最后通过babel-generator
生成目标代码+sourcemap
。
babel
本身不具有任何转换功能,因此当我们不配置任何插件时,经过 babel
的代码和输入是相同的。
本文侧重探讨 babel
生态各包的作用机制,故略去原理分析。
Plugin
babel
转换的功能都分解到一个个 plugin
里面,在 transform
阶段,会应用 plugins
来完成 AST
的转换:
- 把不支持的语法转成目标环境支持的语法来实现相同功能
- 把不支持的
api
自动引入对应的polyfill
plugin
分为三类:
@babel/plugin-transform-xx
:转换插件,语法转换(会自动启用对应的语法插件)。@babel/plugin-proposal-xx
:转换插件,指代那些对ES Proposal
(即还未被ECMA-262
正式发布的特性)进行转换的插件,一旦正式发布后,名称就会被重名为@babel/plugin-transform-xx
。@babel/plugin-syntax-xx
:语法插件,不需要单独配置,会被转换插件依赖,用于语法解析。
plugins
会从前到后顺序执行,前一个 plugin
的处理结果,将作为下一个 plugin
的输入。
Preset
preset
即一组官方推荐的预设插件的集合,可以理解为插件套餐,如:
@babel/preset-env
for compiling ES2015+ syntax@babel/preset-typescript
for TypeScript@babel/preset-react
for React@babel/preset-flow
for Flow
Preset
会从后往前执行(因为作者认为大部分开发者会把 presets
写成 ["es2015", "stage-0"]
,stage-x
是 Javascript
语法的一些提案,那这部分可能依赖了 ES6
的语法,解析的时候得先解析这部分到 ES6
,再把 ES6
解析成 ES5
)
Plugin
会运行在 Preset
之前。
browserslist
browserslist是在不同的前端工具之间共用目标浏览器和 node
版本的配置工具。
eg:package.json:
"browserslist": [
"last 1 version",
"> 1%",
"maintained node versions",
"not dead"
]
core-js
core-js是完全模块化的 javascript
标准库。 包含 ECMA-262
至今为止大部分特性的 polyfill
,如 promises、symbols、collections、iterators、typed arrays
等。目前在用的版本是 core-js@2
、core-js@3
,其中不推荐使用 v2
,因为 v3
支持更多特性的 polyfill
。
core-js
同时提供 3 个包:
core-js
:最常用的版本,引入整个core-js
或部分特性,就会把所有或对应的polyfill
,直接扩展到代码运行的全局环境中(修改原型等方式),业务代码可直接使用最新的ES
写法。jsimport 'core-js'; //全部引入 import 'core-js/features/array/flat'; //针对性引入(feature命名空间) [1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5]
另外,通过查看core-js 源码,很清晰的得到:引入
core-js
即引入core-js/features
,core-js/features
里引入了全部的modules
,包括es
,esnext(proposal,stage)
,web
;另core-js/stable
包括es+web
,故若部分特性引入则用features
命名空间就行。core-js-pure
:类似一种工具函数,不会注入到全局环境,所以整体引入无效。在使用的时候需要单独引入并使用对应polyfill
的module
方法,不能直接使用最新ES
的写法。jsimport flat from 'core-js-pure/features/array/flat'; flat([1, [2, 3], [4, [5]]], 2); // => [1, 2, 3, 4, 5]
另外,通过查看core-js-pure 源码,与
core-js
源码仅internals
和modules
两个文件夹有区别,即在模块导出的时候做了一些处理,形成了类似工具函数的调用方式。很明显在实际业务代码中,直接引用
core-js-pure
还需要格外编码变的复杂,后面介绍通过与@babel/runtime
集成简化使用。core-js-bundle
:编译打包好的版本,包含全部的polyfill
特性,适合在浏览器里面通过script
直接加载。
core-js
需安装在 dependencies
依赖里,并且通常情况不单独使用,要与 babel
集成。
regenerator-runtime
regenerator-runtime 模块来自 facebook
的 regenerator
模块。生成器函数、async
、await
函数经 babel
编译后,regenerator-runtime
模块用于提供功能实现。
@babel/core
babel
的编译核心包,是语法转换的主要工具。通过 plugins
执行后得到 result
对象包括 { code, map, ast }
等,其中 code
即为编译后的代码。所谓 babel
版本多少就是指这个包的版本多少。
@babel/core
在业务项目中,基本不会用到。都是其他编译插件需要依赖他来进行编译。故可以在这些插件的 package.json
里看到:
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
在 npm7
以下,peerDependencies
不会自动安装,从 npm7
开始,会被默认安装,所以保险起见,业务项目最好也手动安装到 devDependencies
。
babel-loader
webpack
中使用 babel
加载需要编译的文件,注意 exclude
和 include
的使用。
@babel/polyfill
babel
7.4 版本已废弃,因为他仅仅依赖了 core-js
和 regenerator-runtime
,安装这两个就可以了。并且它会无差别地引入所有的 polyfill
,这是不符合未来浏览器等运行环境的,按需引入更适合实际需求。
@babel/preset-env
babel7
废弃了 preset-20xx,preset-stage-x
等 preset
包,替换为 @babel/preset-env
。@babel/preset-env
是一个智能预设,集合了一系列常用插件,会根据 browserslist
、compat-table
等设置的目标环境,自动将代码中的新特性转换成目标浏览器支持的代码(仅转换语法)。
通过 package.json
,就能知道它集合了哪些 plugins
:
"dependencies": {
"@babel/compat-data": "^7.15.0",
"@babel/helper-compilation-targets": "^7.15.4",
"@babel/helper-plugin-utils": "^7.14.5",
"@babel/helper-validator-option": "^7.14.5",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.15.4",
"@babel/plugin-proposal-async-generator-functions": "^7.15.8",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-class-static-block": "^7.15.4",
"@babel/plugin-proposal-dynamic-import": "^7.14.5",
"@babel/plugin-proposal-export-namespace-from": "^7.14.5",
"@babel/plugin-proposal-json-strings": "^7.14.5",
"@babel/plugin-proposal-logical-assignment-operators": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-numeric-separator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
"@babel/plugin-proposal-optional-catch-binding": "^7.14.5",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-proposal-private-methods": "^7.14.5",
"@babel/plugin-proposal-private-property-in-object": "^7.15.4",
"@babel/plugin-proposal-unicode-property-regex": "^7.14.5",
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-syntax-class-static-block": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-export-namespace-from": "^7.8.3",
"@babel/plugin-syntax-json-strings": "^7.8.3",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-numeric-separator": "^7.10.4",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5",
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/plugin-transform-arrow-functions": "^7.14.5",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/plugin-transform-block-scoped-functions": "^7.14.5",
"@babel/plugin-transform-block-scoping": "^7.15.3",
"@babel/plugin-transform-classes": "^7.15.4",
"@babel/plugin-transform-computed-properties": "^7.14.5",
"@babel/plugin-transform-destructuring": "^7.14.7",
"@babel/plugin-transform-dotall-regex": "^7.14.5",
"@babel/plugin-transform-duplicate-keys": "^7.14.5",
"@babel/plugin-transform-exponentiation-operator": "^7.14.5",
"@babel/plugin-transform-for-of": "^7.15.4",
"@babel/plugin-transform-function-name": "^7.14.5",
"@babel/plugin-transform-literals": "^7.14.5",
"@babel/plugin-transform-member-expression-literals": "^7.14.5",
"@babel/plugin-transform-modules-amd": "^7.14.5",
"@babel/plugin-transform-modules-commonjs": "^7.15.4",
"@babel/plugin-transform-modules-systemjs": "^7.15.4",
"@babel/plugin-transform-modules-umd": "^7.14.5",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.14.9",
"@babel/plugin-transform-new-target": "^7.14.5",
"@babel/plugin-transform-object-super": "^7.14.5",
"@babel/plugin-transform-parameters": "^7.15.4",
"@babel/plugin-transform-property-literals": "^7.14.5",
"@babel/plugin-transform-regenerator": "^7.14.5",
"@babel/plugin-transform-reserved-words": "^7.14.5",
"@babel/plugin-transform-shorthand-properties": "^7.14.5",
"@babel/plugin-transform-spread": "^7.15.8",
"@babel/plugin-transform-sticky-regex": "^7.14.5",
"@babel/plugin-transform-template-literals": "^7.14.5",
"@babel/plugin-transform-typeof-symbol": "^7.14.5",
"@babel/plugin-transform-unicode-escapes": "^7.14.5",
"@babel/plugin-transform-unicode-regex": "^7.14.5",
"@babel/preset-modules": "^0.1.4",
"@babel/types": "^7.15.6",
"babel-plugin-polyfill-corejs2": "^0.2.2",
"babel-plugin-polyfill-corejs3": "^0.2.5",
"babel-plugin-polyfill-regenerator": "^0.2.2",
"core-js-compat": "^3.16.0",
"semver": "^6.3.0"
},
各 proposal plugin
的提案进度在不断更新,故保持 preset-env
的更新,在业务项目中也是需要定期关注。除此之外,如果我们用到某一个新的还在 proposal
阶段的 ES
特性,并且 preset-env
未集成该 plugin
,就得自己单独配置 plugins
了。
默认的 @babel/preset-env
是无法转换新的 API
,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise
等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign
)都不会转码。需要添加 core-js
和 regenerator-runtime
支持。
需要注意的是,在 @babel/preset-env
7.15.8 版本依赖插件 @babel/plugin-transform-regenerator
,该插件用于编译 async
和生成器。因为他依赖 regenerator-transform->@babel/runtime->regenerator-runtime
,本质还是 regenerator-runtime
提供编译能力。故不需要格外安装 regenerator-runtime
。
preset-env 配置介绍
module.exports = {
presets: [
[
'@babel/env',
{
targets: {
// 目标运行环境,优先级大于 browserslist
browsers: ['> 1%', 'last 2 versions', 'not dead'],
},
debug: true, // 编译时候的 console
modules: false, // 取值可以是 amd, umd, systemjs, commonjs 和 false,为 false 时可以用于 webpack 做 tree shaking
useBuiltIns: 'usage', // usage-按需引入 entry-入口引入(代码里需手动引入core-js) false-不引入(defaults),即关闭polyfill
corejs: 3, // 2-corejs@2(defaults) ,3-corejs@3
},
],
],
};
需手动安装依赖 corejs@3
(dependencies
),已该配置为默认配置,主要关注不同 useBuiltIns
的配置情况:
entry
如果 useBuiltIns
取值为 entry
,需要在代码中手动引入 polyfill
包,实例代码:
import 'core-js'; //手动引入
import 'regenerator-runtime/runtime'; //手动引入
const c = [5, 6, 7].includes(2);
const d = async () => {
const e = await a;
console.log(e);
};
其中引入 regenerator-runtime/runtime
,是为了添加 regenerator-runtime
的 polyfill
,否则会出现 regeneratorRuntime is not defined
的问题。
根据上面的配置,webpack
打包结果有 18000 多行,如果改变 targets
的配置:
targets: 'last 1 Chrome versions',
重新打包则有 6500 多行,说明在 entry
模式下,代码中对 core-js
的 import
会根据 targets
的配置,替换为 core-js
最底层的 modules
引用,并且会全部打包进来。
优点: 所有
polyfill
都被引入,则无需关心具体某个特性的polyfill
,直接写代码就行。缺点:生成的代码包体积太大,很多特性都不需要,对前端性能必然有影响。可以在代码单独引用
core-js
的某一部分:jsimport 'core-js/es/promise'; import 'core-js/es/array';
这样可以减少部分无用代码,但对
FE
对ES
特性十分熟悉,依旧操作难度大。
usage
usage
会根据每个文件里面用到的 ES 特性+target
配置自动引入对应的 polyfill
,如果 targets
的最低环境不支持某个 es
特性,则这个 es
特性的 core-js
的对应 module
会被注入。
实例代码:
index.js
:
import 'test.js';
const c = [5, 6, 7].includes(2);
const d = async () => {
const e = await a;
console.log(e);
};
test.js
:
const f = async () => {
const g = await a;
console.log(g);
};
webpack
打包出来的代码一共有 3700 多行,列出其中 index.js
和 test.js
模块代码分析:
// 省略部分代码
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! regenerator-runtime/runtime.js */ "./node_modules/regenerator-runtime/runtime.js");
/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/es.array.includes.js */ "./node_modules/core-js/modules/es.array.includes.js");
/* harmony import */ var core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_includes_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/es.object.to-string.js */ "./node_modules/core-js/modules/es.object.to-string.js");
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! core-js/modules/es.promise.js */ "./node_modules/core-js/modules/es.promise.js");
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _test_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./test.js */ "./test.js");
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); }); }; }
var c = [5, 6, 7].includes(2);
var d = /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var e;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return a;
case 2:
e = _context.sent;
console.log(e);
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return function d() {
return _ref.apply(this, arguments);
};
}();
/***/ }),
/***/ "./test.js":
/*!*****************!*\
!*** ./test.js ***!
\*****************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.object.to-string.js */ "./node_modules/core-js/modules/es.object.to-string.js");
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/es.promise.js */ "./node_modules/core-js/modules/es.promise.js");
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! regenerator-runtime/runtime.js */ "./node_modules/regenerator-runtime/runtime.js");
/* harmony import */ var regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_2__);
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); }); }; }
var f = /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var g;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return a;
case 2:
g = _context.sent;
console.log(g);
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return function f() {
return _ref.apply(this, arguments);
};
}();
/***/ }),
// 省略部分代码
从代码中可以看出:
- 经
webpack
打包后,每个模块会根据该模块用到的特性(如本例中includes
)引入对应的module
扩展了全局变量。 asyncGeneratorStep
、_asyncToGenerator
之类的helper
函数,会在每个模块里都会定义一次。
同样改变 targets
的配置:
targets: 'last 1 Chrome versions',
则 webpack
打包结果:
(self['webpackChunkbabel_demo'] = self['webpackChunkbabel_demo'] || []).push([
['main'],
{
/***/ './index.js':
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ () => {
const c = [5, 6, 7].includes(2);
const d = async () => {
const e = await a;
console.log(e);
};
/***/
},
},
/******/ (__webpack_require__) => {
// webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => __webpack_require__((__webpack_require__.s = moduleId));
/******/ var __webpack_exports__ = __webpack_exec__('./index.js');
/******/
},
]);
//# sourceMappingURL=main.js.map
一共才 24 行,说明该 target
下 es
的特性是浏览器直接支持的,无需添加 polyfill
。
false
false
即为不引入 corejs
的 polyfill
,只会做语法转换。实例代码:
const c = [5, 6, 7].includes(2);
const f = [1, 2, 3];
const g = [...f, 5, 6, 7];
const d = async () => {
const e = await a;
console.log(e);
};
则 webpack
打包结果:
(self['webpackChunkbabel_demo'] = self['webpackChunkbabel_demo'] || []).push([
['main'],
{
/***/ './index.js':
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ () => {
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);
});
};
}
var c = [5, 6, 7].includes(2);
var f = [1, 2, 3];
var g = [].concat(f, [5, 6, 7]);
var d = /*#__PURE__*/ (function () {
var _ref = _asyncToGenerator(
/*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
var e;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2;
return a;
case 2:
e = _context.sent;
console.log(e);
case 4:
case 'end':
return _context.stop();
}
}
}, _callee);
}),
);
return function d() {
return _ref.apply(this, arguments);
};
})();
/***/
},
},
/******/ (__webpack_require__) => {
// webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => __webpack_require__((__webpack_require__.s = moduleId));
/******/ var __webpack_exports__ = __webpack_exec__('./index.js');
/******/
},
]);
可知仅仅做了语法转换,includes
特性对应的 polyfill
未加入。
@babel/runtime 系列
@babel/runtime
系列包含以下三种:
@babel/runtime
@babel/runtime-corejs2
:@babel/runtime
+core-js@2
@babel/runtime-corejs3
:@babel/runtime
+core-js-pure@3
@babel/runtime
提供 runtime helpers
和 regenerator-runtime
,即只做语法转换,没有新 api
的实现,需要安装到 dependencies
并被 @babel/plugin-transform-runtime
依赖使用。
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime插件主要通过使用@babel/runtime
系列内部的模块来代替重复的 helpers
、对全局空间有污染的 core-js
和 regenerator-runtime
相关变量:
- 对
Babel
编译过程中各模块内重复产生的helper
方法进行重新聚合(全部指向@babel/runtime/helpers
这个module
当中的辅助函数),以达到减少打包体积的目的. - 避免全局补丁污染,对每个模块内提供"沙箱"式的补丁。
几个例子
准备以下测试文件:
index.js
:
import 'test.js';
const c = [5, 6, 7].includes(2);
const d = async () => {
const e = await a;
console.log(e);
};
test.js
:
const f = async () => {
const g = await a;
console.log(g);
};
例一
添加插件,同时关闭 preset-env
的 polyfill
影响:
babel.config.js
:
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
// 目标运行环境,优先级大于 browserslist
browsers: ['> 1%', 'last 2 versions', 'not dead'],
},
useBuiltIns: false,
// corejs:3,
debug: true,
},
],
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3, //为false就安装 npm i @babel/runtime, 为2就安装@babel/runtime-corejs2,为3就安装@babel/runtime-corejs3
},
],
],
};
默认情况下 transform-runtime
是不启用对 core-js
的 polyfill
处理,那时需手动安装需手动安装 @babel/runtime
,本例需手动安装依赖 @babel/runtime-corejs3
,webpack
打包结果(保留部分代码):
//省略部分代码
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _babel_runtime_corejs3_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs3/helpers/asyncToGenerator */ "./node_modules/@babel/runtime-corejs3/helpers/esm/asyncToGenerator.js");
/* harmony import */ var _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime-corejs3/regenerator */ "./node_modules/@babel/runtime-corejs3/regenerator/index.js");
/* harmony import */ var _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/instance/includes */ "./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _test_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./test.js */ "./test.js");
var _context;
var c = _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_2___default()(_context = [5, 6, 7]).call(_context, 2);
var d = /*#__PURE__*/function () {
var _ref = (0,_babel_runtime_corejs3_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])( /*#__PURE__*/_babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee() {
var e;
return _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function _callee$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return a;
case 2:
e = _context2.sent;
console.log(e);
case 4:
case "end":
return _context2.stop();
}
}
}, _callee);
}));
return function d() {
return _ref.apply(this, arguments);
};
}();
/***/ }),
/***/ "./test.js":
/*!*****************!*\
!*** ./test.js ***!
\*****************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _babel_runtime_corejs3_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs3/helpers/asyncToGenerator */ "./node_modules/@babel/runtime-corejs3/helpers/esm/asyncToGenerator.js");
/* harmony import */ var _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime-corejs3/regenerator */ "./node_modules/@babel/runtime-corejs3/regenerator/index.js");
/* harmony import */ var _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1__);
var f = /*#__PURE__*/function () {
var _ref = (0,_babel_runtime_corejs3_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])( /*#__PURE__*/_babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee() {
var g;
return _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return a;
case 2:
g = _context.sent;
console.log(g);
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return function f() {
return _ref.apply(this, arguments);
};
}();
/***/ }),
//省略部分代码
从代码中可以看出:
- 重复的
helper
函数转换成公共的、单独的依赖(./node_modules/@babel/runtime-corejs3/helpers/esm/asyncToGenerator.js
)引入,不在重复定义; - 创建一个沙盒环境,能将这些特性对应的全局变量转换为对
core-js
和regenerator-runtime
非全局变量版本的引用(如includes
就被转换)。
例二
将例一里的 targets
改为:targets: 'last 1 Chrome versions'
,其他不变,再次 webpack
打包结果(保留部分代码):
//省略部分代码
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/instance/includes */ "./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _test_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./test.js */ "./test.js");
/* harmony import */ var _test_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_test_js__WEBPACK_IMPORTED_MODULE_1__);
var _context;
const c = _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_0___default()(_context = [5, 6, 7]).call(_context, 2);
const d = async () => {
const e = await a;
console.log(e);
};
/***/ }),
/***/ "./test.js":
/*!*****************!*\
!*** ./test.js ***!
\*****************/
/***/ (() => {
const f = async () => {
const g = await a;
console.log(g);
};
/***/ }),
//省略部分代码
从代码中可以看出:
通过 preset-env
的 target
配置,语法转换在高版本代码量减少,但 corejs
的 polyfill
未减少。原因是 transform-runtime
的 polyfill
对目标环境是不做判断的,只要它识别到代码里有用到新的 ES
特性,就会进行替换。
例三
同时开启 preset-env
和 transform-runtime
的 polyfill
功能:
babel.config.js
:
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['> 1%', 'last 2 versions', 'not dead'],
},
useBuiltIns: 'usage',
corejs: 3,
debug: true,
},
],
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
},
],
],
};
index.js
:
Promise.resolve().finally();
webpack
打包结果(保留部分代码):
//省略部分代码
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_promise__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/promise */ "./node_modules/@babel/runtime-corejs3/core-js-stable/promise.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_promise__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_promise__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/es.object.to-string.js */ "./node_modules/core-js/modules/es.object.to-string.js");
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/es.promise.js */ "./node_modules/core-js/modules/es.promise.js");
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var core_js_modules_es_promise_finally_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! core-js/modules/es.promise.finally.js */ "./node_modules/core-js/modules/es.promise.finally.js");
/* harmony import */ var core_js_modules_es_promise_finally_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_promise_finally_js__WEBPACK_IMPORTED_MODULE_3__);
_babel_runtime_corejs3_core_js_stable_promise__WEBPACK_IMPORTED_MODULE_0___default().resolve().finally();
/***/ }),
//省略部分代码
从代码中可以看出:
出现了 preset-env
的 polyfill
与 transform-runtime
的 polyfill
并存的现象。所以两种 polyfill
不能同时启用。
由以上 3 个例子和 usage
的例子得出结论:
plugin-transform-runtime
跟preset-env
提供的polyfill
适用的场景是完全不同,前者适合开发库,后者适合开发application
plugin-transform-runtime
与preset-env
的polyfill
不能同时启用plugin-transform-runtime
的polyfill
不判断目标运行环境,因为plugin
执行在preset
之前
babel 8
在未来的 babel8
的babel-polyfills会解决上述问题,支持按需自定义配置一个 polyfill provider
,并将 transform-runtime
做的事情内置到 @babel/preset-env
,并且 plugin
也可以使用 targets
。
babel7 最佳实践
使用 @babel/preset-env 的 polyfill
npm
安装:
npm i @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm i core-js@3
babel.config.js
配置:
module.exports = {
presets: [
[
'@babel/env',
{
targets: {
browsers: ['> 1%', 'last 2 versions', 'not dead'],
},
useBuiltIns: 'usage',
corejs: 3,
},
],
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: false,
regenerator: false,
},
],
],
};
该方案会污染模块内全局,但会根据 target
来一定程度上减少 polyfill
,进而减小体积,适合业务项目。
使用 @babel/plugin-transform-runtime 的 polyfill
npm
安装:
npm i @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm i @babel/runtime-corejs3
babel.config.js
配置:
module.exports = {
presets: [
[
'@babel/env',
{
targets: {
browsers: ['> 1%', 'last 2 versions', 'not dead'],
},
useBuiltIns: false,
},
],
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
},
],
],
};
该方案不会污染全局,但是这个 transform-runtime
没法利用 target
因此使 polyfill
全量引入导致体积增大,适合开发库。