【第九期】Rollup:下一代ES模塊打包工具

Vue,React,D3等衆多知名項目都使用rollup進行構建打包,爲何沒有選擇咱們項目中常用的webpack呢?這篇文章主要分析下rollup同webpack相比打包的優點以及它的一些基本使用方式。

背景

rollup從設計之初就是面向ES module的,它誕生時AMD、CMD、UMD的格式之爭還很火熱,做者但願充分利用ES module機制,構建出結構扁平性能出衆的類庫。前端

ES module機制

ES module的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西,舉例來講:node

  1. ES import只能做爲模塊頂層的語句出現,不能出如今 function 裏面或是 if 裏面。
  2. ES import的模塊名只能是字符串常量。
  3. 無論 import 的語句出現的位置在哪裏,在模塊初始化的時候全部的 import 都必須已經導入完成。
  4. import binding 是 immutable 的,相似 const。好比說你不能 import { a } from './a' 而後給 a 賦值個其餘什麼東西。

這些設計雖然使得靈活性不如CommonJS的require,但卻保證了 ES modules 的依賴關係是肯定的,和運行時的狀態無關,從而也就保證了ES modules是能夠進行可靠的靜態分析的。webpack

rollup對比webpack

咱們經過一個案例看一下webpackrollup打包後的代碼結構。web

源文件:json

// index.js
import a from './a.js'
import b from './b.js' 

export default () => {
  console.log(a())
}

// a.js
export default () => 'a'

// b.js
export default () => 'b'
複製代碼

webpack打包生成:瀏覽器

(function(modules) { // webpackBootstrap
  // 大量的runtime代碼
  // ...
})
({
  "./src/a.js":
  (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
  eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (() => 'a');\n\n//# sourceURL=webpack:///./src/a.js?");

  }),

  "./src/b.js":
  (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
  eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (() => 'b');\n\n//# sourceURL=webpack:///./src/b.js?");

  }),

  "./src/index.js":
  (function(module, __webpack_exports__, __webpack_require__) {
 "use strict";
  eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ \"./src/a.js\");\n/* harmony import */ var _b_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./b.js */ \"./src/b.js\");\n\n \n\n/* harmony default export */ __webpack_exports__[\"default\"] = (() => {\n console.log(Object(_a_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])())\n});\n\n//# sourceURL=webpack:///./src/index.js?");
  })
})
複製代碼

rollup打包生成:緩存

var test = (function () {
 'use strict';

  var a = () => 'a';

  var index = () => {
    console.log(a());
    
  };

  return index;

}());
複製代碼

首先,webpack致力於複雜SPA的模塊化構建,優點在於:性能優化

  1. 經過loader處理各類各樣的資源依賴
  2. HMR模塊熱替換
  3. 按需加載
  4. 提取公共模塊

rollup致力於打造性能出衆的類庫,有以下優點:bash

  1. 編譯出來的代碼可讀性好
  2. rollup打包後生成的bundle內容十分乾淨,沒有什麼多餘的代碼,只是將各個模塊按照依賴順序拼接起來,全部模塊構建在一個函數內(Scope Hoisting), 執行效率更高。相比webpack(webpack打包後會生成__webpack_require__等runtime代碼),rollup擁有無可比擬的性能優點,這是由依賴處理方式決定的,編譯時依賴處理(rollup)天然比運行時依賴處理(webpack)性能更好
  3. 對於ES模塊依賴庫,rollup會靜態分析代碼中的 import,並將排除任何未實際使用的代碼(tree-shaking,下文會有具體解釋)
  4. 支持程序流分析,能更加正確的判斷項目自己的代碼是否有反作用(配合tree-shaking)
  5. 支持導出es模塊文件(webpack不支持導出es模塊)

固然,rollup也有明顯的缺點:前端框架

  1. 模塊過於靜態化,HMR很難實現
  2. 僅面向ES module,沒法可靠的處理commonjs以及umd依賴

webpack和rollup的使用原則

經過以上的對比能夠得出,構建App應用時,webpack比較合適,若是是類庫(純js項目),rollup更加適合。

webpack構建App的優點體如今如下幾方面:

  1. 強大的插件生態,主流前端框架都有對應的loader
  2. 面向App的特性支持,好比以前提到的HMR按需加載公共模塊提取等都是開發App應用必要的特性
  3. 簡化Web開發各個環節,包括圖片自動base64,資源緩存(chunkId),按路由作代碼拆分,懶加載
  4. 可靠的依賴模塊處理,不像rollup那樣僅面向ES module,面臨cjs的問題(webpack經過__webpack_require__實現各類類型的模塊依賴問題)

rollup的優點在於構建高性能的bundle,這正是類庫所須要的。

tree-shaking

tree-shaking能夠理解爲經過工具"搖"咱們的JS文件,將其中用不到的代碼"搖"掉,屬於性能優化的範疇。

tree-shaking較早由rollup實現,後來webpack2也藉助於UglifyJS實現了tree-shaking的功能。

tree-shaking的本質是藉助ES module的靜態分析來消除無用的js代碼,無用代碼有如下幾個特徵:

  1. 代碼不會被執行到
  2. 代碼執行的結果不會被用到
  3. 代碼隻影響死變量

rollup打包時的tree-shaking案例:

// add.js:
export default (a, b) => {
  return a + b
}

// index.js:
import add from './add.js'
export default () => {
  add(1, 2)
}

// 打包後bundle.js:
var index = () => {
};
export default index;
複製代碼

add(1, 2)的執行結果在index.js中沒有被用到,所以在bundle.js中被'搖'掉了。

若是函數中存在反作用,那麼tree-shaking會失效:

// add.js:
export default (a, b) => {
  window.a = 'a'
  return a + b
}

// ...

// bundle.js:
var add = (a, b) => {
  window.a = 'a';
  return a + b
};

var index = () => {
  add(1, 2);
};

export default index;
複製代碼

因此咱們儘可能不要寫帶有反作用的代碼。

rollup使用教程

首先在項目中安裝rollup:

yarn add rollup -D
複製代碼

package.json中加入構建腳本命令:

{
  "scripts": {
    "build": "rollup -c"
  }
}
複製代碼

核心屬性

rollup 支持命令行JS API兩種調用方式,咱們重點來看下命令行配置文件中的幾個核心屬性:

// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';

export default [{
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
    name: 'test'
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({
      exclude: 'node_modules/**'
    })
  ]
}]
複製代碼

1. input: 填寫打包的入口文件路徑

2. output.format: 源碼構建輸出的格式

  1. iife: 自執行函數, 可經過 <script> 標籤加載
  2. amd: 瀏覽器端的模塊規範, 可經過 RequireJS 可加載
  3. cjs: Node 默認的模塊規範, 可經過 Webpack 加載
  4. umd: 兼容 IIFE, AMD, CJS 三種模塊規範
  5. es: ES module 規範, 可用 Webpack, Rollup 加載

rollup爲了方便類庫的使用者進行tree-shaking,提供了es的構建格式:

源文件:

// add.js:
export default (a, b) => a + b

// index.js:
import add from './add.js'
export default () => {
  var result = add(1, 2)
  console.log(result)
}
複製代碼

rollup按照es的格式構建:

var add = (a, b) => {
  return a + b
};
var index = () => {
  var result = add(1, 2);
  console.log(result);
};
export default index;
複製代碼

能夠看出,咱們獲得了一個基於ES module規範的bundle,此時讀者可能會有這樣的疑問:應用項目中一般會設置babel屏蔽node_module目錄下的文件,若是將pkg.main指向當前ES module規範的bundle,項目最終打包後的bundle中會包含ES module代碼。

爲了解決上述問題,rollup最先提出了pkg.module,配置導出格式爲es的文件的路徑,打包工具在遇到pkg.module字段時,會優先使用。

綜上所述,類庫經過rollup能夠設置打包出兩份文件,一份umd(按照實際須要可選其餘),一份es,將它們的路徑分別設置爲package.json中的mainmodule的值。這樣就能方便類庫的使用者進行tree-shaking

3. external + output.globals

rollup經過external + output.globals來標記外部依賴,以lodash爲例:

// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
export default [
  {
    input: 'src/index.js',
    output: {
      file: 'dist/bundle.js',
      format: 'iife',
      name: 'test',
      globals: {
        'lodash': '_'
      }
    },
    external: [
      'lodash'
    ],
    plugins: [
      resolve(),
      commonjs()
    ]
  }
]

// index.js
import lodash from 'lodash'
export default () => {
  console.log(lodash)
}

// bundle.js
var test = (function (lodash) {
 'use strict';

  lodash = lodash && lodash.hasOwnProperty('default') ? lodash['default'] : lodash;

  var index = () => {
    console.log(lodash);
  };

  return index;

}(_));
複製代碼

4. plugins

rollup同時提供了一些插件來解決壓縮babel轉換等問題,這裏列舉幾個經常使用的插件:

  1. rollup-plugin-alias: 配置module的別名
  2. rollup-plugin-babel: 打包過程當中使用Babel進行轉換, 須要安裝和配置Babel
  3. rollup-plugin-eslint: 提供ESLint能力, 須要安裝和配置ESLint
  4. rollup-plugin-node-resolve: 解析node_modules 中的模塊
  5. rollup-plugin-commonjs: 轉換 CJS -> ESM, 一般配合上面一個插件使用
  6. rollup-plugin-replace: 相似於webpack的DefinePlugin

其餘配置見:rollup官網

最後感謝您花時間閱讀這篇文章,但願這篇文章能對您有所幫助。

參考文章: 1.www.zhihu.com/question/41… 2.juejin.im/post/5a4dc8… 3.www.ayqy.net/blog/rollup…


水滴前端團隊招募夥伴,歡迎投遞簡歷到郵箱:fed@shuidihuzhu.com

相關文章
相關標籤/搜索