《深入淺出webpack》有感

曾培森發表於2019-10-09
對於前端仔來說,相信大家對webpack都再熟悉不過了,但是你對webpack的瞭解程度又有多深呢,筆者花了幾天時間看了一下《深入淺出webpack》,雖然說書中大部分介紹的是配置和使用相關的,但是如果你對webpack的配置、使用、原理和構建流程更加熟悉的話,對於你的開發可以說是百里無一害!本文不會侷限於介紹配置,也不會詳細介紹打包原理(後面打算寫一篇有關webpack打包原理的~),更多著重於webpack打包的思想介紹。

沒有打包構建的日子

nodejs的出現對於構建工具具有重要的意義,在沒有nodejs之前,js只能執行在瀏覽器環境下,所以意味著對釋出前的js檔案要進行處理,十分侷限,沒有打包工具,只能用PHP指令碼來處理檔案,甚至還需要藉助一些線上壓縮網站,開發體驗十分差勁,在史前時代存在以下幾個痛點:
1、缺乏檔案處理工具,對檔案進行編譯或其他預處理,進行打包壓縮等工作;
2、缺乏檔案的模組化,引入第三方庫直接用cdn引入,需要處理依賴管理,人為控制指令碼的載入順序,並且存在全域性變數命名衝突的問題;
3、缺乏程式碼校驗和自動化測試,在程式碼被提交到倉庫前需要校驗程式碼是否符合規範,以及單元測試是否通過。

有了打包構建工具的日子

隨著nodejs的誕生,我們可以在開發環境下書寫nodejs程式碼指令碼,對我們的前端程式碼做預處理,編譯壓縮等工作,最初誕生的是grunt和gulp,Grunt和Gulp都屬於任務流工具Tast Runner,兩者都是通過配置好配置檔案,但是相比之下,gulp通過函式式編寫配置檔案,以及前端人員所熟悉的鏈式呼叫,讓大家覺得更易懂更易上手,gulp本身借鑑了grunt的經驗進行升級和加入一些新特性。正因為流管理多工配置輸出方式的提高,人們逐漸選擇使用Gulp而放棄grunt。
有了grunt和gulp,檔案壓縮處理的工作解決了,程式碼校驗和測試也可以處理了,但是模組化仍沒有結果?
其實前端的痛點還遠不止模組化那麼簡單,頻繁的DOM節點處理,JS裡雜糅了互動邏輯、請求邏輯、資料處理和校驗邏輯、DOM操作邏輯,導致JQ書寫的程式碼就更義大利炒大便,呸!義大利炒麵一樣。在團隊開發中,可能你的程式碼要給別人維護,這就非常痛苦了。

clipboard.png

webpack誕生記

1、模組化思想

隔離不同的js檔案,模組化開發,僅暴露當前模組所需要的其他模組,這是模組化思想想要傳遞給我們的。nodejs誕生後,後端所採取的模組化思想是commonjs,然而,不同於後端,前端的程式碼執行在瀏覽器端,有兩點不同之處:
1、沒有nodejs執行環境,不支援module.exports的書寫格式;
2、後端require一個檔案,是讀取本地檔案的形式,速度極快,而對於前端而言,需要去動態載入一個js檔案,存在額外耗時。
於是AMD思想應運而生,對此的相應實現是requireJS,允許你定義好模組名稱、模組依賴以及當前的模組程式碼(function),通過廣度優先遍歷的方式,遞迴載入父模組所依賴的子模組,但是這也暴露出了一些問題:
1、通過js載入執行後再去載入其依賴的子模組,這個遞迴載入過程本身是耗時的;
2、模組化思想提倡我們分隔邏輯,管理好各個js檔案內的邏輯,一旦分割的JS檔案過多,最終造成前端資源載入壓力。
不過不用擔心,requireJS提供了r.js來處理髮布前的模組合成,幫助你把多個JS檔案打包成一個檔案。
再到後來,國內出現了CMD的思想,不同於AMD的宣告依賴的形式,允許你動態載入依賴,但是其實現以及具體的執行結果被大家詬病。
再到後後來,為了解決這種不同庫的模組實現不一致的問題,提出了UMD,其實很簡單,只是寫一段hack,讓你的模組能夠相容不同的模組載入場景,無論是commonjs還是AMD,如果都沒有的話就直接宣告為一個全域性變數的形式。
下面引用一段UMD的程式碼:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.Vue = factory());
}(this, function () { 
  'use strict';
  //your code here
}

再到後後後來,未來的大一統,es6中所提出的import和export的形式來統一前後端的模組化載入方式。相比以往的實現,import/export的形式,在模組化載入JS檔案的時候,保留動態的執行引用,其次,不允許動態控制載入依賴,使得tree-shaking成為可能。

2、元件化思想

元件化的思想並非前端所特有,在客戶端也會面臨相同的問題。想象一下,A跟B被同時分配一起開發完成一個首頁頁面,包括導航欄、輪播圖、網站列表資料,登入框等,兩人需要如何分工協作?導航欄、列表這種需要在多個頁面複用的HTML怎麼辦?假如在沒有元件化處理的情況下:
1、A和B分工困難麻煩,程式碼提交時會處理大量的衝突;
2、導航欄、列表等多處複用的地方,需要cv大法直接複製貼上到另一個頁面中去使用。
元件化思想,讓我們把頁面劃分為一個個元件,元件內部維護自己的UI展示、互動邏輯,元件間可以進行資料通訊,實現一種變相的相互隔離,便不會出現A和B兩人一起編輯一段html的難受場景,同時,提高了程式碼的可維護性和複用性,這是其解決的關鍵痛點。
引用vue官網的一張有關元件化思想的圖:

clipboard.png

3、MVC框架、MVVM框架的流行

在模組化和元件化的基礎上,實現了頁面元件之間的隔離和各自維護,但是面臨的最大的一個痛點問題,仍然是前面所說的,前端的JS邏輯中雜糅了各種處理邏輯,互動邏輯、請求邏輯、資料處理和校驗邏輯、DOM操作邏輯;而其實這一切可以劃分為兩個層次,一個是資料層,一個是檢視層,如何避免重複的書寫操作DOM的邏輯,如果說早期的各種模板引擎給了我們初期的解決方案,那麼vue、react以及angular就是在模板引擎的基礎上的上層建築。
為了讓我們更加專注於資料的處理,MVC框架和MVVM框架幫我們做了以下兩件事:
1、監聽頁面操作事件,觸發相應的事件鉤子,執行程式碼邏輯,即V層到M層的過程;
2、執行程式碼邏輯後,資料層發生修改,幫我們更新渲染頁面,即M層到V層的過程;vue中通過vm實現,react中通過觸發setState通知。
如此,我們只需要書寫一次html,在html中寫明繫結或展示的資料,同時繫結好事件監聽器,後續便不需要再處理檢視層相關的操作,只需要關注於自己的業務邏輯程式碼、資料層的處理等。
MVVM架構流程圖:

clipboard.png

4、程式碼打包構建

前面介紹了grunt、gulp打包構建工具,其實webpack本質也是打包構建工具,但是webpack呈現出來的功能更為強大和成熟。對於程式碼的預處理、模組化載入、程式碼分割等,webpack具有更大的優勢。

webpack誕生!

clipboard.png

讀者讀到這裡,可能仍有些許疑惑,前面講了這麼多,為啥還是沒有介紹到webpack相關的,其實不然,仔細回想一下前面所介紹的思想,以及你平時使用webpack來打包構建的時候,其實webpack正是幫你處理了這些繁雜瑣碎的事情。
如果程式碼預處理壓縮足以,那麼grunt和gulp已經滿足了;
如果說模組化開發足以,那麼requireJS和Browserify已經滿足了;
如果說元件化開發、MV*框架足以,那麼只需要在頁面內引入相應的vue或react框架,足矣。
筆者寫這邊文章,更多是想讓大家能夠思考工具或者框架背後,所呈現出來的思想,webpack就像是一個巨無霸,集大成者,它解決了打包構建,它處理了模組化開發,它幫助你和其他框架完美融合實現元件化開發;而這幾年來MV*框架的流行對於webpack市場的迅速擴充套件有著不小的貢獻。

webpack為我們做了以下這些事:(引自《深入淺出webpack》)
程式碼轉換:TypeScript 編譯成 JavaScript、SCSS 編譯成 CSS 等。
檔案優化:壓縮 JavaScript、CSS、HTML 程式碼,壓縮合並圖片等。
程式碼分割:提取多個頁面的公共程式碼、提取首屏不需要執行部分的程式碼讓其非同步載入。
模組合併:在採用模組化的專案裡會有很多個模組和檔案,需要構建功能把模組分類合併成一個檔案。
自動重新整理:監聽本地原始碼的變化,自動重新構建、重新整理瀏覽器。
程式碼校驗:在程式碼被提交到倉庫前需要校驗程式碼是否符合規範,以及單元測試是否通過。

其實以小見大,你能窺見的不僅是webpack的思想,更多的是前端的發展,從最初的土法煉鋼,不規範,到如今的模組化、元件化、MV*框架,是前端思想的進步。作為一個前端仔,我們應該探索和研究的是如何磨刀、磨好刀,而不是砍柴而已。

謝謝觀看~

相關文章