babel-polyfill VS babel-runtime VS babel-preset-env

azumia發表於2018-05-07

在專案開發過程中,我們往往需要引入babel來解決程式碼相容性的問題。目前有三種方式,分別是babel-polyfill,babel-runtime和babel-preset-env,那麼這三種方式有什麼區別,結合webpack打包出來的效果哪種比較優呢,下面我們來對比一下。

準備工作

開始對比之前,我們需要初始化一個webpack專案

npm init babel-test
複製程式碼

這裡我們不打算安裝webpack 4.X的版本,安裝一下3.X的版本即可

npm i -D webpack@3.7.0
複製程式碼

在專案根目錄下新建一個webpack.config.js檔案,配置如下

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  // JavaScript 執行入口檔案
  entry: {
    app: ['./main.js']
  },
  output: {
    // 把所有依賴的模組合併輸出到一個 bundle.js 檔案
    filename: 'bundle.js',
    // 輸出檔案都放到 dist 目錄下
    path: path.resolve(__dirname, './dist'),
  },
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    inline: true
  },
  module: {
    rules: [
      {
        // 用正則去匹配要用該 loader 轉換的 CSS 檔案
        test: /\.css$/,
        // use: ['style-loader', 'css-loader?minimize'],
        use: ExtractTextPlugin.extract({
          // 轉換 .css 檔案需要使用的 Loader
          use: ['css-loader']
        })
      },
      {
        test: /\.js$/,
        use: ['babel-loader']
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      // 從 .js 檔案中提取出來的 .css 檔案的名稱
      filename: `[name].css`,
    })
  ]
};
複製程式碼

package.json配置如下

{
  "name": "babel-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --config webpack.config.js",
    "dev": "webpack-dev-server --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-env": "^1.6.1",
    "css-loader": "^0.28.11",
    "extract-text-webpack-plugin": "^3.0.2",
    "style-loader": "^0.20.3",
    "webpack": "^3.7.0",
    "webpack-cli": "^1.5.3",
    "webpack-dev-server": "^2.11.1"
  },
  "dependencies": {
    "babel-polyfill": "^6.26.0",
    "babel-runtime": "^6.26.0"
  }
}

複製程式碼

其中涉及到的包執行一下npm install安裝一下即可,這裡就不贅述了。這裡的部分包接下來會重複提到,說明為什麼要這樣裝

專案初始化完成後的專案結構如下

babel-polyfill VS babel-runtime VS babel-preset-env

我們往main.js裡面寫入一些程式碼

const elements = [1, 2, 3].map((item) => {
  return (
    console.log('9999')
  )
});

console.log(elements);

async function azumia() {
  console.log('begin');
  await new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, 1000)
  })
  console.log('done');
}
azumia();

console.log(Object.values({ 1: 2 }));

console.log(Array.isArray([]));
複製程式碼

babel-polyfill

babel-polyfill 是為了模擬一個完整的ES2015+環境,旨在用於應用程式而不是庫/工具。並且使用babel-node時,這個polyfill會自動載入。這裡要注意的是babel-polyfill是一次性引入你的專案中的,並且同專案程式碼一起編譯到生產環境。而且會汙染全域性變數。像Map,Array.prototype.find這些就存在於全域性空間中。

所以這裡將其安裝到生產環境

npm install babel-polyfill --save
複製程式碼

webpack.config.js中這樣配置即可

  entry: {
    app: ['babel-polyfill','./main.js']
  }
複製程式碼

我們執行一下一下命令,開始打包

npm run start
複製程式碼

打包出來的bundle.js的檔案大小為259K,而加入babel-polyfill之前的包的大小僅為4K,體積大了許多。 那麼我們能不能做到按需引用babel-polyfill,從而減小包的大小呢?答案是可以的,這就要靠babel-runtime來實現了。

babel-runtime

babel-runtime不會汙染全域性空間和內建物件原型。事實上babel-runtime是一個模組,你可以把它作為依賴來達成ES2015的支援。

比如環境不支援Promise,你可以在專案中加入

require(‘babel-runtime/core-js/promise’)
複製程式碼

來獲取Promise。

這樣我們就彌補了babel-polyfill的缺點,達到了按需載入的效果。但是在實際專案開發過程中,我們往往會寫很多新的es6 api,每次都要手動引入相應的包比較麻煩,維護起來也不方便,每個檔案重複引入也造成程式碼的臃腫。

要解決這個問題,就要用到 babel-plugin-transform-runtime,它會分析我們的 ast 中,是否有引用 babel-rumtime 中的墊片(通過對映關係),如果有,就會在當前模組頂部插入我們需要的墊片。

接下來我們嘗試一下,先安裝babel-runtime和babel-plugin-transform-runtime

npm install --save babel-runtime
npm install --save-dev babel-plugin-transform-runtime
複製程式碼

由於 babel-runtime只是集中了polyfill的library,對應需要的 polyfill 都是要引入專案中,並跟專案程式碼一起打包的,所以就要加入到生產環境依賴中

下面在.babelrc中加入以下配置

{
  "plugins": ["transform-runtime"]
}
複製程式碼

執行打包命令,打包出來的bundle.js的大小為63K,比完整引入polyfill小了好多。但是事物都有兩面性,babel-runtime有個缺點,它不模擬例項方法,即內建物件原型上的方法,所以類似Array.prototype.find,你通過babel-runtime是無法使用的,這隻能通過 babel-polyfill 來轉碼,因為 babel-polyfill 是直接在原型鏈上增加方法。這就悲催了,難道還是要完整引入babel-polyfill?其實還有一個解決的辦法,就是用babel-preset-env

babel-preset-env

babel-preset-env 能根據當前的執行環境,自動確定你需要的 plugins 和 polyfills。通過各個 es標準 feature 在不同瀏覽器以及 node 版本的支援情況,再去維護一個 feature 跟 plugins 之間的對映關係,最終確定需要的 plugins。關於詳細的配置說明,請點選這裡

我們修改一下.babelrc的配置

{
  "presets": [
    ["env", {
      "targets": {
        "chrome": 52,
        "browsers": ["last 2 versions", "safari 7"]
      },
      "modules": false,
      "useBuiltIns": "usage",
      "debug": false
    }]
  ]
}
複製程式碼

其中的useBuiltIns就是是否開啟自動支援 polyfill,它能自動給每個檔案新增其需要的poly-fill。 我們來嘗試一下,main.js中映入babel-polyfill

require('babel-polyfill')
複製程式碼

你沒看錯,是要在main.js中引入,直接在webpack中配置貌似是不行的。

執行打包命令後,bundle.js的大小為194K,對比之下還是要小了些的。

如果此時在瀏覽器中開啟頁面,會發現如下報錯

babel-polyfill VS babel-runtime VS babel-preset-env

這個問題的原因及解決方案請點選這裡,我們修改一下babel-polyfill的引入方式,require 改為 import,將引入提升到前面

 - require('babel-polyfill')
 + import 'babel-polyfill'
複製程式碼

再次打包後即可

總結

對比以上三種方案,我們得出以下結論

方案 打包後大小 優點 缺點
babel-polyfill 259K 完整模擬ES2015+環境 體積過大;汙染全域性物件和內建的物件原型
babel-runtime 63K 按需引入,打包體積小 不模擬例項方法
babel-preset-env(開啟useBuiltIns) 194K 按需引入,可配置性高 -

方案沒有絕對的優劣,在開發過程中還是要根據實際情況靈活運用。

  • 參考文件

你真的會用 Babel 嗎?

babel-polyfill VS babel-runtime

相關文章