標題黨了哈哈哈~~~react
原文地址git
相信不少人和筆者從前同樣,babel的配置都是從網上覆制黏貼或者使用現成的腳手架,雖然這可以工做但仍是但願你們可以知其因此然,所以本文將對babel(babel@7)的配置作一次較爲完整的梳理。es6
es6增長的內容能夠分爲語法和api兩部分,搞清楚這點很重要,新語法好比箭頭函數、解構等:github
const fn = () => {} const arr2 = [...arr1]
新的api好比Map、Promise等:web
const m = new Map() const p = new Promise(() => {})
@babel/core,看名字就知道這是babel的核心,沒他不行,因此首先安裝這個包shell
npm install @babel/core
它的做用就是根據咱們的配置文件轉換代碼,配置文件一般爲.babelrc
(靜態文件)或者babel.config.js
(可編程),這裏以.babelrc
爲例,在項目的根目錄下建立一個空文件命名爲.babelrc
,而後建立一個js文件(test.js)測試用:npm
/* test.js */ const fn = () => {}
這裏咱們安裝下@babel/cli
以便可以在命令行使用babel編程
npm install @babel/cli
安裝完成後執行babel編譯,命令行輸入json
npx babel test.js --watch --out-file test-compiled.js
結果發現test-compiled.js的內容依然是es6的箭頭函數,不用着急,咱們的.babelrc尚未寫配置呢api
Now, out of the box Babel doesn't do anything. It basically acts like const babel = code => code; by parsing the code and then generating the same code back out again. You will need to add plugins for Babel to do anything.
上面是babel官網的一段話,能夠理解爲babel是基於插件架構的,假如你什麼插件也不提供,那麼babel什麼也不會作,即你輸入什麼輸出的依然是什麼。那麼咱們如今想要把剪頭函數轉換爲es5函數只須要提供一個箭頭函數插件就能夠了:
/* .babelrc */ { "plugins": ["@babel/plugin-transform-arrow-functions"] }
轉換後的test-compiled.js爲:
/* test.js */ const fn = () => {} /* test-compiled.js */ const fn = function () {}
那我想使用es6的解構語法怎麼辦?很簡單,添加解構插件就好了:
/* .babelrc */ { "plugins": [ "@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-destructuring" ] }
問題是有那麼多的語法須要轉換,一個個的添加插件也太麻煩了,幸虧babel提供了presets
,他能夠理解爲插件的集合,省去了咱們一個個引入插件的麻煩,官方提供了不少presets,好比preset-env
(處理es6+規範語法的插件集合)、preset-stage
(處理尚處在提案語法的插件集合)、preset-react
(處理react語法的插件集合)等,這裏咱們主要介紹下preset-env
:
/* .babelrc */ { "presets": ["@babel/preset-env"] }
@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s).
以上是babel官網對preset-env
的介紹,大體意思是說preset-env
可讓你使用es6的語法去寫代碼,而且只轉換須要轉換的代碼。默認狀況下preset-env
什麼都不須要配置,此時他轉換全部es6+的代碼,然而咱們能夠提供一個targets配置項指定運行環境:
/* .babelrc */ { "presets": [ ["@babel/preset-env", { "targets": "ie >= 8" }] ] }
此時只有ie8以上版本瀏覽器不支持的語法纔會被轉換,查看咱們的test-compiled.js文件發現一切都很好:
/* test.js */ const fn = () => {} const arr1 = [1, 2, 3] const arr2 = [...arr1] /* test-compiled.js */ var fn = function fn() {}; var arr1 = [1, 2, 3]; var arr2 = [].concat(arr1);
如今咱們稍微改一下test.js:
/* test.js */ const fn = () => {} new Promise(() => {}) /* test-compiled.js */ var fn = function fn() {}; new Promise(function () {});
咱們發現Promise並無被轉換,什麼!ie8還支持Promise?那是不可能的...。還記得本文開頭提到es6+規範增長的內容包括新的語法和新的api,新增的語法是能夠用babel來transform的,可是新的api只能被polyfill,所以須要咱們安裝@babel/polyfill
,再簡單的修改下test.js以下:
/* test.js */ import '@babel/polyfill' const fn = () => {} new Promise(() => {}) /* test-compiled.js */ import '@babel/polyfill'; var fn = function fn() {}; new Promise(function () {});
如今代碼能夠完美的運行在ie8的環境了,可是還存在一個問題:@babel/polyfill
這個包的體積太大了,咱們只須要Promise就夠了,假如可以按需polyfill就行了。真巧,preset-env
恰好提供了這個功能:
/* .babelrc */ { "presets": [ ["@babel/preset-env", { "modules": false, "useBuiltIns": "entry", "targets": "ie >= 8" }] ] }
咱們只需給preset-env
添加一個useBuiltIns
配置項便可,值能夠是entry
和usage
,假如是entry
,會在入口處把全部ie8以上瀏覽器不支持api的polyfill引入進來,以下:
/* test.js */ import '@babel/polyfill' const fn = () => {} new Promise(() => {}) /* test-compiled.js */ import "core-js/modules/es6.array.copy-within"; import "core-js/modules/es6.array.every"; import "core-js/modules/es6.array.fill"; ... //省略若干引入 import "core-js/modules/web.immediate"; import "core-js/modules/web.dom.iterable"; import "regenerator-runtime/runtime"; var fn = function fn() {}; new Promise(function () {});
細心的你會發現transform後,import '@babel/polyfill'
消失了,反卻是多了一堆import 'core-js/...'
的內容,事實上,@babel/polyfill
這個包自己是沒有內容的,它依賴於core-js
和regenerator-runtime
這兩個包,這兩個包提供了es6+規範的運行時環境。所以當咱們不須要按需polyfill時直接引入@babel-polyfill
就好了,它會把core-js
和regenerator-runtime
所有導入,當咱們須要按需polyfill時只需配置下useBuiltIns
就好了,它會根據目標環境自動按需引入core-js
和regenerator-runtime
。
前面還提到useBuiltIns
的值還能夠是usage
,其功能更爲強大,它會掃描你的代碼,只有你的代碼用到了哪一個新的api,它纔會引入相應的polyfill:
/* .babelrc */ { "presets": [ ["@babel/preset-env", { "modules": false, "useBuiltIns": "usage", "targets": "ie >= 8" }] ] }
transform後的test-compiled.js相應的會簡化不少:
/* test.js */ const fn = () => {} new Promise(() => {}) /* test-compiled.js */ import "core-js/modules/es6.promise"; import "core-js/modules/es6.object.to-string"; var fn = function fn() {}; new Promise(function () {});
遺憾的是這個功能還處於試驗狀態,謹慎使用。
事實上假如你是在寫一個app的話,以上關於babel的配置差很少已經夠了,你可能須要添加一些特定用途的Plugin
和Preset
,好比react項目你須要在presets
添加@babel/preset-react
,假如你想使用動態導入功能你須要在plugins
添加@babel/plugin-syntax-dynamic-import
等等,這些不在贅述。假如你是在寫一個公共的庫或者框架,下面提到的點可能還須要你注意下。
有時候語法的轉換相對複雜,可能須要一些helper函數,如轉換es6的class:
/* test.js */ class Test {} /* test-compiled.js */ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Test = function Test() { _classCallCheck(this, Test); };
示例中es6的class須要一個_classCallCheck輔助函數,試想假如咱們多個文件中都用到了es6的class,那麼每一個文件都須要定義一遍_classCallCheck函數,這也是一筆不小的浪費,假如將這些helper函數抽離到一個包中,由全部的文件共同引用則能夠減小可觀的代碼量。而@babel/runtime
作的正好是這件事,它提供了各類各樣的helper函數,可是咱們如何知道該引入哪個helper函數呢?總不能本身手動引入吧,事實上babel提供了一個@babel/plugin-transform-runtime
插件幫咱們自動引入helper。咱們首先安裝@babel/runtime
和@babel/plugin-transform-runtime
:
npm install @babel/runtime @babel/plugin-transform-runtime
而後修改babel配置以下:
/* .babelrc */ { "presets": [ ["@babel/preset-env", { "modules": false, "useBuiltIns": "usage", "targets": "ie >= 8" }] ], "plugins": [ "@babel/plugin-transform-runtime" ] }
如今咱們再來看test-compiled.js文件,裏面的_classCallCheck輔助函數已是從@babel/runtime
引入的了:
/* test.js */ class Test {} /* test-compiled.js */ import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; var Test = function Test() { _classCallCheck(this, Test); };
看到這裏你可能會說,這不扯淡嘛!幾個helper函數能爲我減小多少體積,我才懶得安裝插件。事實上@babel/plugin-transform-runtime
還有一個更重要的功能,它能夠爲你的代碼建立一個sandboxed environment
(沙箱環境),這在你編寫一些類庫等公共代碼的時候尤爲重要。
上文咱們提到,對於Promise、Map等這些es6+規範的api咱們是經過提供polyfill兼容低版本瀏覽器的,這樣作會有一個反作用就是污染了全局變量,假如你是在寫一個app還好,但若是你是在寫一個公共的類庫可能會致使一些問題,你的類庫可能會把一些全局的api覆蓋掉。幸虧@babel/plugin-transform-runtime
給咱們提供了一個配置項corejs
,它能夠將這些變量隔離在局部做用域中:
/* .babelrc */ { "presets": [ ["@babel/preset-env", { "modules": false, "targets": "ie >= 8" }] ], "plugins": [ ["@babel/plugin-transform-runtime", { "corejs": 2 }] ] }
注意:這裏必定要配置corejs,同時安裝@babel/runtime-corejs2
,不配置的狀況下@babel/plugin-transform-runtime
默認是不引入這些polyfill的helper的。corejs的值現階段通常指定爲2,能夠近似理解爲是@babel/runtime
的版本。咱們如今再來看下test-compiled.js被轉換成了什麼:
/* test.js */ class Test {} new Promise(() => {}) /* test-compiled.js */ import _Promise from "@babel/runtime-corejs2/core-js/promise"; import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck"; var Test = function Test() { _classCallCheck(this, Test); }; new _Promise(function () {});
如咱們所願,已經爲Promise的polyfill建立了一個沙箱環境。
最後咱們再爲test.js稍微添加點內容:
/* test.js */ class Test {} new Promise(() => {}) const b = [1, 2, 3].includes(1) /* test-compiled.js */ import _Promise from "@babel/runtime-corejs2/core-js/promise"; import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck"; var Test = function Test() { _classCallCheck(this, Test); }; new _Promise(function () {}); var b = [1, 2, 3].includes(1);
能夠發現,includes
方法並無引入輔助函數,可這明明也是es6裏面的api啊。這是由於includes
是數組的實例方法,要想polyfill必須修改Array
的原型,這樣一來就污染了全局環境,所以@babel/plugin-transform-runtime
是處理不了這些es6+規範的實例方法的。
以上基本是本文的所有內容了,最後再來個總結和須要注意的地方:
preset-stage
,事實上babel@7已經不推薦使用它了,假如你須要使用尚在提案的語法,請直接添加相應的plugin。preset-env
配置polyfill@babel/runtime
,須要注意一些實例方法的使用babel-loader
的版本...待補充
全文完