Babel 配置用法解析

Babel 配置用法解析

剛復工的時候我司業務太多了,我已不記得咱們連續做戰了多少天,最近算是有時間能夠學習學習個人babel大寶貝了,上週末看了下babel的一些核心模塊以及babel的一些配置,今天繼續以博客的形式記錄總結下來。react


寫前面:babel默認是隻會去轉義js語法的,不會去轉換新的API,好比像Promise、Generator、Symbol這種全局API對象,babel是不會去編譯的。在我學會了babe配置l大法以後,看我一下子怎麼把這些新的API給它編譯出來就完事兒了。
本文基於babel7.8.0。我主要記錄下babel配置須要的一些重要的模塊兒包,來一步步進行babel的一個配置解析(以babel.config.js方式配置爲例)。
本文主要涉及到的一些babel包:@babel/core@babel/cli@babel/plugin*@babel/preset-env@babel/polyfill@babel/runtime@babel/plugin-transform-runtime
那,話很少說,發車?


@babel/core

@babel/core 這個包裏主要都是一些去對代碼進行轉換的核心方法,具體裏面的一些api方法我就不作介紹了;引用官網的一句話:「全部轉換將使用本地配置文件」,因此待會兒咱們的babel.config.js配置文件就很重要了;再一個core就是核心的意思嘛,因此咱們話很少說先把它裝起來,gogogoios

npm install --save-dev @babel/core


@babel/cli

這個 @babel/cli就是babel帶有的內置cli,能夠用來讓咱們從命令行來編譯咱們的文件,有了他咱們就很方便的對babel進行學習了,那話很少說,先裝起來?git

npm install --save-dev @babel/cli

裝完以後你就能夠這樣來編譯你的文件:es6

npx babel study.js --watch --out-file study-compiled.js

簡單介紹下上面命令用到的幾個參數:--out-file用來把編譯後的目標文件輸出到對應的文件;若是但願在每次更改目標文件時都進行編譯,能夠加上 --watch選項;固然還有一些別的選項,不過在我學習babel以及配置的話,這兩個選項已經夠我用了。github

在操做的過程當中若是改了babel配置發現編譯出來的文件並無實時編譯的狀況,須要注意下,若是改了配置文件那就須要從新執行這段命令,要否則babel讀不到新的配置。web

若是你已經建立了study.js文件而且執行了這段命令,你會發現,對應的study-compiled.js還沒發生變化,由於咱們還沒開始寫babel的配置文件,莫慌,立刻開始。

npm

@babel/plugin*和@babel/preset-env

babel插件和babel預設是babel配置的兩個主要模塊,因此我就放在一塊兒說了。json

@babel/plugin*
首先咱們先來講下babelPluginsbabel官網也說了,人babel是基於插件化的,大概就是說全是插件,因此說咱們配置文件裏若是什麼插件也不配的話,那輸入和輸出就是同樣的,插件插件,你得插上我才讓你用。我來編譯一個最簡單的箭頭函數來看下這個babel的插件怎麼用,來了,這波咱們就須要配置文件了,如下全部的配置都是說的在babel.config.js文件裏,相應的插件記得install

/* babel.config.js */

module.exports = {
  presets: [
  ],
  plugins: [
    "@babel/plugin-transform-arrow-functions"
  ]
}

而後執行咱們上面那段cli的命令,就會獲得這種效果:api

/* study.js */
const study = () => {}

/* study-compiled.js */
const study = function () {};

固然,若是咱們想要使用es6給數值新增的指數運算符怎麼辦,只須要添加相應的 @babel/plugin-transform-exponentiation-operator 插件便可:promise

/* babel.config.js */

module.exports = {
  presets: [
  ],
  plugins: [
    "@babel/plugin-transform-arrow-functions",
    "@babel/plugin-transform-exponentiation-operator"
  ]
}

對應的es6語法就會變成:

/* study.js */
const exponentiation = 2 ** 2

/* study-compiled.js */
const exponentiation = Math.pow(2, 2);


@babel/preset-env

從上面的插件化咱們就知道須要哪一個插件就去引入就完事兒,那麼問題來了,es6新增的語法那麼多,我總不能一個插件一個插件去添加吧,這樣也太麻煩了,這個時候就要用到babel提供的presets了。

presets也就是預設的意思,大概意思就是能夠預先設定好一些東西,就免得咱們一個個的去引入插件了。官方提供了不少presets,好比preset-env(處理es6+規範語法的插件集合)、preset-stage(一些處理尚在提案階段的語法的插件集合,固然這種預設的方式在 babel 7+ 版本已經被廢棄了)、preset-react(處理react語法的插件集合)等等。

咱們主要來介紹下preset-envpreset-env是一個智能預設,配置了它就可讓你用es6+去書寫你的代碼,並且他會按需去加載所須要的插件,讓你的生活更加美好。。。接下來咱們記得先install這個 @babel/preset-env一波,不配任何插件,而後咱們再來看看效果如何:

/* babel.config.js */

module.exports = {
  presets: [
    "@babel/preset-env"
  ],
  plugins: [
  ]
}

對應的es6語法就會變成:

/* study.js */
const study = () => {}

const arr1 = [1, 2, 33]
const arr2 = [...arr1]

const exponentiation = 2 ** 2

// 新增API
new Promise(() => {})
new Map()


/* study-compiled.js */
var study = function study() {};

var arr1 = [1, 2, 33];
var arr2 = [].concat(arr1);
var exponentiation = Math.pow(2, 2);

// 新增API
new Promise(function () {});
new Map();

你會發現es6+的語法都被編譯了,咱們並無設置任何插件哦,應該也看到了新增的API方法並無被編譯,在這裏咱們埋下伏筆,等下文講到polyfill的時候再治他。

Browserslist集成

關於preset-env,咱們還能夠提供一個targets配置項指定運行環境,就是咱們能夠配置對應目標瀏覽器環境,那麼babel就會編譯出對應目標瀏覽器環境能夠運行的代碼。相信有同窗遇到過在低版本系統ios手機裏本身的項目會白屏,實際上是某些語法在ios低版本系統裏不支持,這個時候咱們能夠直接配置ios 7瀏覽器環境均可以支持的代碼:

/* babel.config.js */

module.exports = {
  presets: [
    [
      "@babel/preset-env", {
        'targets': {
          'browsers': ['ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS瀏覽器版本7
        }
      }
    ]
  ],
  plugins: [
  ]
}

固然babelBrowserslist集成還支持在package.json文件裏或者新建一個 .browserslistrc 文件來指定對應目標環境。browserslist配置源

@babel/polyfill(由core-js2和regenerator-runtime組成的一個集成包)

上文也提到了像Promise這種API我們的babel並無給轉義,那是由於babel默認是隻會去轉義js語法的,不會去轉換新的API,好比像Promise、Generator、Symbol這種全局API對象,babel是不會去編譯的,這個時候就要掏出 @babel/polyfill 了。用法很簡單,先安裝一波,而後咱們只須要在入口文件頂部引入 @babel/polyfill 就可使用新增的API了。

/* study.js */
import '@babel/polyfill'
// 新增API
new Promise(function () {});

/* study-compiled.js */
require("@babel/polyfill");
// 新增API
new Promise(function () {});

小細節:import被編譯成了require,若是想要編譯出來的模塊引入規範仍是import,則能夠在preset-env的配置項中添加"modules": false便可。
modules的options:"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默認爲"auto"

可是問題又來了,有時候咱們項目裏並無用到那麼多的新增API,可是 @babel/polyfill 會把全部瀏覽器環境的的polyfill都引入,整個包的體積就會很大,咱們想要對目標環境按需引入相應的polyfill應該怎麼辦呢,這個時候咱們就可使用 preset-env 的配置項中的useBuiltIns屬性來按需引入polyfill。

/* babel.config.js */

module.exports = {
  presets: [
    [
      "@babel/preset-env", {
        "modules": false,
        "useBuiltIns": "entry",
        'targets': {
          'browsers': ['ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS瀏覽器版本7
        }
      }
    ]
  ],
  plugins: [
  ]
}

這個時候就會在入口處只把全部ie8以上以及iOS 7瀏覽器不支持api的polyfill引入進來。

最終效果:

/* study.js */
import '@babel/polyfill'
// 新增API
new Promise(function () {});

/* study-compiled.js */
import "core-js/modules/es6.array.copy-within";
import "core-js/modules/es6.array.every";
...//省略若干
import "core-js/modules/web.immediate";
import "core-js/modules/web.dom.iterable";
import "regenerator-runtime/runtime";
// 新增API
new Promise(function () {});

此時你會發現,import '@babel/polyfill'沒有了,引入的是咱們目標環境相應的polyfill。可是有沒有發現引入的都是import 'core-js/...'的內容,標題已經說啦,@babel/polyfil是由core-js2和regenerator-runtime組成的一個集成包。

這個時候你又會想,假如個人項目裏面只用到了Promise這個API,能不能只給我引入Promise相應的API呢?答案是必能夠!,讓咱們先來好好了解下preset-env的配置項中的useBuiltIns屬性。

useBuiltIns

選項:"usage"| "entry"| false,默認爲false。

entry咱們已經用過了,意義就是在入口處將根據咱們配置的瀏覽器兼容,將目標瀏覽器環境全部不支持的API都引入。

usage就很nb了,當配置成usage的時候,babel會掃描你的每一個文件,而後檢查你都用到了哪些新的API,跟進咱們配置的瀏覽器兼容,只引入相應API的polyfill,咱們把useBuiltIns屬性設置爲usage再來看下編譯效果:

/* study.js */
import '@babel/polyfill'
// 新增API
new Promise(function () {});

/* study-compiled.js */
import "core-js/modules/es.object.to-string";
import "core-js/modules/es.promise";
// 新增API
new Promise(function () {});

我就問你帥不帥!徹底的按需引入,牛逼了!

相信你也看到了一個東西,當咱們使用useBuiltIns選項的時候,你的命令行裏面是否是顯示了一坨這樣的警告,大概是在配置文件中未指定core-js版本時,默認會使用core-js2:

WARNING: We noticed you're using the useBuiltIns option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the corejs option.

前面也說到了 @babel/polyfil 是由core-js2和regenerator-runtime組成的一個集成包,如今core-js3已經發布了,並且很穩定。可是core-js2在18年的時候已經再也不維護了;@babel/polyfil引入的是2不是3,而且 @babel/polyfill 在babel7.4.0已經再也不推薦使用了,要廢掉(好像是由於@babel/polyfill不支持core-js2平滑的過渡到core-js3)。因此core-js官方如今推薦咱們使用polyfill的時候直接引入core-js和regenerator-runtime/runtime這兩個包徹底取代 @babel/polyfil 來爲了防止重大更改。

固然,咱們須要在preset-env配置項中指定core-js版本,這樣就不會再有警告⚠️了:

/* babel.config.js */

module.exports = {
  presets: [
    [
      "@babel/preset-env", {
        "modules": false,
        "useBuiltIns": "entry",
        "corejs": "3",
        'targets': {
          'browsers': ['not ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS瀏覽器版本7
        }
      }
    ]
  ],
  plugins: [
  ]
}


@babel/runtime(依賴@babel/helpers和regenerator-runtime)

有的時候一些語法的轉換會比較複雜,babel會引入一些helper函數,好比說對es6的class進行轉換:

/* study.js */
class Test {}

/* study-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);
};

能夠看到上面引入了helper函數來處理class的轉換。可是問題又來了,若是好多文件都使用到了複雜語法的轉換,這個仍是簡單點的,有些helper函數是很複雜代碼量不少的,那豈不是每一個文件都會定義一遍這些個函數,每一個文件的代碼會不少?若是說能夠把這些helper函數都抽離到一個公共的包裏,用到的地方只須要引入對應的函數便可,咱們的編譯出來的代碼量會大大滴減小,這個時候就須要用到 @babel/plugin-transform-runtime 插件來配合@babel/runtime進行使用。記得先安裝一波,而後在插件選項中加入 @babel/plugin-transform-runtime 這個插件,而後咱們來看看編譯後的效果:

/* study.js */
class Test {}

/* study-compiled.js */
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";

var Test = function Test() {
  _classCallCheck(this, Test);
};

固然若是咱們只是爲了減小編譯出來的文件中代碼量而使用這個插件的話就過小看他了,並且也沒有必要。

@babel/plugin-transform-runtime還有一個最重要的做用:好比說像上面咱們說的Promise就須要提供相應的polyfill去解決,這樣作會有一個反作用,就是會污染全局變量。若是咱們只是在一個業務項目這樣搞還好,也沒別人要用到。可是若是咱們是在維護一個公共的東西,好比公共組件庫,咱們這樣搞,你的一些polyfill可能會把一些全局的api給改掉,反作用就會很明顯,別人用你的組件庫的時候就可能會出問題。@babel/plugin-transform-runtime插件爲咱們提供了一個配置項corejs,他能夠給這些polyfill提供一個沙箱環境,這樣就不會污染到全局變量,無反作用你說美不美。

記得安裝 @babel/runtime-corejs2 這個包(穩定版用2就能夠),注意若是不配置的話,是不會提供沙箱環境的。而後在 @babel/plugin-transform-runtime 插件配置corejs:

/* babel.config.js */

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        "modules": false,
        "useBuiltIns": "usage",
        "corejs": "3",
        'targets': {
          'browsers': ["ie >= 8", "iOS 7"] // 支持ie8,直接使用iOS瀏覽器版本7
        }
      }
    ]
  ],
  plugins: [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 2
      }
    ]
  ]
}

咱們來看下編譯後的效果:

/* study.js */
new Promise(() => {})
class Test {}

/* study-compiled.js */
import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck";
import _Promise from "@babel/runtime-corejs2/core-js/promise";

new _Promise(function () {});
var Test = function Test() {
  _classCallCheck(this, Test);
};

小節

  • 在你修改了babel配置項以後必定要記得重啓編譯命令,不然不會生效
  • 維護公共組件庫或者一些別的公共庫推薦要使用@babel/runtime配合@babel/plugin-transform-runtime來創建沙箱環境

接下來本人會去繼續研究babel是如何解析編譯的,target:理解babel如何解析編譯,可以手寫一個babel插件出來。最近需求比較多,下一篇估計得等到Q2了。。。

寫在最後:

最近也是疫情期間,你們必定要記得儘可能少出門,出門必帶口罩。像白衣天使們致敬!

相關文章
相關標籤/搜索