從零開始配置Babel

babel對我來講其實一直都是黑盒,我只知道用它能夠轉換js代碼以兼容低版本的瀏覽器,可是具體是怎麼配置的,從未深刻了解過,此次我就記錄一下babel的配置過程git

PS: 因爲babel 7.4版本較以前變化很大,這裏咱們就以最新版爲準github

基礎配置

這裏咱們新建一個項目,從零開始配置babelweb

mkdir babel-test
cd babel-test
npm init -y
npm install --save-dev @babel/core @babel/cli
複製代碼

其中@babel/core是babel的核心包,這裏必裝,@babel/cli是babel提供的命令行工具,能夠在終端中直接使用或配合npm scripts使用,用來生成轉換以後的js文件chrome

下面咱們新建src目錄,並添加index.js文件用來測試代碼轉換的效果:npm

mkdir src
cd src
touch index.js
複製代碼

index.js中寫入如下代碼:json

const message = "hello world"

const say = (message) => {
  console.log(message)
}

say(message)
複製代碼

這裏咱們直接運行是能夠的,可是若是在低版本瀏覽器,好比IE11中,那麼就會報錯,由於constarrow functionES2015/ES6標準,IE11並不支持,因此咱們須要用babel來進行轉換promise

這裏咱們用npm script配置轉換腳本,打開package.json,修改以下:瀏覽器

...
"scripts": {
  "build": "babel src --out-dir lib"
},
...
複製代碼

其中out-dir用來指定轉換以後的文件輸出位置,這裏咱們將轉換後的文件都放在lib目錄下bash

除了上面的轉換指令,咱們還須要一個配置文件,在項目根目錄新建.babelrc,修改以下:babel

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": 11
        }
      }
    ]
  ],
  "plugins": []
}
複製代碼

這裏咱們使用了@babel/preset-env,它是babel官方提供的一個智能預設包,其中包含了不少經常使用的插件並提供了一些配置項,這裏咱們使用targets將代碼最終要運行的瀏覽器指定爲了IE11,那麼轉換後的代碼就能夠在IE11上運行。

PS: @babel/preset-env這個包的更多配置項能夠參考官方文檔:babeljs.io/docs/en/bab…

這裏預設包須要手動安裝

npm install --save-dev @babel/preset-env
複製代碼

而後咱們執行轉換腳本

npm run build
複製代碼

轉換成功後,咱們會在lib目錄下看到index.js文件,其中代碼以下:

"use strict";

var message = "hello world";

var say = function say(message) {
  console.log(message);
};

say(message);
複製代碼

可見constarrow function語法都轉換成了ES5語法,能夠在IE11中運行

注意

若是咱們將targets改成chrome,並將版本指定爲80,轉換後的代碼仍是跟原來同樣的,由於chrome 80是支持ES6語法的

"targets": {
 "chrome": 80
}
複製代碼

targets是能夠同時指定多個瀏覽器的,可是最終轉換的代碼仍是以最低版本爲準,好比配置以下:

"targets": {
 "ie": 11,
 "chrome": 80
}
複製代碼

代碼會轉換爲ES5語法

Polyfill

在介紹polyfill以前,咱們將index.js中的代碼修改爲這樣:

const message = "hello world";

const say = message => {
  console.log(message);
};

function delay(time = 1000) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(new Date().getSeconds());
      resolve();
    }, time);
  });
}

console.log(new Date().getSeconds());
delay(3000).then(() => {
  say(message);
});
複製代碼

這裏咱們用ES6標準中才存在的Promise語法寫了一個delay函數,hello world將在3s後輸出

咱們執行npm run build後,轉換後的代碼爲:

"use strict";

var message = "hello world";

var say = function say(message) {
  console.log(message);
};

function delay() {
  var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000;
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log(new Date().getSeconds());
      resolve();
    }, time);
  });
}

console.log(new Date().getSeconds());
delay(3000).then(function () {
  say(message);
});
複製代碼

這裏咱們能夠看到constarrow function語法都正常轉換爲了ES5標準,可是Promise仍是和以前同樣,若是在IE11中運行會提示Promise is not defined

這是由於babel是能夠直接轉換基礎語法的也就是syntax, 可是ES6+標準下的新特性也就是features,babel是不能直接轉換的,須要藉助polyfill來實現,polyfill翻譯成中文就是墊片的意思,用來墊平不一樣瀏覽器環境以前差別

syntax

syntax是指一些基礎語法,babel是能夠直接轉換的,好比:

  • let
  • const
  • arrow function
  • class
  • template string
  • destruct
  • 等等

features

features是指ES6+ 標準推出的一些新特性,babel不能直接轉換,好比:

  • Promise
  • Set
  • Map
  • Array.prototype.includes
  • Object.assign
  • 等等

@babel/babel-polyfill

@babel/babel-polyfill這個包就能夠實現上面的features,從而完整的模擬ES2015+環境。可是在babel 7.4版本中已經明確表示不推薦使用了,官方建議咱們使用core-js來替代,其實babel-polyfill內部就是用core-jsregenerator-runtime/runtime來實現的。

以前咱們應該是直接在入口文件頂部這樣使用

import "@babel/polyfill"
複製代碼

如今能夠直接改爲這樣

import "core-js/stable";
import "regenerator-runtime/runtime";
複製代碼

core-js 和 regenerator-runtime

core-js是JavaScript的模塊化標準庫,它包含了ECMAScript全部標準的polyfill實現

regenerator-runtime主要是爲了生成器函數提供運行時

.babelrc配置

下面咱們就來實現features的轉換,首先咱們須要安裝如下包

npm install --save core-js regenerator-runtime
複製代碼

修改.babelrc以下

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "corejs": {
          "version": 3
        },
        "targets": {
          "ie": 11
        }
      }
    ]
  ],
  "plugins": []
}
複製代碼

其中useBuildIns是用來決定如何使用polyfill:

  • false 默認,不作任何polyfill處理
  • entry 須要咱們在入口文件手動引入polyfill包,babel會自動剔除掉targets中已經原生支持的polyfill,剩餘的不管有沒有用到,會所有引入進來
  • usage 不須要咱們手動引入polyfill包,但仍是要安裝,babel會按需加載須要的polyfill,可是對第三方依賴包無效,經常使用來在開發第三方庫時使用

這裏咱們先使用entry,除此以外咱們還指定了corejs的版本,而後咱們在文件頂部手動引入polyfill也就是core-js:

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

const message = "hello world";
...
複製代碼

而後執行npm run build,會發現轉換後的代碼裏面引入了全部polyfill,包括咱們須要的Promise

...
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.object.values");
require("core-js/modules/es.promise");
...
複製代碼

這樣,咱們的代碼就能夠在低版本瀏覽器中使用了。

@babel/runtime 和 @babel/plugin-transform-runtime

這兩個包是一塊兒使用的,主要是爲了解決轉換以後代碼重複使用而形成的包體積較大的問題,由於babel在轉換代碼時會使用一些helpers輔助函數,好比下面的代碼:

async function delay() {
  console.log(new Date().getSeconds());
  await new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, 3000);
  });
  console.log(new Date().getSeconds());
}

delay();
複製代碼

轉換以後,咱們會發現生成的代碼除了一些polyfill和實際的代碼以外,還有一些helpers代碼:

...
function asyncGeneratorStep(gen, resolve, r...
function _asyncToGenerator(fn) { retu...
...
複製代碼

若是有不少文件須要轉換,那這些代碼可能就會重複,爲了解決這個問題,咱們可使用plugin-transform-runtime將這些helpers輔助函數的使用方式改成引用的方式,讓它們都去引用runtime包裏的代碼,這樣他們就是重複引用同一個代碼,就不會出現重複的問題了。其中babel-runtime這個包裏面就包含了全部的helpers輔助函數。

咱們須要手動安裝:

npm install --save @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
複製代碼

.babelrc修改以下:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "corejs": {
          "version": 3
        },
        "targets": {
          "ie": 11
        }
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}
複製代碼

這樣轉換以後,那些helpers代碼就變成了require引入的方式:

...
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
...
複製代碼

Polyfill 按需加載

上面咱們提到了咱們可使用useBuiltInsusage選項來按需加載polyfill,這種主要是在咱們開發第三方庫時使用,這裏修改index.js中的代碼以下:

new Promise(resolve => {
  resolve();
});
const arr = [1, 2, 3];
console.log(arr.includes(3));
複製代碼

其中.babelrc改爲以下配置:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": {
          "version": 3
        },
        "targets": {
          "ie": 11
        }
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}
複製代碼

轉換以後生成的文件就是這樣:

"use strict";

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

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

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

new Promise(function (resolve) {
  resolve();
});
var arr = [1, 2, 3];
console.log(arr.includes(3));
複製代碼

這裏咱們能夠看到,咱們只引用了部分polyfill,可是這裏又一個問題,那就是polyfill是注入到全局做用域中的,使用咱們庫的開發者不必定願意污染全局做用域,因此說,合理的解決方案應該是注入到當前做用域中,不影響全局做用域,咱們修改配置以下:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": {
          "version": 3
        },
        "targets": {
          "ie": 11
        }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": {
          "version": 3
        }
      }
    ]
  ]
}
複製代碼

轉換以後,代碼以下:

"use strict";

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

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

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

new _promise.default(function (resolve) {
  resolve();
});
var arr = [1, 2, 3];
console.log((0, _includes.default)(arr).call(arr, 3));
複製代碼

這樣咱們就完美解決了做用域污染的問題

PS: @babel/plugin-transform-runtime這個包是不能讀取@babel/preset-env包的targets選項的配置的,若是咱們配置了這個包的corejs選項,它會把咱們代碼中全部用到的Features都轉化爲對corejs提供的polyfill的引用,好比咱們把上面代碼中的targets改成chrome 80,轉換以後的代碼仍是會包含promise這個polyfill,關於這個問題我發了issues: github.com/babel/websi…,官方建議是:這個包的corejs選項主要是爲了開發第三方庫時使用,由於開發者沒法控制庫的瀏覽器運行環境。

Proposals

在實際開發中,除了使用ECMAScript標準中已存在的語法,咱們還可使用一些在提案中,可是尚未正式發佈的語法,好比String.prototype.replaceAll

index.js代碼以下:

const queryString = "q=query+string+parameters";
const withSpaces = queryString.replaceAll("+", " ");
console.log(withSpaces);
複製代碼

轉換以後的代碼:

"use strict";

var queryString = "q=query+string+parameters";
var withSpaces = queryString.replaceAll("+", " ");
console.log(withSpaces);
複製代碼

這裏咱們發現語法並無轉換,這裏咱們就須要配置proposals以轉換這些還在提案中的語法:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": {
          "version": 3,
          "proposals": true
        },
        "targets": {
          "ie": 11
        }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": {
          "version": 3,
          "proposals": true
        }
      }
    ]
  ]
}
複製代碼

轉換以後:

"use strict";

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

var _replaceAll = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/replace-all"));

var queryString = "q=query+string+parameters";
var withSpaces = (0, _replaceAll.default)(queryString).call(queryString, "+", " ");
console.log(withSpaces);
複製代碼

這樣咱們就能夠愉快的進行開發了

參考連接

相關文章
相關標籤/搜索