讓我們來聊聊前端的工程化

北冥有隻魚發表於2022-12-14

前端發展淺談

讓我們回憶一下前端系列的文章:

  • Vue 學習筆記(一) 初遇
  • Node.js 教程(一) 基本概念與基本使用
  • Vue學習筆記(二) 相識篇
  • Vue學習筆記(三) 甚歡篇
  • JavaScript學習筆記(一) promise和async/wait
  • CSS學習筆記(一) 盒子模型

Node.js 讓JavaScript程式碼可以在瀏覽器之外的地方執行,同時引入了模組化,提升了JavaScript的複用能力。而Vue則為我們帶來的是快速構建大型Web系統的能力:

  • 響應式:Vue會自動化跟蹤JavaScript狀態並在其發生變化時響應式地更新DOM
  • 宣告式渲染: Vue基於標準HTML擴充了一套模板語法,使我們可以宣告式地描述最終輸出的HTML和JavaScript之間的關係。
  • 元件化: 提升的是Web工程的複用能力, 將JavaScript、CSS、HTML封裝在一起,按需引入。

那開發Web應用,我們可以將Node.js和Vue.js結合在一起,將Vue.js當做依賴引入, 同時也享受npm帶來的便利。那最終的產物呢,我們可以類比服務端應用程式Java,Java最終交付的是一個jar或war,這個jar或者war被JVM或者Servelt容器(Tomcat)所解析,對外提供服務。那Node.js和Vue.js結合之後呢,最終交付的應該是什麼呢?對於Java的服務端應用程式來說,一般用Spring Boot來搭建,對於Spring Boot程式搭建的Web應用都會有一個入口。那對於對於Web前端呢,是否也需要一個入口呢? 我們當前掌握的有:

  • Vue.js (基本特性的使用)
  • Node.js (會用npm、簡單的啟動一個web伺服器)
  • 基本的JavaScript、HTML、CSS
  • Sass: CSS的擴充套件語言

我沒學系統的學習前端的時候覺得這些就足夠了,但我忽略的一點就是我是在以服務端的程式在看前端,所以會存在一些盲點。用Java寫服務端應用程式的時候,開發的JDK版本和生產的版本是一致的,也就是說你用JDK 8開發的程式碼,實際應用的JDK版本也只會是JDK 8,不會是JDK 7,JDK 8的部分程式碼在JDK 7上可能是執行不了的。如果你用了JDK 8的新特性,僅僅是語法層面的,比如Lambda,但是你又想這套程式碼在JDK 7上執行,那其實可以用retrolambda來進行轉換。那如果我用JDK 8 新增的新類庫呢? 比如Stream,這個JDK 8才引入的類,那其實可以試著用streamSupport以及ThreeThen來進行轉換。如果你只用了JDK 7的語法特性和庫,那在編譯的時候可以加上-source 1.7 -target來指定編譯到JDK 7版本的class檔案。如果不這麼做可能就會出現下面的異常:

java.lang.UnsupportedClassVersionError

那我們用JavaScript、CSS、HTML構建的頁面呢? 這些是被瀏覽器所解析的,這就意味著我們無法確定使用者瀏覽器的版本,也就是說高版本的JavaScript特性可能在使用者瀏覽器上市無法被支援的,但高版本的JavaScript特性又實在好用,總不能在開發的時候,低版本的寫一套,高版本的寫一套吧。有沒有像Java那樣的轉換工具,將高版本的程式碼等價轉換成效果一樣的低版本程式碼呢? 當然是有的,這也就是Babel,那什麼是Babel:

Babel是一個工具鏈,主要用於將採用ECMAScript 2015+語法編寫的程式碼轉換為向後相容的JavaScript語法,以便能夠執行在當前和舊版本的瀏覽器或其他環境中。
  • 語法轉換
  • 透過Polyfill(我個人傾向於將其翻譯為適配)方式在目標環境中新增缺失的特性(透過引入第三方polyfill模組,例如core-js)
  • 原始碼轉換

下面是babel的語法轉換示例:

// Babel 輸入: ES2015 箭頭函式
[1, 2, 3].map(n => n + 1);

// Babel 輸出: ES5 語法實現的同等功能
[1, 2, 3].map(function(n) {
  return n + 1;
});

我們不打算在這一篇完全介紹babel,我們本篇的主題是前端工程化。讓我們回到最終的問題,Web前端頁面最終應該交付成一個什麼樣的形式? 我們在回顧一下前後端不分離的時代,即前端程式碼都在一個專案下的場景,這樣專案在如今已經不多見了,但是我想這樣的專案會讓我們有所啟發。一般用maven構建的專案,會有一個resources資料夾,這個資料夾我們用來放配置檔案,在這個資料夾下面我們一般有兩個資料夾: static、templates:

專案結構

templates 放html,static放css、js。一般templates裡面會直接放一個index.html,這也是首頁,進入首頁之後來跳對應的頁面。那我最後用Vue.js、Node.js、CSS、Sass、Babel開發的瀏覽器頁面也被做成這樣的形式呢? 即js 資料夾裡放js檔案,css資料夾放css檔案,圖片放在jpg資料夾,字型放在font資料夾呢。這也就是webpack做的工作,下面這張圖來自webpack官網:

webpack

Webpack將我們構建前端頁面所用的各種檔案按類別放入對應的資料夾,所以Webpack也被稱為打包工具,本質上webpack是一個用於現代JavaScript應用程式的靜態打包工具,當webpack處理應用程式時,它會在內部從一個或多個入口點構建一個依賴圖,然後將你專案所需的每個模組組合成一個或多個bundles,它們均為靜態資源,用於展示你的內容。

bundle的對應的中文為: 捆, (一)包,(一)扎,一批(同類事物或出售的貨品);風趣的人; 笑料;一大筆錢。

下面是表達這個詞的圖片:

bundle

那該怎麼理解這個bundle, 或者什麼是module bundle?

## module bundling 簡介

什麼是模組捆綁? 宏觀上說,模組捆綁只是將一組模組(及其依賴項)以正確的順序拼接成一個檔案(或一組檔案)的過程。在JavaScript中,一個模組就是一個檔案。一個指令碼就是一個模組。模組之間可以相互載入,並可以使用特殊的指令export和import來交換功能,從另一個模組呼叫一個模組的函式。

那為什麼需要合併模組(bundle module)呢? 原因在於我們將程式劃分為模組時,通常會將這些模組組織到不同的檔案和資料夾中,一般情況下,還會引入其他庫的模組,比如Underscore.js或React。

因此頁面需要的模組都必須包含在主html檔案的script標籤中,然後在使用者訪問您的主頁時由瀏覽器載入,一個檔案一個script標籤意味著瀏覽器必須一個一個的載入檔案,一個一個的載入也就意味著會花一定的時間,一個載入就對應一個HTTP請求,

為了解決這個問題,我們將所有的檔案捆綁或連結成一個 大檔案(或幾個檔案,視情況而定)以減少請求數量。另一種加速的方式是減小程式碼體積,從原始碼中刪除不必要的字元, 例如空格、註釋、換行符等。

由於JavaScript的原生模組化來的比較晚,JavaScript社群有幾種不同的模組系統,如果你現在使用的是非原生模組系統,如CommonJS或者AMD,則需要使用專門的工具將這些模組轉換為對瀏覽器來說更加友好的程式碼,這也就是Browserify、RequireJS、WebPack等其他打包工具發揮作用的地方。除了合併模組之外,模組打包工具還提供了其他大量附加功能,例如在更改程式碼的時候自動重新編譯程式碼。

前端工程化

說了這麼多,前端工程化在哪裡呢? 或者我們換種方式來理解前端工程化,前端工程化是一個組合詞,前端 + 工程化,那什麼是工程化? 或者讓我們回顧下軟體工程的發展簡史:

1970年代和1980年代的軟體危機。在那個時代,許多軟體最後都得到了一個悲慘的結局,軟體專案開發時間大大超出了規劃的時間表。一些專案導致了財產的流失,甚至某些軟體導致了人員傷亡。同時軟體開發人員也發現軟體開發的難度越來越大。在軟體工程界被大量引用的案例是Therac-25的意外:在1985年六月到1987年一月之間,六個已知的醫療事故來自於Therac-25錯誤地超過劑量,導致患者死亡或嚴重輻射灼傷。

鑑於軟體開發時所遭遇困境,北大西洋公約組織在1968年舉辦了首次軟體工程學術會議,並於會中提出"軟體工程"來界定軟體開發所需相關知識,並建議"軟體開發應該是類似工程的活動"。軟體工程自1968年正式提出至今,這段時間累積了大量的研究成功,廣泛地進行大量的技術實踐,藉由學術界和產業界的共同努力,軟體工程正逐漸發展成為一門專業學科。

關於軟體工程的定義,在GB/T11457-2006《訊息技術 軟體工程術語》中將其定義為"應用電腦科學理論和技術以及工程管理原則和方法,按預算和進度,實現滿足使用者要求的軟體產品的定義、開發、和維護的工程或進行研究的學科"。

Therac-25: 是加拿大原子能有限公司(AECL) 在 Therac-6 和 Therac-20 裝置之後於 1982 年生產的一種計算機控制的放射治療機。它有時會給患者帶來比正常情況高數百倍的輻射劑量,導致死亡或重傷

軟體的複雜讓專案時間超出了規劃時間,導致了一定程度的財產損失,軟體在變得複雜的過程中,出現了質量的不可控,造成了人員的傷亡。由此引出了軟體工程,軟體工程的目標就在於進度與質量的把控。而對於軟體開發難度越來越大,也是軟體工程需要解決的問題,這對應程式碼的複用,版本管理,開發協作。這也就是軟體工程化。我想前端工程化也是因為出現了類似這樣的問題,才會引入工程化的方法來去解決對應的問題, 對於頁面變的不斷複雜的情況下,加快開發進度同時保證質量,這是前端工程和軟體工程共同追求的,那如何加快開發進度呢? 那就是引入工具,自動化,將一些重複的需求變成工具,減少對於程式設計師的依賴,這也就是打包工具出現的原因,像webpack、Babel。另一種加快開發進度的原因就是增強程式碼的可複用性,這也就是模組化,將功能封裝進模組,不需要在重複開發。以此來實現質量和進度的把控,無論前端怎麼邊,或者需求怎麼變,工程化追求的始終就是速度與質量,而開發速度則讓我想到了工具,越是大型的專案工具就越多,會有更多的標準流程。所以隨著軟體的發展,應當會有越來越多工具進入軟體開發,需求也會傾向於被拆分,便於分工。

寫在最後

老實說本文最初的思路是從軟體工程的定義引出打包工具這些,但關於工程化來說這方面並沒有明確的定義,所以沒有辦法想做數學證明一樣,給定某些條件證明某個結論的正確性。後面覺得工程化更像是程式碼量變多之後的一個自然選擇,即實現自動化、避免做重複操作、可複用性高,質量好、進度快。為了實現這些前端引入了各種各樣的工具和概念。關於模組合併的部分主要參考自: 《JavaScript Modules: A beginner’s guide》。最後放的有連結,非常值得一看。

參考資料

相關文章