babel歸納總結

github: babel歸納總結

在前端的發展過程中,javascript的兼容性,一直是前端頭痛的問題,在以前的一些有些項目中,爲解決瀏覽器兼容而花費的時間甚至還要多餘實際的業務邏輯開發時間,babel就是其中處理兼容的轉譯工具(或者叫平臺)。

babel是什麼

javascript在不斷髮展,新的提案標準每年都會有,在得到廣泛普及之前,Babel 把用最新標準編寫的 JavaScript 代碼向下編譯成可以在今天隨處可用的版本

babel的編譯過程分爲3步,解析(parse),轉換(transform),生成(generate),對應的三個插件分別是Babylonbabel-traversebabel-generator
babylon將源碼轉換爲抽象語法樹(AST);babel-traverse通過AST生成一個便於操作、轉換的path對象,供我們的babel插件處理;babel-generator讀取AST並將其轉換爲代碼和源碼映射。這些過程不是本文的關注點,我們關注的是結果,哪些插件與我們的生產息息相關,我們如何去使用babel的插件。

通過vue中的babel配置來了解babel

vue腳手架生成的項目在.babelrc文件中的配置:

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"]
}

plugin配置項

babel插件推崇的是功能的單一性,就是每個插件的功能儘可能的單一,比如我要使用es6的箭頭函數,那就可以裝一個轉換插件npm i -D @babel/plugin-transform-arrow-functions,將其寫進.babelrc文件裏就行了:詳情

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

這樣,我們寫的:

(a) => [...a]

會被該插件轉換爲:

function (a) {
  return [...a]
}

這個插件也只解決箭頭函數的轉換,就算函數內部用了其它新語法也不管,這個好處是很明顯的,就跟咱寫項目推崇組件的細膩度一個道理

presets配置項

然而呢,js發展有點快,想一下那個es2015(es6)一下加了多少東西,我們要使用還得一個一個的npm i -D xxx,這個有點小麻煩,所以就可以採用presets配置項。npm i -D babel-preset-es2015,然後配置.babelrc詳情

爲了承接上文,這裏暫時先用babel6的寫法,babel7裏也可以用babel-preset-es2015,但是文檔裏去掉了,es2015、es2016、es2017(2018年的東西直接寫在env裏了,7月份2019年的新標準就要來羅@_@)等都被放在env裏面了,以後這幾個preset會不會砍掉就不知道咯
{
  "presets": ["es2015"],
  "plugins": []
}

這樣我們就可以使用包括箭頭函數在內的es6的新語法而不用去擔心兼容問題。這下這兩個的關係也就清晰了,presets裏面配置的是一些plugins集合

babel 7.3.0裏面,presets -- 對應插件有這些:

  • env -- @babel/preset-env
  • stage-0 -- @babel/preset-stage-0
  • stage-1 -- @babel/preset-stage-1
  • stage-2 -- @babel/preset-stage-2
  • stage-3 -- @babel/preset-stage-3
  • flow -- @babel/preset-flow
  • react -- @babel/preset-react
  • minify -- babel-preset-minify
  • typescript -- @babel/preset-typescript

env

在presets配置裏面,我們看到了:

["env", {
  "modules": false,
  "targets": {
    "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
  }
}]

這個env是@babel/preset-env這個集合插件配置項,這裏的配置項:

  • modules:"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto".

    • 意思就是讓babel把es6 模塊轉化爲其它模塊化類型。如果選擇 false 就不進行模塊化轉,我們的目標是瀏覽器,es6以前js是沒有模塊化的,commonjs、amd等只是社區方案,沒有瀏覽器支持的,所以我們設置爲false,如果我們寫node上運行的代碼,就要設置爲"commonjs"
  • target:就是告訴babel你的js要兼容哪些環境,它會幫你將你寫的js轉譯成目標環境兼容的js語法,這個具體配置可以看browserslist

那就是說,js無論用什麼新玩意,@babel/preset-env都能跟我兼容到我想要的環境?帶着問題,我們再看看官網的介紹,What is Babel

  1. Transform syntax(轉換語法)
  2. Polyfill features that are missing in your target environment (Ployfill新特性--也就是Api)
  3. Source code transformations(源碼轉換)
  4. And more!

所以呢,我們的.babelrc文件裏面配置的只是轉換語法的,所以,對於上面的問題答案是“js無論用什麼新語法@babel/preset-env都能幫你兼容到目標環境”。比如:

a => a
// 轉爲
function (a) {return a}

至於第二點,api的polyfill,我們在下面介紹

@babel/preset-env具體可以幫我們轉化哪些呢?看這兒JavaScript新特性和Babel插件的映射關係,這個是@babel/preset-env集合插件所包含的插件列表,每個插件對應轉換一個新特性,至於沒有的,比如promise,請往下看。

stage-2

在上面的配置中,我們看到env下面有個stage-2。stage-x,這裏麪包含的都是當年最新規範的草案,每年更新。細分爲如下幾步

  • Stage 0 - 稻草人: 只是一個想法,經過 TC39 成員提出即可。
  • Stage 1 - 提案: 初步嘗試。
  • Stage 2 - 初稿: 完成初步規範。
  • Stage 3 - 候選: 完成規範和瀏覽器初步實現。
  • Stage 4 - 完成: 將被添加到下一年度發佈。

官網裏有一句話It is important to note that @babel/preset-env does not support stage-x plugins.,就是說@babel/preset-env中不包含在草案階段的新屬性

其實我們通過plugin-features,以及proposals/finished-proposals(其中2019就是今年的stage-4),可以發現@babel/preset-env是包含了stage-4階段的plugins的。

比如寫react的同學比較熟悉的decorators目前就處於stage-2階段,我們要用這些處於草案階段的新屬性,可以安裝npm i -D @babel/preset-stage-2,然後在presets裏寫上stage-2,babel就會通過那些處於草案階段的新屬性的插件將我們代碼中的用到的新屬性轉譯成爲es5

此外,低一級的 stage 會包含所有高級 stage 的內容,例如 stage-2 會包含 stage-2, stage-3 的所有內容。

babel-ployfill

Babel 幾乎可以編譯所有時新的 JavaScript 語法,但對於 APIs 來說卻並非如此。比如說:PromiseWeakMapArray.fromObject.assignArray.prototype.includesgenerator等。爲了解決這個問題,我們使用一種叫做 Polyfill(代碼填充,也可譯作兼容性補丁) 的技術。 簡單地說,polyfill 即是在當前運行環境中用來複制(意指模擬性的複製,而不是拷貝)尚不存在的原生 api 的代碼。 能讓你提前使用還不可用的 APIs。

引入它很簡單,我們npm i -S @babel/polyfill

  • 在vue中的入口文件main.js文件的最上面:

    import "@babel/polyfill";
  • 或者在webpack入口裏引入:

      module.exports = {
        entry: ["@babel/polyfill", "./main.js"],
      };

兩者任選其一

上面這兩種方式是將整個polyfill都引入了,很多代碼其實對我們是沒有用的,比如,我們的env配置的是不需要兼容ie9以下的瀏覽器,這種引入方式把所有兼容ie的代碼都引入了,包含ie8以下,所以,一般我們會在.babelrc文件裏的env裏配置下useBuiltIns參數爲true,這樣babel在引入的時候會根據我們env環境去加載相應的polyfill:詳細

// .babelrc
{
  ["env", {
    "modules": false,
    "targets": {
      "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
    },
    // 是否自動引入polyfill,開啓此選項必須保證已經安裝了babel-polyfill
    // 在這裏設置自動引入後,babel會根據你配置的兼容的環境,去按需加載polyfill的代碼,這樣能保證代碼量最少
    // 參數:Boolean,默認爲false.
    "useBuiltIns": true
  }]
}

@babel/plugin-transform-runtime

我們看到上面的配置中有個transform-runtime,這個是配置@babel/plugin-transform-runtime,它是做什麼的呢?官網說:一個插件,通過重複使用babel注入的助手(helper)代碼,來減少代碼體積,我們看看它是如何工作的。

npm i -D @babel/plugin-transform-runtime

// .babelrc
{
  "plugins": [
    "@babel/plugin-transform-runtime",
    // 默認配置
      {
        "absoluteRuntime": false,
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
  ]
}

比如這個es6的class類:

class Person {
}

在沒有使用transform-runtime時,每個使用class函數處,Babel 會生成class的helper函數放置在文件頂部,就是說多個文件使用了class, babel就會在每個文件裏面生成一個相同的helper:

"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function"); } }

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

這樣不必要的重複會使我們的代碼體積非常雍腫,transform-runtime就是來解決這個重複生成helper的問題的,它會將這個es6的class語法的轉譯函數放在babel-runtime/helpers/classCallCheck裏,然後在使用處通過require引入,這樣將轉譯函數放在一起,就可以通過webpack將其打包成一個文件,瀏覽器加載一次就行了。

"use strict";

var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var Person = function Person() {
  (0, _classCallCheck3.default)(this, Person);
};

@babel/plugin-transform-runtime && @babel/runtime 對比 babel-polyfill

官方推薦我們將@babel/plugin-transform-runtime@babel/runtime結合使用,有必要嗎?按照官網的原話,這個@babel/runtime就是個以模塊化方式包含函數實現的包,我們從名字上來看,plugin-transform-runtime就是通過插件將我們的新語法轉換polyfill包,而runtime就是個現成的polyfill庫。有點繞了,直接給結果:在我們開發前端項目中,不需要@babel/runtime,它作用在node項目中。

tranform-runtime和babel-polyfill的區別

  • babel-polyfill 是當前環境注入這些 es6+ 標準的墊片,好處是引用一次,不再擔心兼容,而且它就是全局下的包,代碼的任何地方都可以使用。缺點也很明顯,它會污染原生的一些方法,polyfill 把原生的方法重寫了,如果當前項目已經有一個 polyfill 的包了,那你只能保留其一。而且一次性引入這麼一個包,會大大增加體積。如果你只是用幾個特性,就沒必要了,如果你是開發較大的應用,而且會頻繁使用新特性並考慮兼容,那就直接引入吧。
  • transform-runtime 是利用 plugin 自動識別並替換代碼中的新特性,你不需要再引入,只需要裝好 babel-runtime 和 配好 plugin 就可以了。好處是按需替換,檢測到你需要哪個,就引入哪個 polyfill,如果只用了一部分,打包完的文件體積對比 babel-polyfill 會小很多。而且 transform-runtime 不會污染原生的對象,方法,也不會對其他 polyfill 產生影響。所以 transform-runtime 的方式更適合開發工具包,庫,一方面是體積夠小,另一方面是用戶(開發者)不會因爲引用了我們的工具,包而污染了全局的原生方法,產生副作用,還是應該留給用戶自己去選擇。缺點是隨着應用的增大,相同的 polyfill 每個模塊都要做重複的工作(檢測,替換),雖然 polyfill 只是引用,編譯效率不夠高效。另外,instance 上新添加的一些方法,babel-plugin-transform-runtime 是沒有做處理的,比如 數組的 includes, filter, fill 等,這個算是一個關鍵問題吧,直接推薦用 polyfill

幾個常用的babel插件

babel-cil

Babel 的 CLI 是一種在命令行下使用 Babel 編譯文件的簡單方法。有時候我們只是寫一個插件,需要用babel轉一下我們代碼中的高階語法,因爲項目可能不太大,用不到構建工具,就可以用babel-cil。轉換依據我們的.babelrc文件或者package.json中babel選項

  • 編譯一個文件

    babel my-file.js
  • 如果我們想要把一個目錄整個編譯成一個新的目錄,可以使用 --out-dir 或者 -d。.

    $ babel src --out-dir lib
    # 或
    $ babel src -d lib

babel-loader

babel-loader是什麼呢?前面說了,我們可以通過babel-cil在命令行裏告訴babel轉譯哪些js,也可以通過babel-register,在代碼裏通過require來轉,但是,現在前端開發是一個工程化過程,依賴關係比較複雜,在一個稍微大點兒的項目中還真沒法手動告訴babel要處理哪些文件,比如一個.vue文件,裏面還包含html、css,還有一些不是js的鬼語法,這時候就要藉助其它插件先提前處理下,所以,webpack根據依賴關係,加載文件的時候遇到js文件後,會將文件內容的js字符串根據loader配置的先後順序,挨個兒傳遞給它們處理,babel-loader就是其中之一

總結

什麼是babel

babel就是將目標環境(瀏覽器)通過打補丁升級成支持最新javascript語法的環境。

用vue腳手架生成的項目,js怎麼兼容到ie9

// .babelrc
{
    "presets": [
      ["env", {
        // 這裏默認是false,不用再寫一遍
-        // "modules": false,
        // 一般不單獨寫出來,babel/preset-env會自個讀取package裏面的browserslist,與css兼容環境保持一致
        // https://github.com/browserslist/browserslist
-      //  "targets": {
-        //  "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
-      //  },
+        "useBuiltIns": true
      }],
      "stage-2"
    ],
    "plugins": ["transform-vue-jsx", "transform-runtime"]
  }

// webpack.base.conf.js
module.exports = {
  entry: ["@babel/polyfill", "./main.js"],
};

插件快照

名稱 作用 備註
babel/cli 允許命令行使用 babel 命令轉譯文件 一般在寫插件時使用
babel/polyfill 爲所有 API 增加兼容方法 需要在所有代碼之前 require,且體積比較大
babel/plugin-transform-runtime 把幫助類方法從每次使用前定義改爲統一 require,精簡代碼 ---
babel/runtime helper庫 需要安裝爲依賴,而不是開發依賴,node環境使用,web環境不需要
babel/loader babel插件在webpack項目中的一個入口 ---
babel/core babel的polyfill庫 ---
babel/preset-env babel預製環境的集合插件,通過配置目標環境,轉換標準上的新特性 只轉新特性,不轉api
babel/preset-stage-2 轉換草案stage-2以及stage-3階段的的新屬性 ---

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章