babel之配置檔案.babelrc入門詳解

wqzwh發表於2018-02-10

什麼是Babel

官方解釋,是下一代JavaScript 語法的編譯器。

既然是下一代Javascript的標準,瀏覽器因版本的不同對此會有相容性問題,JavaScript的新的方法都不能使用,但是目前我們在專案開發一直提倡使用最新的語法糖編寫,不但能減少程式碼量,而且async,await等新特性還解決了回撥的編寫機制,減輕了程式碼維護成本。

Babel就因此而生,它可以讓你放心使用大部分的JavaScript的新的標準的方法,然後編譯成相容絕大多數的主流瀏覽器的程式碼。在專案工程腳手架中,一般會使用.babelrc檔案,通過配置一些引數配合webpack進行打包壓縮。也通過網上了解,寫法各有不同,引數也大不相同,因此,我重新整理一份資料,詳細的介紹下各個配置項的意義所在,以便清晰瞭解如果使用。

以下配置主要正對webpack3+寫法。

Babel轉譯器

在.babelrc配置檔案中,主要是對預設(presets)和外掛(plugins)進行配置,因此不同的轉譯器作用不同的配置項,大致可分為以下三項:

1.語法轉義器。主要對javascript最新的語法糖進行編譯,並不負責轉譯javascript新增的api和全域性物件。例如let/const就可以被編譯,而includes/Object.assign等並不能被編譯。常用到的轉譯器包有,babel-preset-env、babel-preset-es2015、babel-preset-es2016、babel-preset-es2017、babel-preset-latest等。在實際開發中可以只選用babel-preset-env來代替餘下的,但是還需要配上javascirpt的製作規範一起使用,同時也是官方推薦

{
  "presets": ["env", {
      "modules": false
    }],
    "stage-2"
}
複製程式碼

2.補丁轉義器。主要負責轉譯javascript新增的api和全域性物件,例如babel-plugin-transform-runtime這個外掛能夠編譯Object.assign,同時也可以引入babel-polyfill進一步對includes這類用法保證在瀏覽器的相容性。Object.assign 會被編譯成以下程式碼:

__WEBPACK_IMPORTED_MODULE_1_babel_runtime_core_js_object_assign___default()
複製程式碼

3.jsx和flow外掛,這類轉譯器用來轉譯JSX語法和移除型別宣告的,使用Rect的時候你將用到它,轉譯器名稱為babel-preset-react

建立預設(presets)

主要通過npm安裝babel-preset-xx外掛來配合使用,例如通過 npm install babel-preset-stage-2 babel-preset-env --save-dev 安裝,會有相應如下配置。

{
  "presets": [
    ["env", options],
    "stage-2"
  ]
}
複製程式碼
stage-2配置

babel主要提供以下幾種轉義器包,括號裡面是對應配置檔案的配置項

babel-preset-stage-0(stage-0) 
babel-preset-stage-1(stage-1) 
babel-preset-stage-2(stage-2) 
babel-preset-stage-3(stage-3)
複製程式碼

不同階段的轉譯器之間是包含的關係,preset-stage-0轉譯器除了包含了preset-stage-1的所有功能還增加了transform-do-expressions外掛和transform-function-bind外掛,同樣preset-stage-1轉譯器除了包含preset-stage-2的全部功能外還增加了一些額外的功能。

options配置介紹

官方推薦使用babel-preset-env來替代一些外掛包的安裝(es2015-arrow-functions,es2015-block-scoped-functions等等),並且有如下幾種配置資訊,介紹幾個常用的,

更多配置可以參考官網https://babeljs.io/docs/plugins/preset-env/

{
    "targets": {
        "chrome": 52,
        "browsers": ["last 2 versions", "safari 7"],
        "node":"6.10"
    }
    "modules": false
}
複製程式碼

targets可以制定相容瀏覽器版本,如果設定了browsers,那麼就會覆蓋targets原本對瀏覽器的限制配置。

targets.node正對node版本進行編譯

modules通常都會設定為false,因為預設都是支援CommonJS規範,同時還有其他配置引數:"amd" | "umd" | "systemjs" | "commonjs",systemjs我還不知道規範寫法是什麼,amd和umd以及commonjs相對比較熟悉,下面簡要列舉下書寫規範。

amd程式碼規範,在ng1中會用到比較多,主要用於依賴注入:

define(['jquery'], function ($) {
    //    方法
    function myFunc(){};
 
    //    暴露公共方法
    return myFunc;
})
複製程式碼

commonjs規範,也是node環境中尊崇的一種規範:

var $ = require('jquery');
//    方法
function myFunc(){};
 
//    暴露公共方法(一個)
module.exports = myFunc;
{% endcodeblock %}
>umd規範,相容amd以及commonjs規範,目前在第三方外掛編寫使用比較多:
{% codeblock lang:javascript %}
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS之類的
        module.exports = factory(require('jquery'));
    } else {
        // 瀏覽器全域性變數(root 即 window)
        root.returnExports = factory(root.jQuery);
    }
}(this, function ($) {
    //    方法
    function myFunc(){};
 
    //    暴露公共方法
    return myFunc;
}));
複製程式碼

外掛(plugins)

外掛配置項同預設配置項一樣,需要搭配babel相應的外掛進行配置,可以選擇配置外掛來滿足單個需求,例如早期我們會有如下配置:

{
  "plugins": [
    "check-es2015-constants",
    "es2015-arrow-functions",
    "es2015-block-scoped-functions",
    // ...
  ]
}
複製程式碼

但是這些外掛從維護到書寫極為麻煩,後來官方統一推薦使用env,全部替代了這些單一的外掛功能,可以簡化配置如下,也就是我前面提到了babel-preset-env:

{
  "presets": [
    "es2015"
  ]
}
複製程式碼

這裡主要介紹兩款常用外掛,分別是babel-plugin-transform-runtime,babel-plugin-syntax-dynamic-import。

基本配置程式碼如下:

{
  "plugins": [
    "syntax-dynamic-import",["transform-runtime"]
  ]
}
複製程式碼

transform-runtime

為了解決這種全域性物件或者全域性物件方法編譯不足的情況,才出現了transform-runtime這個外掛,但是它只會對es6的語法進行轉換,而不會對新api進行轉換。如果需要轉換新api,也可以通過使用babel-polyfill來規避相容性問題。

對Object.assign進行編譯,配置與未配置經過webpack編譯後的程式碼片段如下:

// 未設定程式碼片段:
__webpack_require__("ez/6");
var aaa = 1;

function fna() {
  var dd = 33333;
  var cc = Object.assign({ key: 2 });
  var xx = String.prototype.repeat.call('b', 3);
  if ("foobar".String.prototype.includes("foo")) {
    var vv = 1;
  }

  return dd;
}
複製程式碼
// 設定程式碼片段:
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_babel_runtime_core_js_object_assign___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_babel_runtime_core_js_object_assign__);

__webpack_require__("ez/6");
var aaa = 1;

function fna() {
  var dd = 33333;
  var cc = __WEBPACK_IMPORTED_MODULE_1_babel_runtime_core_js_object_assign___default()({ key: 2 });
  var xx = String.prototype.repeat.call('b', 3);
  if ("foobar".String.prototype.includes("foo")) {
    var vv = 1;
  }

  return dd;
}
複製程式碼

對class定義類會進行編譯,配置與未配置經過webpack編譯後的程式碼片段如下:

// 未設定程式碼片段:
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Canvas = function Canvas(height, width) {
  _classCallCheck(this, Canvas);

  this.height = height;
  this.width = width;
};

var Canvas2 = function Canvas2(height, width) {
  _classCallCheck(this, Canvas2);

  this.height = height;
  this.width = width;
};
複製程式碼
// 設定程式碼片段:
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck__);

var Canvas = function Canvas(height, width) {
  __WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck___default()(this, Canvas);

  this.height = height;
  this.width = width;
};

var Canvas2 = function Canvas2(height, width) {
  __WEBPACK_IMPORTED_MODULE_0_babel_runtime_helpers_classCallCheck___default()(this, Canvas2);

  this.height = height;
  this.width = width;
};
複製程式碼

對Generator函式也有同上的編譯效果,目前專案中使用該函式較小,一般使用promise替代,以及async await所以未對該函式做測試。

官方說對promise也會產生編譯,但是實際測試結果卻沒有效果

經過webpack打包最終測試,引入transform-runtime該配置項後,打包入口js檔案大小會略微增大,並不像官方說的能夠縮小檔案體積

未配置的最終打包效果:

babel之配置檔案.babelrc入門詳解

配置後的最終打包效果:

babel之配置檔案.babelrc入門詳解

雖然檔案大小會有所增大,但是解決一些相容性的問題,同時,從以上給出的測試程式碼例子來看,使用transform-runtime後,可以減少內部全域性函式的定義,從結構上看尊崇了webpack的模組化思想,所以還是建議使用該外掛。

syntax-dynamic-import

這個外掛主要解決動態引入模組的問題

function nDate() {
  import('moment').then(function(moment) {
    console.log(moment.default().format());
  }).catch(function(err) {
    console.log('Failed to load moment', err);
  });
}

nDate();
複製程式碼

如果.babelrc配置項中使用了"stage-2",也可以不實用該外掛,同樣支援動態模組引入。

不然就會報以下錯誤:

  • Module build failed: SyntaxError: 'import' and 'export' may only appear at the top level, or (import 和 export只能在最外層,也就是不能用在函式或者塊中)

  • Module build failed: SyntaxError: Unexpected token, expected {

其他配置項

ignore

主要作用就是可以指定不編譯那些程式碼

{
  "ignore":["./module/a.js"]
}
複製程式碼

let,Object.assign,class定義都未編譯,編譯效果如下:

__webpack_require__("ez/6");
const aaa = 1;

function fna () {
  let dd = 33333
  let cc = Object.assign({key:2})
  let xx = String.prototype.repeat.call('b', 3)
  if ("foobar".String.prototype.includes("foo")) {
    let vv = 1
  }

  return dd
}

function fna2 () {
  return fna() + aaa + __WEBPACK_IMPORTED_MODULE_0__b__["a" /* default */]
}

class Canvas {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Canvas2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
複製程式碼

minified

主要設定編譯後是否是壓縮,boolean型別,如果使用babel-cli進行打包編譯檔案這個配置項能夠起到作用,但是目前大部分還是會依賴第三方打包工具,例如webpack,所以這個配置引數一般不用設定,webpack外掛中的UglifyJsPlugin做了壓縮的工作。

comments

在生成的檔案中,不產生註釋,boolean型別,webpack外掛中的UglifyJsPlugin也同樣整合了這個功能。

env

基本配置如下:

{
  "env": {
    // test 是提前設定的環境變數,如果沒有設定BABEL_ENV則使用NODE_ENV,如果都沒有設定預設就是development
    "test": {
      "presets": ["env", "stage-2"],
      // instanbul是一個用來測試轉碼後程式碼的工具
      "plugins": ["istanbul"]
    }
  }
}
複製程式碼

再談相容性問題

Babel預設只轉換新的JavaScript語法,而不轉換新的API,比如Iterator、Generator、Set、Maps、Promise等等全域性物件,以及一些定義在全域性物件上的方法(比如Object.assign)都不會轉碼,具體的可以參考babel-plugin-transform-runtime模組的definitions.js檔案。

這裡主要涉及到babel編譯後依然會存在瀏覽器相容性問題,一般會使用transform-runtime和babel-polyfill配合使用,對於後者只需要在專案入口檔案require引入即可。

當然在使用類似Object.assign函式功能時,可以使用lodash庫來替代,promise可以使用Q.js替代等等方案,這樣依賴可以不需要引入以上外掛,具體可以根據專案具體安排

總結

.babelrc配置檔案主要還是以presets和plugins組成,通過和webpack配合進行使用,分享下我們在專案中常用的配置。以上都是通過學習總結出來的,有什麼不對的地方希望指出。

vue專案開發使用的配置如下:

{
  "presets": [
    ["env", {
      "modules": false
    }],
    "stage-2"
  ],
  // 下面指的是在生成的檔案中,不產生註釋
  "comments": false,
  "plugins": ["transform-runtime","syntax-dynamic-import"],
  "env": {
    // test 是提前設定的環境變數,如果沒有設定BABEL_ENV則使用NODE_ENV,如果都沒有設定預設就是development
    "test": {
      "presets": ["env", "stage-2"],
      // instanbul是一個用來測試轉碼後程式碼的工具
      "plugins": ["istanbul"]
    }
  }
}
複製程式碼

react專案開發使用的配置如下:

{
  "presets": [
    ["env", { "modules": false }],
    "stage-2",
    "react"
  ],
  "plugins": ["transform-runtime"],
  "comments": false,
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": [ "istanbul" ]
    }
  }
}
複製程式碼

2018-11-18 關於stage-x介紹的補充

stage-3包括以下外掛:

  transform-async-to-generator 支援async/await

  transform-exponentiation-operator 支援冪運算子語法糖

stage-2包括stage-3的所有外掛,額外還包括以下外掛:

  syntax-trailing-function-commas 支援尾逗號函式,額...很雞肋

  transform-object-reset-spread 支援物件的解構賦值

stage-1包括stage-2所有外掛,額外還包括以下外掛:

  transform-class-constructor-call 支援class的建構函式

  transform-class-properties 支援class的static屬性

  transform-decorators 支援es7的裝飾者模式即@,這其實是很有用的特性,對於HOC來說這是一個不錯的語法糖

  transform-export-extensions 支援export方法

stage-0包括stage-1所有外掛,額外還包括以下外掛:

  transform-do-expressions 支援在jsx中書寫if/else

  transform-function-bind 支援::操作符來切換上下文,類似於es5的bind   

2018-12-10 關於babel-plugin-syntax-dynamic-import 使用補充

感謝@丹哥一號同學提出問題,經過測試當使用webpack版本4.20.2及以上的時候(4以上的其他版本暫時沒測),不需要這個外掛,同時也不需要stage-2的配置,同樣可以支援import以及動態import,基本用法根參考以下示例:

function nDate() {
  import('moment').then(function(moment) {
    console.log(moment.default().format());
  }).catch(function(err) {
    console.log('Failed to load moment', err);
  });
}

nDate();
複製程式碼

相關文章