深入對比 Webpack、Parcel、Rollup 打包工具


總字數:3,493 | 閱讀時間:14分鐘 

我們在之前用了兩篇文章來介紹了Webpack的配置和優化,那麼爲什麼這篇文章還要來對比Parcel和Rollup呢?配置過Webpack的童鞋可能會發現了,雖然它具有很高的可配置性和擴展性,以及豐富的插件系統,但是這些無一不給我們的上手帶來限制,有很高的上手門檻。相信大多童鞋在配置時遇到都會遇到莫名其妙的報錯和各種查資料的煩惱,那麼今天就來看一下Parcel和Rollup能給我們的打包帶來哪些便利。

Parcel

Parcel官網的定義就是極速零配置Web應用打包工具,它利用多核處理提供了極快的速度,並且不需要任何配置。

parcel.gif

它有以下優點:

  • 極速打包:Parcel使用worker進程去啓用多核編譯。同時有文件系統緩存,即使在重啓構建後也能快速再編譯。
  • 將你所有的資源打包:Parcel 具備開箱即用的對 JS, CSS, HTML, 文件 及更多的支持,而且不需要插件。
  • 自動轉換:如若有需要,Babel, PostCSS, 和PostHTML甚至 node_modules 包會被用於自動轉換代碼.
  • 零配置代碼分拆:使用動態 import()語法, Parcel 將你的輸出文件束(bundles)分拆,因此你只需要在初次加載時加載你所需要的代碼。
  • 熱模塊替換:Parcel 無需配置,在開發環境的時候會自動在瀏覽器內隨着你的代碼更改而去更新模塊。
  • 友好的錯誤日誌:當遇到錯誤時,Parcel 會輸出 語法高亮的代碼片段,幫助你定位問題。

我們從以下幾個方面分別來看下Parcel如何進行配置。

入口文件

Parcel可以使用任何類型的文件作爲入口,但是最好還是使用Html或者JS文件。我們新建一個項目,創建html和js文件:

<!-- index.html -->
<html>
  <body>
    <script src="./index.js"></script>
  </body>
</html>
//index.js
console.log('hello parcel')

文件創建好後我們就可以啓動Parcel了,它默認內置了一個用於開發環境的服務器,如果將入口設置爲js文件,我們可以在瀏覽器裏查看到js文件內容,但這樣看不到任何頁面效果,因此我們將入口設置爲html文件:

parcel index.html

現在可以在瀏覽器打開http://localhost:1234/,也可以添加-p <prot number>覆蓋默認的1234端口號;這個開發服務器也支持模塊熱更新,我們改變js或者html內容,瀏覽器就會自動更新。同時,Parcel也支持多頁面入口,假設我們的項目結構如下:

- pages
    - home
        index.html
        index.js
    - list
        index.html
        index.js

我們可以繁瑣的手動指定需要加載入口的文件名稱,比如:

parcel ./pages/home/index.html ./pages/list/index.html

也可以通過glob文件匹配模式來匹配所有的html文件:

parcel ./pages/**/*.html

這樣,我們就可以通過http://localhost:1234/home/index.html來訪問到頁面了。

我們發現在入口文件這方面,Parcel相較於Webpack要靈活不少,在Webpack中我們需要通過entry字段來指定入口,而且入口只能是JS文件,要以html爲入口還需要使用第三方插件如html-webpack-plugin;而Parcel在入口文件則寬鬆很多,可以用html文件作爲入口,Parcel會自動加載html文件中引用的腳本或者樣式進行打包;我們甚至還可以將一張圖片或者vue文件直接作爲入口文件。

模塊轉換

通常打包工具只知道如何處理JS文件,在處理其他資源時,比如less/sass、圖片、vue文件等,都需要通過通過轉換器進行轉換,比如webpack的loader就是充當了轉換器的角色;而Parcel支持許多開箱即用的轉換器,比如在項目根目錄配置.babelrc.postcssrc,Parcel就會自動在所有的JS和css上進行轉換。

再比如我們一般在項目中使用sass或者less,然後在js中import來引入樣式,如果在webpack中也是需要通過配置loader才能解析;而我們在Parcel可以不通過任何配置直接import進來,Parcel會自動給我們安裝sass的依賴包:

auto-install.png

代碼分割

雖然項目代碼的增多,打包出來的文件也會隨之增大;通過代碼分割我們可以將首屏不加載的頁面或者模塊拆分到獨立的包中,然後進行按需加載。

在Webpack中實現代碼分割主要是通過配置splitChunks和模塊內的內聯函數動態引入來實現代碼分割,而Parcel支持零配置代碼分割,並且開箱即用。我們可以將代碼拆分成單獨的包,這些包可以按需加載;主要是通過動態import()函數來實現,這個函數與普通的import語句類似,但返回一個Promise對象:

//about.js
export function render({
  console.log("about run");
}
//index.js
let body = document.getElementsByTagName('body')[0]
body.addEventListener("click", ()=>{
  import("./about.js").then((page) => {
    page.render();
    console.log("index import");
  });
});

這樣,Parcel在打包時會將about.js單獨進行打包,然後點擊body時才按需加載。

模塊熱更新

模塊熱更新(HMR)我們在Webpack中也介紹過,是通過在運行時自動更新瀏覽器中的模塊而無需刷新整個頁面,從而改善了開發體驗;其實現的原理主要是在服務器和瀏覽器之間維護了一個websocket連接,服務器自動推送每次代碼改變。

在webpack中主要是通過官方的webpack-dev-server服務器來實現的,而在Parcel中HMR服務器是內置的,開箱即用,在啓動開發環境服務器時自動啓用,無需配置。

Tree Shaking

Parcel不支持Tree Shaking

小結

我們體驗完整個Parcel,發現確實一行配置代碼都沒有寫,和官網的slogan極速零配置確實符合;在Webpack最難配置的模塊轉換方面,Parcel做到了自動識別處理導入的模塊,這是讓我們眼前一亮的地方;而且內部啓用了多進程工作,在打包相同體量的項目時,Parcel會比Webpack快很多;不過Webpack的很多功能Parcel也並不具備,比如常用的Tree Shaking、提取公共代碼等,因此我們在使用它時需要考慮到這些功能是否是我們項目所必須的。

Rollup

我們有時候在開發一些自己項目中用的JS類庫時,比如彈框組件、校驗組件、工具組件等,如果使用Webpack,在打包時會產生很多冗餘代碼,導致一個簡單的類庫打包出來體積也比較龐大;而Rollup就是專門針對類庫進行打包,它的優點是小巧而專注,因此現在很多我們熟知的庫都都使用它進行打包,比如:Vue、React和three.js等。

rollupjs.jpg

Rollup 是一個JavaScript模塊打包器,可以將小塊代碼編譯成大塊複雜的代碼,例如 library 或應用程序。

入口文件

Rollup既然是打包類庫文件,那麼它的入口也就只能是JS文件了(通過第三方插件可以支持Html,這裏不作展開),因此我們新建一個main.js作爲入口文件,打包出來的文件我們命名爲bundle.js,我們可以簡單的通過命令行進行打包:

# 針對瀏覽器環境打包
rollup main.js --file bundle.js --format iife
# 針對Nodejs環境打包
rollup main.js --file bundle.js --format cjs

但是和Webpack一樣,推薦使用配置文件進行打包:

# 默認使用rollup.config.js配置文件
rollup --config

# 使用自定義my.config.js配置文件
$ rollup --config my.config.js

我們來看下rollup.config.js配置文件需要包含哪些選項:

export default {
  // 核心選項
  input,     // 必須
  external,
  plugins,
  // 額外選項
  onwarn,
  // 高危選項
  acorn,
  context,
  moduleContext,
  legacy
  // 必須 (如果要輸出多個,可以是一個數組)
  output: {  
    // 核心選項
    file,    // 必須
    format,  // 必須
    name,
    globals,
    // 額外選項
    paths,
    banner,
    footer,
    intro,
    outro,
    sourcemap,
    sourcemapFile,
    interop,
    // 高危選項
    exports,
    amd,
    indent
    strict
  },
};

我們發現這裏inputoutput.fileoutput.format都是必傳的,因此,一個基礎配置文件如下:

// rollup.config.js
export default {
  input"main.js",
  output: {
    file"bundle.js",
    format"cjs",
  },
};

這裏的format字段大家看了可能不太理解,尤其是裏面的cjs代表什麼意思;由於JS有多種模塊化方式,Rollup可以針對不同的模塊規範打包出不同的文件,它有以下五種選項;

  • amd:異步模塊定義,用於像RequireJS這樣的模塊加載器
  • cjs:CommonJS,適用於 Node 和 Browserify/Webpack
  • es:ES模塊文件
  • iife:自執行模塊,適用於瀏覽器環境 script標籤
  • umd:通用模塊定義,以amd,cjs 和 iife 爲一體

和Webpack一樣,Rollup也支持配置多個文件入口,我們新建foo.jsbar.js兩個入口文件:

export default {
  input: {
    foo"./foo.js",
    bar"./bar.js",
  },
  output: {
    dir"dist",
    format"cjs",
  },
};

這樣打包出來的兩個文件就放入dist中。

插件

插件拓展了Rollup處理其他類型文件的能力,它的功能有點類似於Webpack的loaderplugin的組合;不過配置比webpack中要簡單很多,不用逐個聲明哪個文件用哪個插件處理,只需要在plugins中聲明,在引入對應文件類型時就會自動加載;我們來看幾個常用的插件。

首先是rollup-plugin-json,讓Rollup可以從JSON文件中讀取數據:

import json from "rollup-plugin-json";
export default {
  input"main.js",
  output: {
    file"bundle.js",
    format"iife",
  },
  plugins: [json()],
};

Rollup默認只能加載ES6模塊的js,但是我們項目中通常也會用到CommonJS的模塊,這樣Rollup解析就會出現問題,比如:

//add.js
let count = 1;
let add = () => {
  return count + 1;
};
module.exports = {
  count,
  add,
};

//main.js
//報錯
import add from "./add";
console.log("foo", add.count);

由於ES6模塊導入默認會去找default,因此這裏打包會報錯;這時就需要用到rollup-plugin-commonjs插件來進行轉換:

import commonjs from "rollup-plugin-commonjs";

export default {
  input"./main.js",
  output: {
    file"bundle.js",
    format"iife",
  },
  plugins: [commonjs()],
};

而且目前npm中大多數依賴包都是以CommonJS模塊形式出現,都需用通過這個插件來進行模塊化解析;除此之外,我們引用node_modules中的第三方模塊,還需要用到rollup-plugin-node-resolve進行解析,這兩個插件通常組合使用:

import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";

export default {
  input"./main.js",
  output: {
    file"bundle.js",
    format"iife",
  },
  plugins: [commonjs(), resolve()],
};

除此之外還有很多有用的插件,這裏就不一一贅述使用方法,列出來有需要的童鞋可以根據情況進行使用:

  • rollup-plugin-vue:解析vue文件
  • rollup-plugin-postcss:解析、轉換、提取css文件
  • rollup-plugin-alias:提供modules名稱的alias和reslove功能
  • rollup-plugin-babel:babel轉換
  • rollup-plugin-eslint:eslint代碼檢測
  • rollup-plugin-uglify:js代碼壓縮
  • rollup-plugin-replace:類似Webpack的DefinePlugin, 可在源碼中通過process.env.NODE_ENV用於構建區分環境.

模塊熱更新

Rollup本身不支持啓動開發服務器,我們可以通過rollup-plugin-serve第三方插件來啓動一個靜態資源服務器:

import serve from "rollup-plugin-serve";
export default {
  input"./main.js",
  output: {
    file"dist/bundle.js",
    format"iife",
  },
  plugins: [serve("dist")],
};

不過由於其本質就是一個靜態資源的服務器,因此不支持模塊熱更新

Tree Shaking

由於Rollup本身支持ES6模塊化規範,因此不需要額外配置即可進行Tree Shaking

代碼分割

Rollup代碼分割和Parcel一樣,也是通過按需導入的方式;但是我們輸出的格式format不能使用iife,因爲iife自執行函數會把所有模塊放到一個文件中,可以通過amd或者cjs等其他規範。

export default {
  input"./main.js",
  output: {
    //輸出文件夾
    dir"dist",
    format"amd",
  },
};

這樣我們通過import()動態導入的代碼就會單獨分割到獨立的js中,在調用時按需引入;不過對於這種amd模塊的文件,不能直接在瀏覽器中引用,必須通過實現AMD標準的庫加載,比如Require.js

小結

通過對Rollup的使用介紹,我們發現它有以下優點:

  1. 配置簡單,打包速度快
  2. 自動移除未引用的代碼(內置tree shaking)

但是他也有以下不可忽視的缺點:

  1. 開發服務器不能實現模塊熱更新,調試繁瑣
  2. 瀏覽器環境的代碼分割依賴amd
  3. 加載第三方模塊比較複雜

總結

經過以上對三個打包工具多方面的使用對比,相信大家都有了一個初步的印象;我們來簡單總結一下三個打包工具的使用環境,如果我們需要構建一個簡單的小型應用並讓它快速運行起來,可以使用Parcel;如果需要構建一個類庫只需要導入很少第三方庫,可以使用Rollup;如果需要構建一個複雜的應用,需要集成很多第三方庫,並且需要代碼分拆、HMR等功能,推薦使用Webpack。



本文分享自微信公衆號 - 1024譯站(trans1024)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章