Babel 7使用總結

Babel 7使用總結

​ 2019-07-08javascript

本文基於Babel 7.4.5。java

圖片描述
​ Babel主要模塊如上圖所示,接下來將分別介紹。node

1. @babel/core

@babel/core主要是進行代碼轉換的一些方法,能夠將源代碼根據配置轉換成兼容目標環境的代碼。react

import * as babel from "@babel/core";
babel.transform("code();", options, function(err, result) {
  result.code;
  result.map;
  result.ast;
});

2. @babel/cli

@babel/cli是 babel 提供的命令行工具,用於命令行下編譯源代碼。git

首先安裝依賴:es6

npm install --save-dev @babel/core @babel/cli

新建一個js文件:github

let array = [1,2,3,4,5,6];
array.includes(function(item){
    return item>2;
})
class Robot {
    constructor (msg) {
        this.message = msg
    }
    say () {
        alertMe(this.message)
    }
}
Object.assign({},{
    a:1,b:2
})
const fn = () => 1;
new Promise();

執行轉換:typescript

npx babel index.js --out-file out.js

能夠發現輸出代碼沒有變化,這是由於沒有進行配置來肯定怎麼進行轉換。shell

3. @babel/plugin*

babel是經過插件來進行代碼轉換的,例如箭頭函數使用plugin-transform-arrow-functions插件來進行轉換。npm

首先安裝該插件:

npm install --save-dev @babel/plugin-transform-arrow-functions

能夠經過@babel/cli傳參或者配置文件的方式使用插件:

  • @babel/cli

    npx babel index.js --out-file out.js --plugins=@babel/plugin-transform-arrow-functions

    則能夠獲得out.js文件,能夠看到箭頭函數已經被轉換。

    let array = [1, 2, 3, 4, 5, 6];
    array.includes(function (item) {
      return item > 2;
    });
    class Robot {
        constructor (msg) {
            this.message = msg
        }
        say () {
            alertMe(this.message)
        }
    }
    Object.assign({}, {
      a: 1,
      b: 2
    });
    const fn = function () {
      return 1;
    };
    
    new Promise();
  • 配置文件babel.config.js(javascript寫法)或.babelrc(json寫法),使用配置文件是更加經常使用的方式。

    module.exports = function (api) {
        api.cache(true);
    
        const plugins = [ "@babel/plugin-transform-arrow-functions" ];
    
        return {
            plugins
        };
    }

4. @babel/presets

咱們在index.js中使用了多種es6的語法,一個個的導入插件很麻煩,presets是一組預設好的插件集合。官方爲常見環境組裝了一些 presets (固然也能夠本身配置):

咱們使用@babel/preset-env爲例(使用前需npm install @babel/preset-env):

module.exports = function (api) {
    api.cache(true);
    const presets =  [
        ["@babel/preset-env"]
    ];
    return {
        presets
    };
}

獲得的結果以下, 能夠看到箭頭函數被編譯、es6類、let聲明被編譯了。

"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {
  return item > 2;
});

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    _classCallCheck(this, Robot);

    this.message = msg;
  }

  _createClass(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);

  return Robot;
}();

Object.assign({}, {
  a: 1,
  b: 2
});

var fn = function fn() {
  return 1;
};

new Promise();

可是能夠看到數組的實例方法includes、對象的靜態方法,以及promise並無被編譯。

這是由於babel 把 Javascript 語法爲syntax 和 api, api 指那些咱們能夠經過 函數從新覆蓋的語法 ,相似 includes, map, includes, Promise, 凡是咱們能想到重寫的均可以歸屬到api。syntax 指像箭頭函數,let,const,class, 依賴注入 Decorators等等這些,咱們在 Javascript在運行是沒法重寫的,想象下,在不支持的瀏覽器裏無論怎麼樣,你都用不了 let 這個關鍵字。

@babel/presets默認只對syntax進行轉換,咱們須要使用@babel/polyfill來提供對api的的支持。

5. @babel/polyfill

@babel/polyfill由core-js2和regenerator-runtime組成,後者是facebook開源庫,用來實現對generator、async函數等的支持,前者是js標準庫,包含不一樣版本javascipt語法的實現。

只要在js文件的入口頂部引入@babel/polyfill就能夠在後問的代碼中自由的使用es6 api了。

可是總體@babel/polyfill整個包體積較大,咱們一般只使用了其中一部分方法,而引入整個庫顯然是不合適的。因此你能夠只引入使用的方法:

import 'core-js/features/array/from'; // <- at the top of your entry point
import 'core-js/features/array/flat'; // <- at the top of your entry point
import 'core-js/features/set';        // <- at the top of your entry point
import 'core-js/features/promise';    // <- at the top of your entry point

Array.from(new Set([1, 2, 3, 2, 1]));          // => [1, 2, 3]
[1, [2, 3], [4, [5]]].flat(2);                 // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32

若是你不想污染全局命名空間(例如在寫一個npm庫時,要保持其隔離性)。能夠引入純淨版:

import from from 'core-js-pure/features/array/from';
import flat from 'core-js-pure/features/array/flat';
import Set from 'core-js-pure/features/set';
import Promise from 'core-js-pure/features/promise';

from(new Set([1, 2, 3, 2, 1]));                // => [1, 2, 3]
flat([1, [2, 3], [4, [5]]], 2);                // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32

preset-env的配置項中的useBuiltIns屬性能夠方便@babel/polyfill的使用。

  • useBuiltIns:false(default):此時不對 polyfill 作操做。若是引入 @babel/polyfill,則無視配置的瀏覽器兼容,引入全部的 polyfill
  • useBuiltIns:"entry":根據配置的瀏覽器兼容,引入瀏覽器不兼容的 polyfill。須要在入口文件手動添加 import '@babel/polyfill',會自動根據 browserslist 替換成瀏覽器不兼容的全部 polyfill
  • useBuiltIns:"usage":不須要在文件頂部手動引入@babel/polyfill,會根據代碼中的使用進行按需添加。

在這裏使用useBuiltIns:"usage"做爲示例,babel.config.js文件以下:

module.exports = function (api) {
    api.cache(true);

    const presets =  [
        ["@babel/preset-env",
            {
            "useBuiltIns": "usage",
            "targets":{
                "browsers":["> 1%", "last 2 versions", "not ie                                                             <= 8"]
                }
            }
        ]
    ];
    return {
        presets,
        // plugins
    };
}

獲得的編譯結果:

"use strict";

require("core-js/modules/es6.promise");

require("core-js/modules/es6.object.to-string");

require("core-js/modules/es6.object.assign");

require("core-js/modules/es7.array.includes");

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {
  return item > 2;
});

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    _classCallCheck(this, Robot);

    this.message = msg;
  }

  _createClass(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);

  return Robot;
}();

Object.assign({}, {
  a: 1,
  b: 2
});

var fn = function fn() {
  return 1;
};

new Promise();

能夠看到實現了polyfill的按需引入。可是在配置文件中未指定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.

這是由於core-js3已經發布,@babel/polyfill不支持從core-js2到core-js3的平滑過渡,因此在babel 7.4版本中,已經廢棄@babel/polyfill(只能用core-js2),而是直接引入core-js3和regenerator-runtime代替。

import "@babel/polyfill";

// migration

import "core-js/stable";
import "regenerator-runtime/runtime";

使用core-js3有不少優勢,首先就是新,包含不少新特性,其次就是能夠配合@babel/runtime(後文詳述)。更多優勢見core-js@3, babel and a look into the future

使用core-js3是 babel.config.js以下:

module.exports = function (api) {
    api.cache(true);

    const presets =  [
        ["@babel/preset-env",
            {
            "useBuiltIns": "usage",
            "corejs":3,
            "targets":{
                "browsers":["> 1%", "last 2 versions", "not ie <= 8"]
                }
            }
        ]
    ];
    return {
        presets,
        // plugins
    };
}

仔細觀察上面的編譯結果能夠發現有兩個問題。

  • 高階語法向低階語法轉化時引入了了不少helper函數(如_classCallCheck)。當文件數量不少時,每一個文件都引入這些helper函數會使得文件體積增大,怎麼這些helper函數抽離到單獨的模塊,而後按需引入呢?
  • 雖然polyfill是按需引入的,可是會污染全局命名空間,當你寫的是公共庫時,可能會與使用者本地的方法產生衝突。例如你在你的庫中引入了polyfill中的Promise,使用者自身定義了本身的Promise,這就容易產生衝突。如何將你的公共庫中引入的polyfill api隔離起來呢?

要解決這兩個問題,就要須要使用@babel/runtime和@babel/plugin-transform-runtime了。

6. @babel/runtime

@babel/runtime依賴@babel/helpers和regenerator-runtime,helper函數均可以從這裏面引入,手動的確定不可能,因而 babel 提供了 @babel/plugin-transform-runtime 來替咱們作這些轉換。

babel.config.js文件爲:

module.exports = function (api) {
    api.cache(true);

    const presets =  [
        ["@babel/preset-env",
            {
            "useBuiltIns": "usage",
            "targets":{
                "browsers":["> 1%", "last 2 versions", "not ie <= 8"]
                }
            }
        ]
    ];
    const plugins = [
        ["@babel/plugin-transform-runtime"]
    ]

    return {
        presets,
        plugins
    };
}

獲得的編譯結果是:

"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
require("core-js/modules/es6.object.assign");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
require("core-js/modules/es7.array.includes");

var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {
  return item > 2;
});
var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    (0, _classCallCheck2.default)(this, Robot);
    this.message = msg;
  }

  (0, _createClass2.default)(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);
  return Robot;
}();
Object.assign({}, {
  a: 1,
  b: 2
});
var fn = function fn() {
  return 1;
};
new Promise();

能夠看到咱們的第一個問題已經圓滿解決了。

解決第二個問題須要使用@babel/plugin-transform-runtime option中的corejs參數。默認爲false,不對polyfill進行處理。能夠設爲不一樣版本的core-js。

例如使用core-js2時,須要先安裝

npm install --save @babel/runtime-corejs2

配置文件爲:

module.exports = function (api) {
    api.cache(true);

    const presets =  [
        ["@babel/preset-env",
            {
            "useBuiltIns": "usage",
            "targets":{
                "browsers":["> 1%", "last 2 versions", "not ie <= 8"]
                }
            }
        ]
    ];
    const plugins = [
        ["@babel/plugin-transform-runtime",{corejs:2}]
    ]

    return {
        presets,
        plugins
    };
}

獲得的結果是:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));

var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign"));

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

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));

require("core-js/modules/es7.array.includes");

var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {
  return item > 2;
});

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    (0, _classCallCheck2.default)(this, Robot);
    this.message = msg;
  }

  (0, _createClass2.default)(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);
  return Robot;
}();

(0, _assign.default)({}, {
  a: 1,
  b: 2
});

var fn = function fn() {
  return 1;
};

new _promise.default();

能夠看到polyfill引入時獲得了一個別名,能夠避免全局變量污染,可是能夠發現實例方法includes並無獲得相應的處理。這是core-js2沒有解決的問題,隨着2019年3月core-js3的發佈,這個問題獲得了完美解決。咱們將corejs設爲3,獲得告終果以下:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign"));

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

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var array = [1, 2, 3, 4, 5, 6];
(0, _includes.default)(array).call(array, function (item) {
  return item > 2;
});

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    (0, _classCallCheck2.default)(this, Robot);
    this.message = msg;
  }

  (0, _createClass2.default)(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);
  return Robot;
}();

(0, _assign.default)({}, {
  a: 1,
  b: 2
});

var fn = function fn() {
  return 1;
};

new _promise.default();

7. @babel/register

通過 babel 的編譯後,咱們的源代碼與運行在生產下的代碼是不同的。

babel-register 則提供了動態編譯。換句話說,咱們的源代碼可以真正運行在生產環境下,不須要 babel 編譯這一環節。

咱們先在項目下安裝 babel-register:

$ npm install --save-dev @babel/register

而後在入口文件中 require

require('@babel/register')
require('./app')

在入口文件頭部引入 @babel/register 後,咱們的 app 文件中便可使用任意 es2015 的特性。

固然,壞處是動態編譯,致使程序在速度、性能上有所損耗。(咱們在啓動測試腳本的時候可使用)

7. @babel/node

咱們上面說,babel-register 提供動態編譯,可以讓咱們的源代碼真正運行在生產環境下 - 但其實否則,咱們仍須要作部分調整,好比新增一個入口文件,並在該文件中 require('@babel/register')。而 babel-node 能真正作到一行源代碼都不須要調整:

$ npm install --save-dev @babel/core @babel/node
$ npx babel-node app.js

只是,請不要在生產環境中使用 babel-node,由於它是動態編譯源代碼,應用啓動速度很是慢

參考

http://babel.docschina.org/docs/en/babel-plugin-transform-runtime#technical-details

https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md

https://blog.hhking.cn/2019/0...

https://segmentfault.com/a/11...

https://zhuanlan.zhihu.com/p/...

https://blog.zfanw.com/babel-...

https://www.thebasement.be/up...

相關文章
相關標籤/搜索