先放一張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手冊