🐬 Babel编译转码的范围
Babel默认只转换新的JavaScript语法,而不转换新的API。 例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。 如果想使用这些新的对象和方法,则需要为当前环境提供一个垫片(polyfill)。
🐟 Babel的工作流:
输入字符串 -> @babel/parser parser -> AST -> transformer[s] -> AST -> @babel/generator -> 输出字符串
其中在 transformer[s] 阶段,就是使用plugins来转换代码的阶段。不同的plugin转换特定的代码,而preset是一组完成特定转换的plugins,比如babel-preset-es2017即包含syntax-trailing-function-commas | transform-async-to-generator两个plugin,用于支持ES2017的新特性。
plugin 分为 transform plugin(实际转换代码) 和 syntax-plugin(语法支持,即parse阶段),一般transform plugin包含对应的syntax plugin
以前常用的yearly presets(preset-es2015 | preset-es2016 | preset-es2017)在 Babel 7 中已经不推荐使用了,建议用 preset-env 代替。
🍥 主要的测试方法
// npm install @babel/core @babel/cli @babel/preset-env -D
// npm install @babel/polyfill -S
// npm install @babel/runtime-corejs2 -D
// 文件1:polyfill.js
// import 'babel-polyfill'
console.log([1, 2, 3].includes(2))
console.log(Object.assign({}, {a: 1}))
console.log(Array.from([1,2,3]))
//文件2: babel.config.js
const presets = [
["@babel/env", {
targets: {
node: '0.10.8',
// node: 'current'
},
useBuiltIns: 'usage'
}]
];
const plugins = [
["@babel/plugin-transform-runtime", {
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}]
]
module.exports = { presets, plugins };
// npx babel ./polyfill.js -d dist 得到
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _from = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/from"));
var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign"));
// import 'babel-polyfill'
console.log([1, 2, 3].includes(2));
console.log((0, _assign.default)({}, {
a: 1
}));
console.log((0, _from.default)([1, 2, 3]));
// node dist/polifill.js 得到
true
{ a: 1 }
[ 1, 2, 3 ]
🐠 babel编译代码的几种方式
- 直接执行es6代码
# babel-node 默认是加载了 babel-polyfill 的,所以各种新的API 都能用。
npm i -g babel-cli
#babel7的cli安装方式
npm install --save-dev @babel/core @babel/cli
babel-node es6.js
- babel-register
Node中另一种直接执行ES6代码的方式是使用 babel-register,该库引入后会重写你的require加载器,让你的Node代码中require模块时自动进行ES6转码。例如在你的 index.js 中使用 babel-register:
// index.js
require('babel-register')
...
require('./abc.js') // abc.js可以用ES6语法编写,require时会自动使用babel编译
- babel命令
# 编译 example.js 输出到 compiled.js
babel example.js -o compiled.js
# 或 整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ babel src --out-dir lib
# 或者
$ babel src -d lib
# -s 参数生成source map文件
$ babel src -d lib -s
- API调用babel实现源码编译
npm install babel-core --save-dev // 老版本的babel
npm install @babel/core --save-dev // babel7
var babel = require("@babel/core");
import { transform } from "@babel/core";
import * as babel from "@babel/core";
babel.transform("code();", options, function(err, result) {
result.code;
result.map;
result.ast;
});
- 通过babel-loader调用babel
babel-loader 是无法独立存在运行的。在babel-loader的package.json里你会发现有个 peerDependencies,其中就定义了一个依赖项是webpack。peerDependencies依赖表示了一个模块所依赖的宿主运行环境(一般各种插件的包内会使用 peerDependencies 来表明自己的宿主依赖)。
{
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
]
}
]
}
}
🐡 @babel/polyfill
babel-polyfill通过向全局对象和内置对象的prototype上添加方法来模拟完整的 ES2015+ 环境。
- @babel/polyfill主要用于应用程序(如浏览器,babel-node), 而不是库/工具, 并且使用babel-node时,这个polyfill会自动加载。
- @babel/polyfill包括 core-js2和regenerator runtime模块。
- @babel/polyfill是一次性引入你的项目中的,并且同项目代码一起编译到生产环境。
- 问题1: @babel/polyfill包含所有补丁,不管浏览器是否支持,也不管你的项目是否有用到,都全量引了, 在浏览器中,这些代码体积比较大。
- 问题2: 会污染全局变量。像Map,Array.prototype.find这些就存在于全局空间中。
- 新版本@babel/polyfill 加入了core-js3支持。
shim、polyfill所谓的垫片技术,是通过提前加载一个脚本,给宿主环境添加一些额外的功能。从而让宿主拥有更多的能力。例如可以基于JavaScript的原型能力,给Array.prototype增加额外的方法,就可以一定程度上让宿主环境拥有ES6的能力。除了对ES6+之外,我们还得根据项目情况,添加一些额外的shim或者polyfill。比如fetch、requestAnimationFrame 这种浏览器API,如果我们需要兼容IE8,还需要添加 ES5 shim来兼容更早的JS语法。
🦈 babel-runtime
babel转译代码的时候需要一些工具方法,这些方法默认都会加到用到他们的文件的开头,有时这些方法会很长。babel-runtime就是为了优化这种情况的,将这些工具方法抽离出来,不用每个文件都引入。
- babel-runtime可以看作是一种库/工具,搭配babel-plugin-transform-runtime,一般在开发第三方类库时使用。
- babel-runtime不会污染全局空间和内置对象原型。
- 多次使用只会打包一次。
- 弥补了babel-polyfill的缺点,达到了按需加载的效果。
- 一般在开发第三方类库或工具时使用,一般放在'devDependencies'里。
- babel-runtime的问题: 不能转码实例方法。
// 这只能通过 babel-polyfill来转译,因为 babel-polyfill 是直接在原型链上增加方法。
'!!!'.repeat(3);
'hello'.includes('h');
transform-runtime开启corejs的方案, preset-env的useBuiltIns设置为"usage", preset-env的useBuiltIns可以按需在全局进行polyfill,会require相应实例方法的垫片。
babel-plugin-transform-runtime的作用是分析我们的 ast,通过映射关系插入 babel-rumtime 中的垫片, runtime 编译器插件做了以下三件事:
- 当你使用 generators/async 函数时,自动引入 babel-runtime/regenerator 。
- 自动引入 babel-runtime/core-js 并映射 ES6 静态方法和内置插件。
- 移除内联的 Babel helper 并使用模块 babel-runtime/helpers 代替。
console.log({ ...a });
// 编译后是:
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
console.log(_objectSpread({}, a));
// 然后我们会有很多文件,每个文件都引入一遍 helper 方法,会有很多冗余。所以我们通常会使用 @babel/plugin-transform-runtime 来复用这些 helper 方法。
// 在 .babelrc 里配置:
{
"plugins": [
"@babel/transform-runtime"
]
}
// 编译后是:
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
console.log((0, _objectSpread2.default)({}, a));
🦐 补丁方案
// .babelrc的配置说明:
{
"presets": [
// 预设, Babel 插件的组合套餐
// 插件的执行顺序:
// 插件在 Presets 前运行。
// 插件顺序从前往后排列。
// Preset 顺序是颠倒的(从后往前)
"@babel/preset-react",
// 支持react特有的一些语法,如jsx等
["@babel/preset-env", {
"modules": false,
//该参数的含义是:启用将ES6模块语法转换为另一种模块类型/模块的导入导出方式。将该设置为false就不会转换模块。默认为 auto, 基本会是'commonjs'。
//该值可以有如下:'amd' | 'umd' | 'systemjs' | 'commonjs' | auto | false
//设置为false的原因: 以前我们需要使用babel来将ES6的模块语法转换为AMD, CommonJS,UMD之类的模块化标准语法,但是现在webpack都帮我做了这件事了,所以我们不需要babel来做,因此需要在babel配置项中设置modules为false,这里导成其他的模块类型也没问题,因为webpack的模块支持这些方式。
"targets": {
// tagets 指支持的运行环境
// 运行环境: chrome, opera, edge, firefox, safari, ie, ios, android, node, electron
// 如果不设置,就相当于声明了所有ES2015+的plugin
"browsers": ["last 2 versions", "safari 7", "chrome": 49],
// 支持每个浏览器最后两个版本和safari大于等于7版本所需的polyfill代码转换。
// 支持chrome49所需的polyfill代码转换。
"node": "current"
// 如果通过Babel编译Node.js代码的话,可以设置 "target.node" 是 'current', 含义是 支持的是当前运行版本的nodejs。
},
"loose": false,
// 含义是:允许它们为这个 preset 的任何插件启用"loose" 转换, 默认为false。
// babel编译时,对class的属性采用赋值表达式,而不是Object.defineProperty(更简洁)
// 默认情况下,当在 Babel 下使用模块(module)时,将导出(export)一个不可枚举的 __esModule 属性。
"useBuiltIns": "usage",
// "usage" | "entry" | false, 默认是false
// entry的含义是找到入口文件里引入的 @babel/polyfill,并替换为 targets 浏览器/环境需要的补丁列表。
// usage指按需引入。
// false指不动态添加polyfills,并且不转译 import "core-js" 和 import "@babel/polyfill",会引入全部的polyfills。
"corejs": 2,
// 新版本的@babel/polyfill包含了core-js@2和core-js@3版本,所以需要声明版本,否则webpack运行时会报warning,此处使用core-js@2版本
"debug": false,
// 是否在转码时打印debug信息
"include": [], // 总是启用的 plugins, 可以让babel加载指定名称的插件。
"exclude": [], // 强制不启用的 plugins,可以让babel去除指定名称的插件。
"forceAllTransforms": false, // 强制使用所有的plugins,用于只能支持ES5的uglify可以正确压缩代码
}]
],
"plugins": [
[ "transform-runtime", {
// enables the re-use of Babel's injected helper code to save on codesize.
"corejs": false,
// 默认false,或者数字:{ corejs: 2/3 },代表需要使用corejs的版本。
// 如果是false则使用@babel/runtime,如果是2则使用@babel/runtime-corejs2,除了runtime中的helpers,另外含有Promise,Symbol等。
"helpers": true, // 默认是true,是否替换helpers。
"polyfill": false, // v7无该属性
"regenerator": true, // 默认true,generator是否被转译成用regenerator runtime包装不污染全局作用域的代码。
"useBuiltIns": false, // v7无该属性
"useESModules": false, // 默认false,如果是true将不会用@babel/plugin-transform-modules-commonjs进行转译,这样会减小打包体积,因为不需要保持语义。
"absoluteRuntime": false
}],
[ "import", { "libraryName": "antd", "style": true } ]
]
"env": {
"development":{
"plugins": [ "dva-hmr" ]
},
"production": {}
}
}
- useESModules:如果是true将不会用@babel/plugin-transform-modules-commonjs进行转译,这样会减小打包体积,因为不需要保持语义。
//useESModules: false
exports.__esModule = true;
exports.default = function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
//useESModules: true
export default function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
- useBuiltIns: 'usage':当每个文件里用到(需要polyfill的特性)时,在文件中添加特定的import语句。这可以保证每个polyfill的特性仅load一次。
/// input
var a = new Promise(); // a.js
var b = new Map(); // b.js
/// output
// a.js
import "core-js/modules/es6.promise";
var a = new Promise();
// b.js
import "core-js/modules/es6.map";
var b = new Map();
- useBuiltIns: 'entry':替换import "@babel/polyfill" / require("@babel/polyfill")语句为独立的(根据环境)需要引入的polyfill特性的import语句。
// input
import "@babel/polyfill";
// output
import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
- useBuiltIns: false,babel不会帮你处理任何polyfill的事,你必须手动处理。
- 关于loose mode可参考Babel 6: loose模式。
方案一: corejs2, @babel/preset-env + useBuiltins: entry + targets
- babel-preset-env 能根据当前的运行环境,自动确定你需要的 plugins 和 polyfills。通过各个 es标准 feature 在不同浏览器以及 node 版本的支持情况,再去维护一个 feature 跟 plugins 之间的映射关系,最终确定需要的 plugins。
- 用于替代原来的babel-preset-es20xx/latest(babel-preset-es2015/babel-preset-es2016/babel-preset-es2017)
// 1, 安装的需要的全部依赖。
// npm insall babel-loader@8 @babel/core @babel/preset-env -D
// npm install @babel/polyfill
// 2, 在js代码第一行import '@babel/polyfill',或在webpack的入口entry中写入模块@babel/polyfill。
// 3, 配置文件.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false, // 推荐
"useBuiltIns": "entry", // 推荐
"browsers": ["last 2 versions", "safari 7", "chrome": 49]
"corejs": 2,
// 新版本的@babel/polyfill包含了core-js@2和core-js@3版本,所以需要声明版本,否则webpack运行时会报warning,此处使用core-js@2版本
}
]
],
"plugins": []
}
方案二: corejs3
// 1, 安装的需要的全部依赖。
// npm insall babel-loader@8 @babel/core @babel/preset-env -D
// npm insall core-js regenerator-runtime
// 2, 在js代码第一行,或者入口文件中引入相应的polyfill
// import "core-js/stable"
// import "regenerator-runtime/runtime"
// 3, 配置文件.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "entry",
"browsers": ["last 2 versions", "safari 7", "chrome": 49]
// https://babeljs.io/docs/en/babel-preset-env#usebuiltins
// https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md
"corejs": {
"version": 3, // 使用core-js@3
"proposals": true,
},
"debug": false
}
],
"@babel/preset-react",
"@babel/preset-flow"
],
"plugins": [
"dva-hmr",
// 动态导入, 异步加载语法编译插件
"@babel/plugin-syntax-dynamic-import",
// 可以使用 export v from 'mod'
"@babel/plugin-proposal-export-default-from",
// 装饰器
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
// 类属性, 实例属性
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
],
// antd支持
[
"import",
{
"libraryName": "antd",
"style": true
}
]
]
}
关于使用了@babel/preset-env后,是否还需要使用transform-runtime的问题
基本是不需要的,参考creeperyang/blog#25 里的评论。
🎏 bable7 理解
- core-js(v2)这个库有两个核心的文件夹,分别是 library 和 modules。@babel/runtime-corejs2 使用 library 这个文件夹,@babel/polyfill 使用 modules 这个文件夹。
library 使用 helper 的方式,局部实现某个 api,不会污染全局变量;modules 以污染全局变量的方法来实现 api;
- @babel/polyfill 和@babel/runtime-corejs2 都使用了 core-js(v2)这个库来进行 api 的处理。
- @babel/polyfill: 引用两个包
core-js 和 regenerator-runtime
- @babel/runtime: 引用一个包
regenerator-runtime, 它的devDependencies引用@babel-helpers
- @babel/runtime-corejs2: 引用两个包
core-js和regenerator-runtime, devDependencies引用@babel/helpers
- regenerator-runtime 用来实现generator和async/await
- @babel-helpers 是babel需要的一些函数,默认是放在每个文件的头部的,现在将其抽出来放到一起供各个文件引用这些函数,可以减小代码体积。
- runtime-corejs2 是polyfill和runtime的并集,所以只需要引入@babel/runtime-corejs2就可以替代@babel/polyfill和@babel/runtime了,然后要配置一下
["@babel/plugin-transform-runtime", {"corejs": 2}]
有用的资源
Babel中文文档
各个平台对ES6,ES7等的支持情况
Babel 手册
babel-polyfill VS babel-runtime VS babel-preset-env
Polyfill 方案的过去、现在和未来
强悍的Babel
了解 Babel 6 & 7 生态
@babel/polyfill 还是 @babel/plugin-transform-runtime
corejs3 的更新
🐬 Babel编译转码的范围
Babel默认只转换新的JavaScript语法,而不转换新的API。 例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。 如果想使用这些新的对象和方法,则需要为当前环境提供一个垫片(polyfill)。
🐟 Babel的工作流:
输入字符串 -> @babel/parser parser -> AST -> transformer[s] -> AST -> @babel/generator -> 输出字符串其中在 transformer[s] 阶段,就是使用plugins来转换代码的阶段。不同的plugin转换特定的代码,而preset是一组完成特定转换的plugins,比如babel-preset-es2017即包含syntax-trailing-function-commas | transform-async-to-generator两个plugin,用于支持ES2017的新特性。
🍥 主要的测试方法
🐠 babel编译代码的几种方式
Node中另一种直接执行ES6代码的方式是使用 babel-register,该库引入后会重写你的require加载器,让你的Node代码中require模块时自动进行ES6转码。例如在你的 index.js 中使用 babel-register:
babel-loader 是无法独立存在运行的。在babel-loader的package.json里你会发现有个 peerDependencies,其中就定义了一个依赖项是webpack。peerDependencies依赖表示了一个模块所依赖的宿主运行环境(一般各种插件的包内会使用 peerDependencies 来表明自己的宿主依赖)。
🐡 @babel/polyfill
babel-polyfill通过向全局对象和内置对象的prototype上添加方法来模拟完整的 ES2015+ 环境。
🦈 babel-runtime
babel转译代码的时候需要一些工具方法,这些方法默认都会加到用到他们的文件的开头,有时这些方法会很长。babel-runtime就是为了优化这种情况的,将这些工具方法抽离出来,不用每个文件都引入。
babel-plugin-transform-runtime的作用是分析我们的 ast,通过映射关系插入 babel-rumtime 中的垫片, runtime 编译器插件做了以下三件事:
🦐 补丁方案
方案一: corejs2, @babel/preset-env + useBuiltins: entry + targets
方案二: corejs3
关于使用了@babel/preset-env后,是否还需要使用transform-runtime的问题
基本是不需要的,参考creeperyang/blog#25 里的评论。
🎏 bable7 理解
core-js和regenerator-runtimeregenerator-runtime, 它的devDependencies引用@babel-helperscore-js和regenerator-runtime, devDependencies引用@babel/helpers["@babel/plugin-transform-runtime", {"corejs": 2}]🎣 如何为 Babel 创建插件
有用的资源
Babel中文文档
各个平台对ES6,ES7等的支持情况
Babel 手册
babel-polyfill VS babel-runtime VS babel-preset-env
Polyfill 方案的过去、现在和未来
强悍的Babel
了解 Babel 6 & 7 生态
@babel/polyfill 还是 @babel/plugin-transform-runtime
corejs3 的更新