先放一张Babel的基本流程图javascript
babel是一个JavaScript代码转换器,主要目的是将ES2015+的代码转换成llq或者其余环境识别的代码,主要目的有如下几个:java
安装node
npm install @babel/core -D
复制代码
使用git
const babel = require('@babel/core')
babel.transform("code",optionsObject)
复制代码
Babel的核心包提供了一些最基础的API,好比:babel.transformFile
、babel.transformFromAst
等等,具体可查看,这里es6
@babel/cli
包依赖@babel/core
,因此须要同时安装这两个包。github
npm install @babel/core @babel/cli
复制代码
安装完这两个包后,Babel提供了一个babel
命令,假如如今要 将src文件夹下的js文件编译到lib目录下,执行以下命令:web
./node_modules/.bin/babel src --out-dir lib
复制代码
或者:npm
npx babel src --out-dir lib
复制代码
固然,在使用命令行时能够给babel传递参数,好比下面这样传递plugins:json
./node_modules/.bin/babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions
复制代码
或者传递presetsapi
./node_modules/.bin/babel src --out-dir lib --presets=@babel/env
复制代码
Babel核心包并不会去转换代码,核心包只提供一些核心API,真正的代码转换工做由插件或者预设来完成,好比要转换箭头函数,会用到这个plugin
,@babel/plugin-transform-arrow-functions
,当须要转换的要求增长时,咱们不可能去一一配置相应的plugin,这个时候就能够用到预设了,也就是presets
。presets
是plugins
的集合,一个presets
内部包含了不少plugin
。
Babel转换代码分为两部分,第一部分是将新的语法转换为普通语法,好比下面这样,将ES6中的箭头函数转换为ES5:
转换前:
let fn = (a, b) => a + b;
复制代码
转换后:
var fn = function fn(a, b) {
return a + b;
};
复制代码
第二部分是,模拟新的API,好比说,咱们代码中使用了Promise
,而目标环境不支持Promise
,那么Babel会手动实现一个Promise
,使得目标环境支持Promise
,这个过程叫作polyfill
。
看个例子,下面咱们建立一个项目,目录以下:
node_modules
项目依赖包src
源文件babel.config.js
Babel的配置文件package.json
项目描述文件src
文件下有个index.js
文件,也就是须要编译的文件,内容以下:
let count = 1; //let 声明的变量
let fn = (a, b) => a + b; //箭头函数
let obj = { a: 1, b: 2 }
let b = { ...obj } //解构赋值
let promise = new Promise(() => { //promise
resolve(1)
})
function* it() { //generator 函数
yield 1;
yield 2;
return 3;
}
复制代码
babel.config.js
内容以下:
module.exports = {
presets: [
[
"@babel/env",
{
useBuiltIns: "usage",//也能够写entry
corejs: "2.6.9"
},
]
]
}
复制代码
编译后代码以下:
"use strict";
require("core-js/modules/es6.array.for-each");
require("core-js/modules/es6.array.filter");
require("core-js/modules/es6.symbol");
require("core-js/modules/web.dom.iterable");
require("core-js/modules/es6.array.iterator");
require("core-js/modules/es6.object.keys");
require("core-js/modules/es6.object.define-property");
require("regenerator-runtime/runtime");
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
var _marked =
/*#__PURE__*/
regeneratorRuntime.mark(it);
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; }
var count = 1; //let 声明的变量
var fn = function fn(a, b) {
return a + b;
}; //箭头函数
var obj = {
a: 1,
b: 2
};
var b = _objectSpread({}, obj); //解构赋值
var promise = new Promise(function (resolve) {
//promise
resolve(1);
});
function it() {
return regeneratorRuntime.wrap(function it$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 1;
case 2:
_context.next = 4;
return 2;
case 4:
return _context.abrupt("return", 3);
case 5:
case "end":
return _context.stop();
}
}
}, _marked);
}
复制代码
编译后的代码量增长了不少,仔细分析大概分为这几部分:
一、require
语句,大部分是从core.js
包分别导入具体API对应的polyfill
。
二、由于使用了generator
函数,须要去regenerator-runtime
包中导入对应方法
三、对箭头函数,解构赋值、generator
函数进行改写。
由于咱们在配置文件中配置了这项:
{
useBuiltIns: "usage",
corejs: "2.6.9"
},
复制代码
这个配置表示文件内用到了什么API,那么就转换什么API,没用到的不用转,也就是按需加载,因此咱们会看到那么多的require语句。
还有一种方法是,不须要在每一个文件都进行转换,由于这样会致使代码重复,同一个API在不一样的文件都被转换了。
那么咱们只须要在入口文件中手动引入polyfill
便可。以下:
import '@babel/polyfill'
复制代码
polyfill
后,咱们就能够使用Promise
、WeakMap
、Array.from
、Object.assign
、Array.prototype.includes
等方法了。
有种场景下,咱们可能不须要使用这么多方法,好比在编写库或者工具的时候,那咱们就能够使用transform-runtime
来取代@babel/polyfill
了。
项目根目录下新建babel.config.js
,内容以下:
module.exports = function (api) {
api.cache(true);
const presets = [ ... ];
const plugins = [ ... ];
return {
presets,
plugins
};
}
复制代码
具体配置,可参考,这里
项目根目录下新建.babelrc
,内容以下:
{
"presets": [...],
"plugins": [...]
}
复制代码
具体配置,可参考,这里
也能够在package.json
文件中配置
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
复制代码
这种方法和.babelrc
配置的惟一区别就是,能够编写js代码,好比:
const presets = [ ... ];
const plugins = [ ... ];
if (process.env["ENV"] === "prod") {
plugins.push(...);
}
module.exports = { presets, plugins };
复制代码
babel --plugins @babel/plugin-transform-arrow-functions script.js
复制代码
具体配置,可参考https://babeljs.io/docs/en/babel-cli
require("@babel/core").transform("code", {
plugins: ["@babel/plugin-transform-arrow-functions"]
});
复制代码
具体配置,可参考,这里
插件的写法:
{
"plugins": ["babel-plugin-myPlugin"]
}
复制代码
{
"plugins": ["./node_modules/asdf/plugin"]
}
复制代码
babel-plugin-
开头的,就能够缩写{
"plugins": [
"myPlugin",
"babel-plugin-myPlugin" // equivalent
]
}
复制代码
几条规则:
plugin
后preset
。plugin
的顺序是从左到右。first to lastpreset
的顺序是从右到左。last to firstpreset
的顺序为啥是这样的,官方是这样说的:
This is mostly for ensuring backwards compatibility, since most users list "es2015" before "stage-0"。
插件的写法有如下三种,第三种就是传递参数的形式
{ 1 2 3
"plugins": ["pluginA", ["pluginA"], ["pluginA", {}]]
}
复制代码
具体一点就是这样:
{
"plugins": [
[
"transform-async-to-module-method",
{
"module": "bluebird",
"method": "coroutine"
}
]
]
}
复制代码
preset也同样:
{
"presets": [
[
"env",
{
"loose": true,
"modules": false
}
]
]
}
复制代码
一个插件长这样:
export default function() {
return {
visitor: {
Identifier(path) {
const name = path.node.name;
// reverse the name: JavaScript -> tpircSavaJ
path.node.name = name
.split("")
.reverse()
.join("");
},
},
};
}
复制代码
具体可参考babel手册