babel在项目中的作用
在我的理解来看,babel就是用来转义我们项目中一些浏览器版本不兼容的高级语法(比如es6,es7...) babel的工作过程分为三个阶段:parsing(解析)、transforming(转化)、printing(生成)
1、parsing阶段babel内部的 babylon 负责将es6代码进行语法分析和词法分析后转换成抽象语法树
2、transforming阶段内部的 babel-traverse 负责对抽象语法树进行变换操作
3、printing阶段内部的 babel-generator 负责生成对应的代码
其中第二步的转化是重中之重,babel的插件机制也是在这一步发挥作用的,plugins在这里进行操作,转化成新的AST,再交给第三步的babel-generator。
polyfill、preset-env的区别
通过babel官网可以看到babel-polyfill里的代码是这样的
import "core-js/stable";
import "regenerator-runtime/runtime";
从 babel v7.4.0开始官方就不建议直接使用babel-polyfill了。所以来了解一下@babel/preset-env
@babel/preset-env
@babel/preset-env 会根据 browserlist 配置进行转换,如果需要兼容比较旧的浏览器,需要手动引入 @babel/polyfill @babel/preset-env的option如下
-
targets.esmodules:boolean = false;请注意:在指定 esmodules 目标时,将忽略 browserlists, 即 useBuiltIn 会失效,不转化 es6 语法也不 polyfill,如果 想用 esmodules 又需要 polyfill ,请组合使用 modules = false , useBuiltIn
-
useBuiltIns = false;根据 browserlist 是否转换新语法与 polyfill 新 API;
- false : 不启用polyfill, 如果 import '@babel/polyfill', 会无视 browserlist 将所有的 polyfill 加载进来;
- entry : 启用,需要手动 import '@babel/polyfill', 这样会根据 browserlist 过滤出 需要的 polyfill;
- usage : 不需要手动import '@babel/polyfill'(加上也无妨,构造时会去掉), 且会根据 browserlist + 业务代码使用到的新 API 按需进行 polyfill
-
modules = 'commonjs'
-
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | false, defaults to "commonjs".转换 es6 模块语法到其他 模块规范, false不会转换
-
include:Array<string|RegExp> = [] 如果你 使用了某个新特性(如es6.array.from),无论browserslist 如何你都想 转化它, 则 include: ['es6.array.from']
-
exclude:Array<string|RegExp> = [] 同理
-
-
loose = false(推荐)
- 优势:代码更加简洁,更容易看懂,可能被老浏览器引擎执行得更快,兼容性更好。
- 缺点:当从 编译后的 es6 代码转换成 原生 es6 代码,有可能出现问题。这不值得冒险启用 loose
- 多数的 babel plugin 有两种模式,普通模式会将代码编译成尽可能接近 es6 语义,loose 模式则会将代码编译成 es5 风格 针对loose,看一个列子
// 源码
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
// 普通模式 更接近 es6
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Point =
/*#__PURE__*/
function () {
function Point(x, y) {
_classCallCheck(this, Point);
this.x = x;
this.y = y;
}
_createClass(Point, [{
key: "toString",
value: function toString() {
return "(".concat(this.x, ", ").concat(this.y, ")");
}
}]);
return Point;
}();
// loose = true编译模式 更接近 es5
var Point =
/*#__PURE__*/
function () {
function Point(x, y) {
this.x = x;
this.y = y;
}
var _proto = Point.prototype;
_proto.toString = function toString() {
return "(" + this.x + ", " + this.y + ")";
};
return Point;
}();
虽然@babel/preset-env实现了对代码的按需进行 polyfill,但是我们可以看到babel 在每个需要的文件的顶部都会插入一些 helpers 代码,这可能会导致多个文件都会有重复的 helpers 代码。针对这个问题,接下来我们再看看@babel/plugin-transform-runtime、@babel/runtime
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime 的 helpers 选项就可以把这些模块抽离出来 具体配置如下
"@babel/plugin-transform-runtime",
{
"corejs": false,
// 默认值,可以不写,可选项2、3,
// 配置 corejs 为 3,需要预先安装 @babel/runtime-corejs3
// 配置 corejs 为 2,需要预先安装 @babel/runtime-corejs2,配置 corejs 为 false,需要预先安装 @babel/runtime
"helpers": true, // 默认,可以不写
"regenerator": false, // 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime
"useESModules": true, // 使用 es modules helpers, 减少 commonJS 语法代码
}
添加新配置后打包出新的代码如下
// 添加新配置后编译出来的代码
import "core-js/modules/es6.promise";
import "regenerator-runtime/runtime";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
var asyncFun =
/*#__PURE__*/
function () {
var _ref = _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(setTimeout, 2000);
case 2:
return _context.abrupt("return", '2s 延时后返回字符串');
case 3:
case "end":
return _context.stop();
}
}
}, _callee, this);
}));
return function asyncFun() {
return _ref.apply(this, arguments);
};
}();
export default asyncFun;
可以看到,已经没有了内联的 helpers 代码,大功告成。
总结
首先安装依赖包:
npm i -S @babel/polyfill @babel/runtime @babel/preset-env @babel/plugin-transform-runtime
完整的babel配置如下
.babelrc.js配置
module.exports = {
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false, // 默认值,可以不写
"helpers": true, // 默认,可以不写
"regenerator": false, // 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime
"useESModules": true, // 使用 es modules helpers, 减少 commonJS 语法代码
}
]
],
presets: [
['@babel/plugin-proposal-decorators', { legacy: true }], //项目中使用到装饰器的话需要
['@babel/plugin-proposal-class-properties'],
[
"@babel/preset-env",
{
"modules": false, // 模块使用 es modules ,不使用 commonJS 规范
"useBuiltIns": 'usage', // 默认 false, 可选 entry , usage
}
]
]
}