Halo,Babel

本文基於 babel 7 作敘述,若是以前一直使用 babel 6 的同窗能夠先看本文關於 babel 6 升級 babel 7 的相關模塊javascript

Babel 是如何工做的呢?

Babel 的編譯過程能夠分爲三個階段:java

  • 解析(Parsing):將代碼字符串解析成抽象語法樹。
  • 轉換(Transformation):對抽象語法樹進行轉換操做。
  • 生成(Code Generation): 根據變換後的抽象語法樹再生成代碼字符串。

解析:@babel/parser (Babylon)

使用 @babel/core 中的 parse API 來進行詞法分析(分詞+語法分析),轉化成 AST,爲 Transform 準備。
Tip:感興趣的同窗能夠閱讀這篇文章深刻了解下:www.alloyteam.com/2017/04/ana… 本文重在講述 babel 在項目中的實踐呀。node

轉換:使用者能夠決定的過程

使用 @bable/core 中的 transform API 來進行轉義;咱們熟知的插件就是應用在這一過程,若是這一階段不使用任何插件,Babel 將在生成階段輸出原樣的代碼;react

  1. plugins 和 presets 如何配合工做的呢?
  • Preset 是 Babel 官方作的一些插件集,能夠把它理解成一些插件集合預設,減輕繁瑣的配置。每一年每一個 Preset 只會編譯當年批准的內容;
  • plugin/preset 執行順序:對於轉義的節點,babel 先按照配置文件的 plugins 數組配置去執行,執行完,再倒序讀取 presets 配置項裏每個預設,以下圖:

2. 經常使用插件 plugins: 能夠根據項目開發須要去引入對於的插件,在 Bable plugins 查詢文檔 中查詢,對於大部分插件能夠經過升級 @bebel-preset-env 來引入,而對應實驗性的語法,則須要單獨引入對於的 plugin 來支持,在 Babel 各個階段插件集合 中 Experimental 對應的插件列表中去查詢; 例如:

  • @babel/plugin-proposal-class-properties
  • @babel/plugin-proposal-decorators
  • @babel/plugin-proposal-export-default-from
  • babeljs.io/docs/en/bab…
  1. 經常使用預設 presets:
  • @babel-preset-env:根據用戶的自定義的配置項,來將 ES2015+ 轉化爲 ES5,詳細說明能夠看一下@babel-preset-env 配置說明
  • @babel/preset-react:轉義 react 代碼,詳情能夠閱讀:www.babeljs.cn/docs/babel-…
  • @babel-preset-typescript:轉義 typescript 代碼,能夠閱讀該文章來加深對該插件的認識喔:iamturns.com/typescript-… 一般對於 react 項目,presets 使用 @babel/preset-env + @babel/preset-react 便 ok 了,可是隨着 TypeScript 的火熱,babel 7 爲開發者帶來了 @babel/preset-typescript ,用於支持 ts 開發,從下圖能夠看出,這個插件使用趨勢在增加勢頭很猛呀;

  • 配置推薦
// react + typescript 
"presets": [
    [
      "@babel/preset-env",
      {
        "targets": "cover 95%, safari >= 7", # 根據項目實際狀況配置
        "modules": "cjs",
        "useBuiltIns": "usage"
      }
    ],
    "@babel/react",
    "@babel/preset-typescript", # 若是項目使用 typescript 開發須要安裝這個預設
  ]
複製代碼

生成:

用 babel-generator 經過 AST 生成 ES5 代碼; TIP:若是在 babel 的配置中,不使用任何 presets 和 plugins,那麼生成的代碼和原代碼無異;webpack

你須要知道的 @babel-polyfill

背景:

  1. 因爲 babel 自己只負責語法轉換,好比將 ES6+ 語法轉化爲 ES5。可是若是有些對象、方法,瀏覽器自己不支持,好比:
    • 全局對象:Promise、WeakMap 等。
    • 全局靜態函數:Array.from、Object.assign 等。
    • 實例方法:好比 Array.prototype.includes 等。 此時,須要引入 @babel-polyfill 來模擬實現這些對象與方法。
  2. @babel-polyfill 主要包含了core-js和regenerator兩部分。
    • babel-polyfill:提供瞭如 ES五、ES六、ES7 等規範中 中新定義的各類對象、方法的模擬實現。
    • regenerator:提供 generator 支持,若是應用代碼中用到 generator、async 函數的話用到。

不足:

  1. @babel-polyfill 形成代碼代碼體積過大 以往項目中使用 polyfill,常規的作法有兩種:
    • 在入口 js 文件中 import @babel-polyfill
    • 在webpack 的 entry 配置裏寫入 @babel-polyfill
  2. @babel-polyfill 的會污染全局空間和內置原型對象 像Map,Array.prototype.find這些就存在於全局空間中。

@babel-polyfill VS @babel-runtime

@babel-runtime 出現背景

@babel-polyfill 的不足,在平常業務開發中,影響不是很大的,可是若是在通用的第三方庫的開發中引入 @babel-polyfill,就會帶來潛在的問題:好比咱們項目中自定義了一份新的 API 的實現,可是引入的第三方通用庫使用了polyfill,而且實現了一樣的方法,就會將咱們原有的方法覆蓋(至少會有存在這樣的風險),爲了不這樣的問題,babel 推出了 @babel-runtime 來實現 @babel-polyfill 的絕大部分的功能,它將開發者依賴的全局內置 對象等,單獨抽成模塊,經過模塊導入的方式,避免了對全局做用域的污染。 因此,若是是開發庫、工具,能夠考慮使用 @babel-runtime;git

如何使用 @babel-runtime 來實現 polyfill

  1. 安裝 @babel/plugin-transform-runtime 和 @babel-runtime
# babel-plugin-transform-runtime 用於構建過程的代碼轉換
npm install --save-dev babel-plugin-transform-runtime 
npm install --save babel-runtime
複製代碼
  1. .babelrc plugins 中添加 @babel/plugin-transform-runtime
{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
    ]
  ]
複製代碼

@babel-runtime + @babel/plugin-transform-runtime 實現細節

@babel/plugin-transform-runtime 插件主要作了以下三件事情:es6

  1. core-js aliasing:自動導入 @babel-runtime/core-js,將全局靜態方法、全局內置對象映射到對應的模塊;
# js
var sym = Symbol();

var promise = new Promise();

console.log(arr[Symbol.iterator]());
# 轉化後
"use strict";

var _getIterator2 = require("@babel/runtime-corejs2/core-js/get-iterator");

var _getIterator3 = _interopRequireDefault(_getIterator2);

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

var _promise2 = _interopRequireDefault(_promise);

var _symbol = require("@babel/runtime-corejs2/core-js/symbol");

var _symbol2 = _interopRequireDefault(_symbol);

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

var sym = (0, _symbol2.default)();

var promise = new _promise2.default();

console.log((0, _getIterator3.default)(arr));
複製代碼
  1. Helper aliasing:將內聯的工具函數移除,改爲經過 @babel-runtime/helpers 模塊進行導入
# js
class Person() {}

# 轉化後
"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);
};
複製代碼
  1. Regenerator aliasing:用於在項目中使用 async/generator 時,自動導入 @babel-runtime/regenerator 模塊
# js
function* foo() {}

# 轉化後
"use strict";

var _regenerator = require("@babel/runtime/regenerator");

var _regenerator2 = _interopRequireDefault(_regenerator);

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

var _marked = [foo].map(_regenerator2.default.mark);

function foo() {
  return _regenerator2.default.wrap(
    function foo$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked[0],
    this
  );
}
複製代碼

@babel-runtime + @babel/plugin-transform-runtime 缺點

因爲 @babel-runtime 和 @babel/plugin-transform-runtime 實現方式致使它不會對實例方法進行擴展(例如 Array.prototype.includes()),因此在使用中,存在很大的隱患,簡單的說,若是你使用的是 @babel-runtime 實現的polyfill,那麼,就會出現你使用的部分原型方法得不到支持而報錯,這是不能接受的,因此我推薦在項目開發中,仍是使用 @babel-polyfill 比較穩妥。github

@babel/preset-env preset 帶來的 @babel-polyfill 按需引用

TIP: 關於 @babel/preset-env 的詳細內容能夠查看下一小節的內容 經過在 presets 裏使用 @babel/preset-env preset,設置 useBuiltInts 爲 usage 或者或者 entry 來實現按需引入; 與@babel/preset-env一塊兒使用時(注意,仍然須要安裝@ babel-polyfill。)web

  1. 若是在.babelrc 中指定 useBuiltIns:'usage',則不要在webpack.config.js 和 入口 js 文件引入 @babel-polyfill。
  2. 若是在.babelrc中指定了useBuiltIns:'entry',則經過require或import將@ babel / polyfill包含在應用程序入口點的頂部。
  3. 若是未指定 useBuiltIns 鍵或在 .babelrc 中使用 useBuiltIns:false 顯式設置,請將@ babel / polyfill直接添加到webpack.config.js 對應的配置中去。

你須要瞭解的 @babel/preset-env

使用趨勢

從 2018-9-17 發佈至今,每週的下載量都在穩步提高,推薦小夥伴們在項目中使用基於 babel 7的 @babel/preset-env 插件預設;chrome

簡單介紹

@babel-preset-env 將基於你的實際瀏覽器及運行環境,自動的肯定 babel 插件及 polyfill,編譯ES2015 及此版本以上的語言,在沒有配置項的狀況下,@babel-preset-env 表現的同 babel-preset-latest同樣(或者能夠說同babel-preset-es201五、babel-preset-es201六、 babel-preset-es2017 結合到一塊兒表現的一致)。

優勢

  1. 能夠根據配置的目標環境自動採用須要的 babel 插件;
    • 簡化了 babel 配置
    • 按需引入必要插件,優化了包體積;
  2. 支持 polyfill 相關配置,按需引入相關的 polyfill,優化代碼體積; TIP:能夠經過 www.npmjs.com/package/@ba… 來查看項目版本使用的 @babel/preset-env 版本依賴的 plugins,保證代碼中使用的 js 新的語言特性獲得支持,若是沒有,能夠經過升級 @babel/preset-env 或者安裝對應的 plugin 來支持;

不足:

對於實驗屬性(babel-preset-latest 不支持的)須要手動安裝配置相應的 plugins 或者 presets; 能夠經過 babeljs.io/docs/en/plu… 來查詢相關的實驗屬性對應的插件,來引入支持業務開發;

配置指南

瀏覽器配置相關

  • 每一個瀏覽器最近的兩個版本和 IE 大於等於7的版本所需的 polyfill 和代碼轉譯。
"babel": {
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": ["last 2 versions", "ie >= 7"]
        }
      }
    ]
  ]
},
複製代碼
  • 支持市場份額超過5%的瀏覽器。
"targets": {
  "browsers": "> 5%"
}
複製代碼
  • 指定瀏覽器版本
"targets": {
   "chrome": 56
 }
複製代碼

polyfill 配置: useBuiltIns(默認 false)

@babel-preset-env 默認只支持對語法的轉化,須要開啓useBuiltIns配置才能轉化 API 和實例方法, 來爲標準庫中的新功能提供了 polyfill,爲內置對象,靜態方法,實例方法,生成器函數提供支持。 @babel-preset-env 能夠實現基於特定環境引入須要的polyfill。

  • 可選值包括:"usage" | "entry" | false, 默認爲 false,表示不對 polyfills 處理;
  • entry:根據target中瀏覽器版本的支持,將polyfills拆分引入,僅引入有瀏覽器不支持的polyfill;
  • usage:檢測代碼中ES6/7/8等的使用狀況,僅僅加載代碼中用到的polyfills;

Node.js 配置相關

若是你經過Babel編譯你的Node.js代碼,babel-preset-env 頗有用,設置 "targets.node" 是 "current",支持的是當前運行版本的nodejs:

const presets = [
  [
    "@babel/env",
    {
      node: 'current',
    },
  ],];
module.exports = { presets };
複製代碼

spec : 啓用更符合規範的轉換,但速度會更慢,默認爲 false

loose:是否使用 loose mode,默認爲 false

modules:將 ES6 module 轉換爲其餘模塊規範,可選 "adm" | "umd" | "systemjs" | #### "commonjs" | "cjs" | false,默認爲 false

debug:啓用debug,默認 false

include:一個包含使用的 plugins 的數組

exclude:一個包含不使用的 plugins 的數組

babel 6 遷移 babel 7

在遷移以前建議熟悉 babel 7 具體帶來的改動,這樣有助於咱們在項目中進行具體的優化和進行鍼對性的配置修改

Babel 7 帶來的改動

熟悉相關重要改動,能夠幫助咱們更好的去配置項目,使用最新支持的特性,提升編碼效率

  1. 移除了年度預設用法
    帶有env 的預設已經發布了一年多,如今它會取代一下全部的用法:
  • babel-preset-es2015
  • babel-preset-es2016
  • babel-preset-es2017
  • babel-preset-latest
  1. 移除 stage 預設用法,遷移時能夠參考 github.com/babel/babel… 進行升級;也能夠經過 babel-upgrade 來使用命令行進行替換
  2. 移除 @babel-polyfill 中的提議:能夠參考一下這裏:github.com/babel/babel…
  3. 包重命名:babylon 如今從新命名爲 @babel/parser (看起來是被收了)
  4. 包命名空間
    babel 6 升級 babel 7 最重要的一個改動之一就是給全部的包加了命名空間,這樣作能夠減小不少意外或者故意的命名問題,能夠與社區的其餘插件有一個清晰的區分,造成一個 babel 的命名規範;例如:babel-preset-env 更名爲 @babel/preset-env;
  5. 把 TC39 提議都換成 -proposal 把那些非年度預設的 TC39 插件中的-transform都換成了-propoal,這樣能夠更好的區分出一個提議是否爲 javascript 官方的。例如:
  • @babel/plugin-tranform-function-bind 換成 @babel/plugin-proposal-function-bind
  1. 移除包中的年份
    一些插件中還帶有 -es2015等字符,如今作了統一的規範:通通去掉,例如: @babel/plugin-transform-es2015-classes 換成了 @babel/plugin-transform-classes
  2. 分離 React 和 Flow 之間的預設
  3. 解析選項修改
    如今 Babel 的配置選項會比之前的 Babel6 更加嚴格。像這樣的一個逗號分隔列表:"presets": "es2015, es2016"在之前的版本中能夠運行,從此都要改爲數組才能夠。可是這個對 CLI 不受影響,它依然可使用逗號分隔的字符串。
# right
{
    "presets": ["@babel/preset-env", "@babel/preset-react"]
}

# wrong
{
   "presets": "@babel/preset-env, @babel/preset-react"
}
複製代碼
  1. 插件/預設的暴露
    從此全部的插件和預設向外暴露的都必須是一個函數,而不能是一個對象,這能夠幫助咱們進行緩存。
  2. JSX Fragment Support (<>)
render() {
  return (
    <>
      <ChildA />
      <ChildB />
    </>
  );
}

// output 👇

render() {
  return React.createElement(
    React.Fragment,
    null,
    React.createElement(ChildA, null),
    React.createElement(ChildB, null)
  );
}
複製代碼
  1. TypeScript Support (@babel/preset-typescript)
    使用前(帶有類型聲明的 react 代碼塊)
interface Person {
  firstName: string;
  lastName: string;
}

function greeter(person : Person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}
複製代碼

使用後(移除類型聲明)

function greeter(person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}
複製代碼
  1. Automatic Polyfilling (experimental)
    在 babel 6 中,對於 polyfill 的支持都是採用以下方式:
import "@babel/polyfill";
複製代碼

但它包括整個 polyfill,若是瀏覽器已經支持,你可能不須要導入全部內容。這與@babel/preset-env 試圖用語法解決的問題相同,因此咱們在這裏將它應用於polyfill。選項 useBuiltins:「entry」 經過將原始導入僅拆分爲必要的導入來實現此目的。 可是咱們能夠經過僅導入代碼庫中使用的 polyfill 來作得更好。選項「useBuiltIns:」usage「是咱們第一次嘗試啓用相似的東西(詳細說明)。 關於 @babel/preset-env 詳細的說明,能夠參考這篇文檔:@babel-preset-env 配置說明 ,理解其中每個配置,來利用 babel 新增的 api 來結合項目自身特色進行配置,進行優化。

推薦遷移步驟:

第一步:進入到項目根目錄下:執行 Babel 升級腳本,主要進行以下三種類型的修改:

  1. 將 babel-xx 替換成 @babel/xx,替換成新的插件
  2. 升級 babel-loader 到 支持 babel 7 的版本
  3. 安裝廢棄的 stage 相關的預設對應的 plugins ,各個 stage 對應的插件查詢庫
# 不安裝到本地而是直接運行命令,npm 的新功能
npx babel-upgrade --write

# 或者常規方式
npm i babel-upgrade -g
babel-upgrade --write
複製代碼

以一個依賴 babel 6 的項目爲例:經過 babel 官方提供的升級工具,輕鬆搞定依賴相關的問題:

  • 對 babel-polyfill 進行包命名空間的轉化的同時,又對其進行了升級,對於用戶來講,只須要去配置文件中修改對應的包就 ok 了,毫無其餘痛點,作的很友好;
  • 安裝 babel-preset-stage-1 這個廢棄的 preset 依賴的 plugins 來保證原有的功能可用
    • @babel/plugin-proposal-export-default-from
    • @babel/plugin-proposal-logical-assignment-operators
    • @babel/plugin-proposal-optional-chaining
    • @babel/plugin-proposal-pipeline-operator
    • @babel/plugin-proposal-nullish-coalescing-operator
    • @babel/plugin-proposal-do-expressions
  • 把 TC39 提議都轉化爲 -proposal 例如該項目中使用的 babel-plugin-transform-decorators-legacy -> @babel/plugin-proposal-decorators

第二步:根據上一步 package.json 文件的修改,同步修改 babel 文件中對應的配置,運行開發環境,根據 webpack 報錯逐個解決

  • 升級 babel-plugin-module-resolver:babel 升級到 babel 7 以後,babel-plugin-module-resolve V2 的版本會出現報錯,須要升級到 V3;

  • @babel/plugin-proposal-pipeline-operator 插件沒有添加相關配置項報錯
    • 處理方案:
["@babel/plugin-proposal-pipeline-operator", {
    # 因爲項目中並無使用管道符,因此我這裏就直接給一個 minimal 的屬性值,其餘項目能夠根據具體須要作調整
      "proposal": "minimal"
    }]
複製代碼
  • ES6 modules 與 commonJS 混用致使報錯

- 解決方案:@babel/preset-env 的 modules 屬性設置爲 commonjs/cjs

第三步:優化項目 polyfill 配置

babel 7 帶來了@babel/preset-env 這個插件,它擴展了對 polyfill 的配置,讓使用者能夠優化墊片的體積,作到按需加載,具體細節能夠參考 @babel-preset-env 配置說明

  1. 移除入口文件引入的 @babel-polyfill
  2. 設置 @babel/preset-env useBuiltIns 屬性爲 usage,實現按需加載

  1. 設置 @babel/preset-env 的 corejs 屬性,讓該插件集不只僅支持 stable ECMAScript Features,還支持 proposal ECMAScript Features

配置 Babel 支持 TypeScript 環境開發

能夠參考我這篇 JavaScript 項目遷移 TypeScript 實踐分享 文章來作遷移(在餘下時間我會從新整理髮布到掘金)

  1. 安裝 @babel/preset-typescript 進行 babel 配置
# .babelrc
# yarn add @babel/preset-typescript
"presets": [
    [
      "@babel/env",
      {
        "targets": "cover 95%, safari >= 7",
        "modules": "cjs",
        "corejs": 2,
        "useBuiltIns": "usage",
      }
    ],
    "@babel/react",
    "@babel/typescript"
  ],
複製代碼
  1. 建立 TypeScript 配置文件 tsconfig.json
{
  "compilerOptions": {
    "module": "esnext",
    "target": "es6",
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "sourceMap": true,
    "outDir": "../client-dist",
    "allowJs": true,
    "jsx": "react",
    "noEmit": true,
    "moduleResolution": "node",
    "isolatedModules": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    // `vs code`所須要的,在開發時找到對應的路徑,真實的引用是在`webpack`中配置的`alias`
    "paths": {
    }
  },
  # 這裏這麼設計,是由於咱們項目歷史緣由,js 的業務代碼比較多,在跑 ci tsc 作類型檢查的時候,只走 tsx? 類型文件
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": [
    "node_modules"
  ]
}
複製代碼
  1. webpack 配置修改,支持 TypeScript 開發環境
    • babel-loader 擴展對 .ts?x 文件解析
    • resolves 對應的 extensions 增長對 ts 與 tsx 文件的支持

4. package.json 擴展 TypeScript 類型檢查命令

# package.json
{
  "scripts": {
     "tsc": "tsc --noEmit"
  }
}
複製代碼
  1. 擴展 TypeScript 相關的 Eslint 配置,進行代碼規範約束
    • parser 由 babel-eslint 替換爲 @babel-eslint/parser
    • extends 屬性增長對 typescript 代碼約束插件
      • prettier
      • prettier/react
      • prettier/@typescript-eslint
    • plugins 屬性擴展 @typescript-eslint 插件

總結

本人想經過本文和你們分享一下本身關於 Babel 在項目中使用的一些心得,但願能夠給小夥伴們帶來一些幫助,Babel 不是一個黑盒子,而是一個給開發者提供了各類各樣選擇的工具集,術業有專攻,只須要稍微花一點時間來理解它,就能夠在項目中很安全地使用它,利用 Babel 團隊不斷努力給咱們帶來的新的語言特性的支持; 你們有什麼疑問能夠直接給我留言評論。

參考

gitissue.com/issues/5c18… juejin.im/post/5d0373… juejin.im/post/5d74d4… segmentfault.com/q/101000000… www.jianshu.com/p/d078b5f30… jsweibo.github.io/2019/08/05/… juejin.im/post/5c03a4… blog.hhking.cn/2019/04/02/… github.com/sorrycc/blo… github.com/Kehao/Blog/… juejin.im/post/5b07e7… jsweibo.github.io/2019/08/09/… github.com/SunshowerC/… zhuanlan.zhihu.com/p/43249121 pdsuwwz.github.io/2018/09/29/… - babel 升級踩坑 juejin.im/post/5b174f… juejin.im/entry/5b108… github.com/lmk123/blog… segmentfault.com/a/119000001… segmentfault.com/a/119000001… www.zcfy.cc/article/bab… juejin.im/entry/5b108…

相關文章
相關標籤/搜索