babel 是一個前端的代碼轉換工具,目的是爲了讓開發者使用ECMA最新的標準甚至一些在stage階段的提案功能,而不用過多考慮運行環境的兼容性。javascript
對代碼進行如下步驟:html
根據開發者的設定,將高版本的ES語法轉化爲瀏覽器或者node環境支持的ES語法前端
固然,babel相關的工具不少,下面咱們以babel7爲例,邊用邊分析。java
若是你想掌握babel的基礎配置,請務必跟上一塊兒操做node
若是你實在不想寫代碼,每一步上都會有一個tag,請務必checkout運行一下對應的代碼webpack
git clone github.com/jinggk/babe…git
運行git checkout step-1 && yarn run build查看當前步驟的結果es6
由於涉及到打包,我選擇了webpack,首先咱們不使用任何babel相關的轉碼,嘗試打包一次看下效果:github
測試代碼以下:web
// src/index.js
const fun = async () => {
const data = await Promise.resolve(123);
console.log(data);
};
fun(); // 123
複製代碼
打包後的代碼以下:
eval("const fun = async () => {\n const data = await Promise.resolve(123);\n console.log(data);\n};\n\nfun();\n\n\n//# sourceURL=webpack:///./src/index.js?")
複製代碼
能夠看到默認狀況下,咱們的代碼不會通過任何的轉化,下一步,咱們加入babel
運行git checkout step-2 && yarn run build查看當前步驟的結果
使用babel以前,咱們須要一個配置文件,以便於告訴babel要按照什麼配置來轉化代碼,有4種配置的方式:
想了解具體內容的能夠看這裏
我採用了官方推薦的方式,建立babel.config.js以下
module.exports = {
presets: ['@babel/env'] // env 所包含的插件將支持全部最新的 JavaScript (ES201五、ES2016 等)特性
};
複製代碼
添加babel相關的依賴
yarn add babel-loader @babel/core @babel/preset-env -D
複製代碼
@babel/core 包含了轉換api的全部核心模塊 @babel/preset-env 一組默認的預設插件的集合,所包含的插件將支持全部最新的 JavaScript (ES201五、ES2016 等)特性 babel-loader 經過這個loader來開啓babel對js的做用
修改webpack config,增長如下內容
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
複製代碼
ok,如今咱們從新嘗試打包,能夠看到打包後的結果變爲:
eval("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); } }\n\nfunction _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); }); }; }\n\nvar fun =\n/*#__PURE__*/\nfunction () {\n var _ref = _asyncToGenerator(\n /*#__PURE__*/\n regeneratorRuntime.mark(function _callee() {\n var data;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _context.next = 2;\n return Promise.resolve(123);\n\n case 2:\n data = _context.sent;\n console.log(data);\n\n case 4:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee);\n }));\n\n return function fun() {\n return _ref.apply(this, arguments);\n };\n}();\n\nfun();\n\n//# sourceURL=webpack:///./src/index.js?");
複製代碼
能夠看到裏面加入了不少輔助的函數,好比 asyncGeneratorStep、_asyncToGenerator 等等用於處理async和await的,可是若是你直接運行當前被轉化後的代碼,你會看到一些報錯
ReferenceError: regeneratorRuntime is not defined
複製代碼
這是由於默認的預設組件雖然會幫咱們轉化代碼,可是不會把相關的輔助函數引入,咱們須要經過修改配置的方式來開啓一些額外的功能,修改babel.config.js以下:
解決的方式不止一種,咱們先用最簡單的嘗試
module.exports = {
presets: [
[
'@babel/env',
{
useBuiltIns: 'usage'
}
]
]
};
複製代碼
@babel/env的參數說明:
targets:配置轉化代碼的目標環境,常見配置有:
"targets": "> 0.25%, not dead"
或者"targets": { "chrome": "58", "ie": "11" }
,瞭解更多選項能夠參考browserslist<script type="module"></script>
配合使用current || '8.11.0'
spec: 我的理解就是說轉化的過程會更嚴格和規範,可是付出的代價就是轉化時間會比較長
loose:Boolean,是否啓用「鬆散」模式來轉化,默認是false 舉個例子,定義一個class:
class Person{
say(){
console.log(123)
}
}
複製代碼
正常狀況下,類的原型方法須要經過Object.defineProperty去定義,以保證不可枚舉,因此轉化後的代碼會是:
var _createClass = (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); // (A)
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();
var Person = (function () {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "say",
value: function say() {
console.log(123)
}
}]);
return Person;
})();
複製代碼
可是若是啓用了loose模式,代碼就會被轉爲:
var Person = (function () {
function Person() {
_classCallCheck(this, Person);
}
Person.prototype.say = function toString() {
console.log(123)
};
return Person;
})();
複製代碼
更像是直接用ES5的形式來模擬類,更詳細的內容能夠查看loose-mode
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
默認是auto,這個應該都明白轉化爲不一樣模塊風格的代碼"entry" | "usage" | false
,用來決定babel/preset如何處理代碼中的 polyfills
咱們這裏使用了useage這個選項,嘗試打包,依舊報錯了..
Can't resolve 'core-js/modules/es6.object.to-string'
Can't resolve 'core-js/modules/es6.promise
Can't resolve 'regenerator-runtime/runtime'
複製代碼
babel7.x 以後把一些依賴所有拆分開了,core-js 和 regenerator-runtime相關的lib函數被放在了@babel/polyfill 這個依賴中,所以咱們須要安裝這個依賴,而且爲了全部的js都能正常使用,咱們須要在js文件的入口去主動require一下這個包以獲取一些工具函數的導入~
yarn add @babel/polyfill
複製代碼
注意這是一個生產環境須要的依賴,不要放到devDependencies中~
而後修改 src/index.js 在最頂部加上require
require('@babel/polyfill');
const fun = async () => {
const data = await Promise.resolve(123);
console.log(data);
};
fun();
複製代碼
如今運行從新打包後的代碼,發現已經能夠正常輸出123了~
上面咱們介紹了使用usage的時候,是能夠不須要polyfill的,下面咱們就來去掉polyfill
運行git checkout step-3 && yarn run build查看當前步驟的結果
加入polyfill會致使默認打包的內容把全部的polyfill內容都帶上這其實會形成一些額外的問題
所以咱們須要一個運行時的插件,能夠作到按需加載須要的工具函數,同時還能夠支持只在使用到的運行的地方能夠獲取到而且不污染全局,這個插件就是@babel/plugin-transform-runtime,同時須要安裝@babel/runtime 這個依賴,@babel/plugin-transform-runtime只適用於開發環境,打包以後真正使用的是從babel/runtime中引入的一些runtime tools,而且在開發環境下有一些transform也是須要藉助babel/runtime來實現,回到項目中,執行下面的命令:
yarn remove @babel/polyfill
yarn add @babel/plugin-transform-runtime -D
yarn add @babel/transform
複製代碼
而後運行build,發現,報錯了,報錯信息以下:
Can't resolve 'core-js/modules/es6.object.to-string'
Can't resolve 'core-js/modules/es6.promise'
複製代碼
爲啥,由於@babel/runtime是不包含一些新的語法和對象的,若是須要對新API和語法的polyfill,則還須要安裝@babel/runtime-corejs2這個依賴,並在plugin-transform-runtime中打開對corejs的使用,運行下面命令:
yarn add @babel/runtime-corejs2
複製代碼
修改babel.config.js
module.exports = {
presets: [
[
'@babel/env',
{
useBuiltIns: 'usage'
}
]
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 2
}
]
]
};
複製代碼
corejs 經常使用2個選項,false或者2,false會污染全局的屬性,而指定爲2則不會污染全局的屬性
設置爲false,打包中對Promise的使用方式:
./node_modules/core-js/modules/_promise-resolve.js
複製代碼
直接去加載依賴會致使全局的屬性被影響,若是設置爲2,打包後的結果是:
var _Promise = __webpack_require__(/*! ../core-js/promise */ \"./node_modules/@babel/runtime-corejs2/core-js/promise.js\ 複製代碼
是會定義一個當前環境下的變量,以後經過_Promise變量來使用promise,這樣就不會影響全局的一些屬性,這對於類庫的開發是很重要的~
當咱們使用了 @babel/runtime-corejs2 後實際上是能夠去掉@babel/runtime了,可是要確保corejs設置爲2,由此也能夠看出來:
@babel/runtime-corejs2 約等於 @babel/runtime + polyfill
babel-plugin-transform-runtime 的全部參數以下:
更多關於 babel-plugin-transform-runtime,請移步到官網查看
babel的插件有不少,可是 @babel/env 和 @babel/plugin-transform-runtime 是每個前端開發者都應該瞭解和掌握的插件,對於別的插件,用到的時候去了解和配置一下應該就能夠了
到此,咱們瞭解了babel的一些基礎配置,留有一個小問題,我並無說關於babel的最佳實踐,由於在每一個不一樣的場景和項目中,我以爲都應該是不同的,歡迎留言討論,你認爲的最佳實踐是什麼?
幾乎每當有新的標準被制定的同時,都會有對應的babel插件的出現,感謝babel社區,讓咱們能開心的使用新語法。
下一篇,咱們一塊兒手擼一個簡單的babel,擁有詞法分析,語法分析,轉化和生成代碼的功能,從底層原理來了解babel,一塊兒期待吧~ 若是當前內容對你有所幫助,請點個贊吧,很是感謝~