# 為什麼前端需要工程化? — webpack

發明 webpack 的人應該得圖靈獎

這大概是我對 webpack 的讚嘆程度。

在談論為什麼前端需要工程化之前,我們先來定義一下什麼是工程化,根據維基百科的說法:

軟體工程(英語:software engineering[1] (opens new window)),是軟體開發 (opens new window)領域裡對工程方法的系統應用。

如果再更深入一點歸納維基百科的敘述,可以歸納成最重要的一點:

  • 系統化:用可以量化、標準化的方式來開發、維護軟體、提高軟體品質

那麼,為什麼前端需要系統化呢?我覺得有幾點:

  1. 以前對於前端需求不高,只需要展示靜態頁面就可以了;但隨著科技進步,許多應用也越來越複雜(如 google drive, facebook 等),如果不引入一些幫助工程化的工具,程式開發會變得難以管理
  2. 因為前端發展逐漸成熟,各式工具與理論提出後,大家有餘裕開始發展關於前端工程化的各種工具(gulp, webpack 等)

其實前端工程化並不是一個「哦我們現在來做工程化吧」的 buzzword,而是在開發中或多或少就會「啊要是可以這樣做就好了」的感覺。

今天來介紹幾個在做前端工程化時常見的概念與建構工具。

# Webpack (opens new window)

在比較大型的前端專案當中,webpack 幾乎可以說是不可或缺的工具。

在早期 JavaScript 當中,沒有一個好的模組管理方式,只能靠一些 design pattern 跟紀律來做這件事,當然就會讓一些沒有節操的工程師寫出一些難以維護的程式碼。

再來就是如果你將模組用檔案拆分,很快就會遇到相依性的問題,例如:

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
<script type="text/javascript" src="c.js"></script>
<script type="text/javascript" src="d.js"></script>

d 相依於 a,b,c 相依於 a等等,只要順序一改就會爆炸,久而久之大家都不想模組化全部塞在同一個地方了。

在當時有幾個解決方式,像是 CommonJS 以及 RequireJS 等定義相依的方式,讓函式庫幫你搞定相依性的問題。

但還是免不了幾個問題:

  • 相依性還是要自己宣告
  • 如果想要做動態載入怎麼辦(dynamic import)

webpack 問世後馬上受到開發者青睞,原因就是 webpack 太強大了。除了 javascript 之外,就像一個超大型的 dependencies graph,幫你搞定各種麻煩事。

除了引入 JavaScript 檔案,只要設定 loader,你也可以載入像是圖片、字型檔、CSS 等等。

# Tree shaking

初次看到這個詞的時候覺得超酷,概念上也很容易思考,把樹搖一搖然後枯葉果實就會掉下來。

在這裏枯葉果實指得是不必要的程式碼,tree shaking 指的是有時我們在寫程式時可能只會用到部分模組的程式碼,這樣子就沒有必要把整份檔案都 bundle 進來,減少 bundle size。

問題在於,我們要怎麼知道這段程式碼或函數沒有被使用到?

在一般的靜態語言當中我們可以仰賴編譯器做到這件事,但在 JavaScript 裡就只能靠自己了。幸好 webpack2 以後加入了 tree shaking 功能造福開發者們,但背後仰賴的是 es6 import 的語法來確保能夠被靜態分析。

# Dead Code elimination

簡稱叫做 DCE,只要是消除一些絕對不可能執行的程式碼,像是:

if (false) {
  // your code
}

既然條件是寫死的 false,那麼這裡 code 就不可能執行,在做 transpile 的時候就可以拔掉。你或許會問,誰沒事會寫這種 code 啊?但像是搭配 webpack 的 DefinePlugin (opens new window) 之類的,就可以利用這個機制來幫助消除不必要的程式碼,同時又能保留在 development 或 production 時需要不同 build 機制的彈性。

詳細可以參考 Dan 的 how does development mode work (opens new window)

# Code Splitting

Code splitting 的主要目的在於將應用程式的程式碼與第三方庫的程式碼拆分成兩個檔案。

通常我們使用的第三方程式碼,除非有跳版號,不然幾乎不會更動,這時就可以將這些程式碼額外拆出來變成一包,上傳至 CDN 來啟用快取機制,這樣使用者就只要載入應用本身的程式碼就好,未來版號改變時也可以很容易用 hash 機制來更新。

# Minify

在開發的時候,我們會將變數命名得夠清晰易懂,以便其他人(以及自己)了解用途,但在 production build 的時候並不需要,這時就可以將變數名稱最小化。minify 是個蠻深的學問,例如怎麼刪除註解、正確地消除空白空行、

前面有提到透過傳入參數的方式,不僅可以明確知道裡頭會用的參數有哪些之外,也方便做最小化。

# Dynamic import

在使用者剛載入首頁時,可能還不需要進入到 Profile 的頁面,這時候我們就可以先只載入首頁相關的程式碼,等到使用者進入 Profile 頁面的時候再載入,或是利用 idle 的時間偷偷先幫載入。

# 小結

webpack 設定檔真的非常、非常難寫,尤其是寫出一個堪用、好用的設定檔通常需要不少時間來測試跟調整參數。

如果要一個完善的 building 機制,研究各種 plugin 可能需要不少時間。當然要不要花時間研究建構工具就見仁見智啦。想要省下設定的時間也可以直接用 create-react-app 或是 vue-cli 等生態系的建構工具。

大部分的設定其實都雷同(在前端開發部分),不過可能根據產品的需求跟使用狀況需要不同的調整。

另外,升級會是個相當麻煩的事情,像 webpack 升級時常常出現 breaking changes(雖然也沒有那麼 breaking),這時候要重新修改設定檔又需要花更多時間研究,如果在公司引入 webpack 時,最好一開始就把設定檔寫完整,不要為了趕功能而胡亂設定導致 bundle size 大的要命又不知道怎麼優化。

雖然文章標題寫的是前端工程化,不過這一篇主要講的還是建構工具與對應的概念。