前端工程化指的是什么?
大家好,我是前端西瓜哥。今天來看看前端工程化是什么。
什么是前端工程化?
工程化,可以理解為使用一些方式,去改良然后提高行業(yè)中現(xiàn)有的步驟、設(shè)計(jì)、應(yīng)用方式。前端工程化,就是指對前端進(jìn)行一些流程的標(biāo)準(zhǔn)化,讓開發(fā)變得更有效率,且更好地做產(chǎn)品交付。
一開始,網(wǎng)頁頁面并不復(fù)雜,只是提供一些簡單的展示和交互的靜態(tài)頁面,甚至不需要后端。
后來需要根據(jù)不同用戶返回不同的頁面信息,此時(shí)我們會(huì)用后端讀取數(shù)據(jù),配合一些模板引擎,在后端拼接好內(nèi)容再返回,這就是所謂的服務(wù)端渲染(SSR)。
再后來,頁面變得非常復(fù)雜,于是出現(xiàn)了前后端分離,前端被單獨(dú)拎了出來,專門寫 html、css 和 js,變成了 單頁面應(yīng)用(SPA)。但復(fù)雜也帶來了很多問題,比如多個(gè)腳本的執(zhí)行時(shí)機(jī)不對、css 名沖突、文件過于臃腫、錯(cuò)誤的緩存導(dǎo)致沒能下載最新的資源,等前端復(fù)雜后出現(xiàn)的一系列問題。
隨著 Nodejs 的誕生,我們可以用 JS 去寫前端工具了。為了解決上面這些問題,前端界出現(xiàn)了一大堆的工具和框架:Gulp、Angular、babel、Sass、React、Vue、Webpack、Yarn、TypeScript、ESLint、Docker、k8s 等等。
一切都是為了讓前端的開發(fā)更工程化,也就是 不停地改良前端項(xiàng)目的開發(fā)流程,讓開發(fā)者能夠更高效地開發(fā)、更好地進(jìn)行團(tuán)隊(duì)協(xié)作、讓代碼的風(fēng)格標(biāo)準(zhǔn)化、對資源做壓縮以及懶加載、更好地交付部署等。
(當(dāng)然也因?yàn)楣ぞ哌^多,讓前端直呼 “學(xué)不動(dòng)了”)
我們通過四個(gè)維度來談?wù)勄岸斯こ袒恍┚唧w的細(xì)節(jié),分別是:
- 模塊化
- 組件化
- 規(guī)范化
- 自動(dòng)化
模塊化
模塊化,指的是將代碼功能做拆分,分成獨(dú)立地單能相互依賴的片段。
首先是 JS 的模塊化。
JS 一開始的職責(zé)是給網(wǎng)頁提供一些簡單的交互,所以語法相對簡單且不支持模塊化。隨著網(wǎng)頁的復(fù)雜,發(fā)現(xiàn)原來的組織方式帶來了很多問題,變得難以維護(hù)。
于是 CommonJS、AMD、ES Module 等模塊系統(tǒng)出現(xiàn)了。正統(tǒng)標(biāo)準(zhǔn)是 ES Module,通過 import 關(guān)鍵字引入模塊,通過 export 導(dǎo)出模塊。
JS 的模塊化將代碼做了拆分,解決了全局變量污染、依賴關(guān)系不清晰、多人協(xié)作不方便、腳本引入順序、單元測試等問題。
CSS 的模塊化。
?CSS 的第一個(gè)問題是比較難寫,比如不支持選擇器嵌套,對此我們可以用 CSS 預(yù)編譯器(比如 Less、Sass、Stylus)去寫一些更高級的語法,然后編譯成 CSS。
然后是就 命名沖突問題,一種舊的方案是 BEM,就是通過將 CSS 命名 在組件化的框架中,我們有很多方案,可以用 CSS in JS,也可以用 CSS Module,或者 Vue 特有的 CSS Scoped。
HTML 的模塊化。
html 通常是動(dòng)態(tài)的,在服務(wù)端我們會(huì)使用模板引擎(template),將得到的數(shù)據(jù)注入到占位符中。在后端 Nodejs,我們可以用 pug、handlebars、ejs 等。
前后端分離后,我們通常使用的是 Vue 的 template(類似 handlebars 語法)以及 React 的 JSX。
資源整合模塊化
不同類型的資源無法組織在一起,比如 JS 引擎能識別引入的 js 文件,但無法識別 css 文件。如果我們希望所有的資源都能組織再一起進(jìn)行管理,要分別管理一個(gè)個(gè)不同類型的資源要方便地多。
為了解決這個(gè)問題,webpack 誕生了。webpack 是一個(gè)模塊打包器,能夠?qū)⑷魏钨Y源轉(zhuǎn)換為 js 代碼進(jìn)行導(dǎo)入。比如圖片,它可以先變成一個(gè)靜態(tài)資源服務(wù)的一個(gè)資源,然后在 js 文件 import 的時(shí)候在轉(zhuǎn)換為一個(gè) url 字符串,或者直接就變成一個(gè) base64 字符串。
這些需要使用到一些 loader(加載器)。webpack 是一個(gè)框架,使用者需要根據(jù)需求,添加一些 loader,去識別不同的文件,轉(zhuǎn)化成 JS 代碼導(dǎo)入。
此外還有 plugin(插件),在這整個(gè)流程中做一些處理,比如將導(dǎo)出的 JS 文件插入到 HTML 模板中,或是進(jìn)行代碼的壓縮等等。
組件化
組件化是 UI 層面上的更細(xì)粒度的拆分,一種類似 div 等原生元素的 “自定義元素”。
組件有自己的 HTML、CSS 和 JS,同時(shí)有自己的狀態(tài),并支持嵌入到其他組件中并接受外部的數(shù)據(jù),可以進(jìn)行復(fù)用。組件化可以看作是 UI 層組織方式的一種模塊化。
目前主流的 React 和 Vue 前端框架都是基于組件的。
原本的以資源類型為單位進(jìn)行組織的管理(所有 JS 文件放一個(gè)文件夾、CSS 同理),其實(shí)維護(hù)起來比較困難,也不好復(fù)用,組件化的構(gòu)想是以視覺為單位進(jìn)行拆分,做了結(jié)構(gòu)、樣式、腳本的組裝,抽象出一個(gè) “新的元素”。
組件已經(jīng)是前端開發(fā)的基石了,是一種比較合理的抽象。
規(guī)范化
然后就是前端代碼的規(guī)范。規(guī)范是很重要的,能讓代碼能夠?qū)懙酶菀赘_,避免一些不必要的錯(cuò)誤。
能想到的規(guī)范有:
- 目錄結(jié)構(gòu)規(guī)定。
- 代碼風(fēng)格(包括 JS、HTML、CSS)。
- 注釋規(guī)范。
- commit message 規(guī)范。
- git 工作流規(guī)范。
- Code Review。
- 請求接口規(guī)范。
有些規(guī)范不太能用工具進(jìn)行限制,比如目錄結(jié)構(gòu)。但有些規(guī)范是可以利用到工具的,下面來說說有哪些和規(guī)范相關(guān)的工具。
首先是重磅級的 TypeScript。
TS 是有類型的 JS,是 JS 的超集。通過類型,我們可以預(yù)測變量的行為,比如一個(gè)布爾值類型是不能被作為函數(shù)調(diào)用的,可能為 undefined 的值需要進(jìn)行類型收窄后丟棄 undefined 的可能性才能使用。
TS 越來越流行,是因?yàn)樵诖笮晚?xiàng)目中,類型系統(tǒng)是非常重要的,能夠避免大量的類型錯(cuò)誤。TS 讓代碼即文檔,降低程序員理解代碼的成本。
如果工具層就能做規(guī)范,就不應(yīng)該用文檔去說明,人不是絕對正確的機(jī)器,但工具可以。
然后是 ESLint。
ESLint 能夠檢測 JS 代碼中的錯(cuò)誤,主要兩個(gè)方面:
- 代碼質(zhì)量,比如你不能聲明一個(gè)沒有被使用的變量。
- 代碼風(fēng)格,比如字符串引號必須用單引號。
ESLint 有助于統(tǒng)一團(tuán)隊(duì)的風(fēng)格,讓代碼看起來基本像是一個(gè)人寫的,避免出現(xiàn)字符串一會(huì)用單引號,一會(huì)用雙引號,變量命名一會(huì)用下劃線風(fēng)格,一會(huì)用駝峰風(fēng)格,這種讓強(qiáng)迫癥抓狂的情況。
然后是 commit message。commit message 我們不希望看到像是 “修復(fù)了一些 bug” 這種不夠具體的寫法,希望具有一定的結(jié)構(gòu),比如 "fix(工作臺): 修復(fù)了卡片不能滾動(dòng)的問題"。
對此我們可以用 commitlint 的命令行工具去判斷是否符合特定風(fēng)格。
當(dāng)然還需要確保團(tuán)隊(duì)成員是使用了這些工具的,我們可以用保存后自動(dòng)格式化(需要配合編輯器和對應(yīng)插件)。然后最重要的就是 git hook,可以在本地 commit 時(shí)先對 staged 中的文件做風(fēng)格校驗(yàn)和格式化,然后再檢查 commit messge 風(fēng)格是。如果不對,本地 commit 會(huì)失敗。對應(yīng)的工具是 husky。
自動(dòng)化
重復(fù)的可以自動(dòng)化的流程化工作,應(yīng)該盡量去自動(dòng)化。讓人去做,對人是一種折磨,然后也不能保證質(zhì)量,因?yàn)橥ǔA鞒桃埠軓?fù)雜,即使是簡單,做多了也容易錯(cuò)。
一個(gè)小概率事件只要做的次數(shù)足夠多,它就會(huì)變成大概率事件。這也是為什么分布式系統(tǒng)中容錯(cuò)機(jī)制是非常重要的原因。
首先想到的自然是 CI/CD(持續(xù)集成和持續(xù)交付/部署)。我們將代碼提交到遠(yuǎn)端倉庫時(shí),或者是給一個(gè)分支打了 tag 后,能夠觸發(fā)一些腳本,將我們的項(xiàng)目代碼做打包編譯,發(fā)布成制品,然后發(fā)布到生產(chǎn)環(huán)境。這些都是自動(dòng)化的,流程化的。
CI/CD 工具有很多:Jenkins(比較古老了)、GitLab CI/CD、GitHub Action、Docker(發(fā)布制品) 和 k8s(容器編排)等。
前面說的 git hook,在本地 commit 時(shí)進(jìn)行一些操作,也算是一種簡單的自動(dòng)化。
打包工具
前端工程化的核心是打包工具。
打包工具需要支持的 幾種重要的能力:
- 代碼分割:指的將代碼劃分為可以按需 / 同時(shí)加載的多個(gè)bundles 或組件的能力。比如動(dòng)態(tài) import、提取公共依賴模塊代碼、多個(gè)入口文件沒有重復(fù)代碼、支持 ESM 的值引用模擬等。
- 哈希:資源更新時(shí)做哈希,防止資源緩存。哈希分很多種,比如文件路徑名哈希、內(nèi)容哈希等。
- 包引入:ES Module、CommonJS 以及從 node_modules 目錄引入包的支持。
- 非 JS 資源:導(dǎo)入非 JS 資源的支持,像是 webpack 需要使用各種 loader 來支持,有些打包工具是內(nèi)置的。
- 輸出的模塊格式:支持導(dǎo)出為 ES Module、CommonJS 等模塊。
- 轉(zhuǎn)換處理:比如對圖片壓縮、代碼壓縮、JS 版本降低等,在 webpack 中是使用 plugins 來實(shí)現(xiàn)的。
其他
還有一些零散的可以提高效率的工具。
- babel:開發(fā)時(shí)使用高版本的 ES 語言特性,然后生產(chǎn)環(huán)境用 babel 轉(zhuǎn)換為低版本的兼容性好的 ES5。打包工具內(nèi)部其實(shí)使用了 babel。
- tsc:tsc(TS 編譯工具) 在支持 TS 的前提下,也支持編譯為 JS 低版本。
- polyfill:低版本的 ES5,想要使用一些新的 API,可以自己寫函數(shù)去模擬,這就是 polyfill,通常我們會(huì)使用 core.js 庫,但有些語言層面上的新特性就不能用 polyfill。
- monorepo:將多個(gè)項(xiàng)目放到一個(gè) git 倉庫下,方便包的依賴共用和維護(hù)。
- 異常監(jiān)控:當(dāng)前端報(bào)錯(cuò)時(shí),將相關(guān)信息提交到異常監(jiān)控服務(wù),比如 sentry,通常配合 sourcemap 精確定位源碼中的錯(cuò)誤位置。
- 制品庫:使用 Nexus 來部署自己的私有制品庫,支持各種制品庫。比如發(fā)布 docker 包、npm 包等,配合發(fā)版部署;
- VSCode Snippet:自定義 VSCode 編輯器的代碼片段,可以快速生成一些預(yù)置好的代碼模板,減少一些模板代碼的書寫??梢詺w為自動(dòng)化。
- Mock:前端在后端確認(rèn)返回?cái)?shù)據(jù)結(jié)構(gòu)后完成接口前,可以通過模擬虛假數(shù)據(jù)進(jìn)行調(diào)試開發(fā),比如 yapi 平臺就是除了支持接口文檔,還提供 mock 功能。
- 單元測試:以模塊(比如組件)為單位進(jìn)行測試,保證代碼邏輯符合預(yù)期。單元測試通常比較耗時(shí),會(huì)在提交到遠(yuǎn)端時(shí)或合并到主分支時(shí)進(jìn)行。流行的單元測試庫有 Jest。
- 熱重載:因?yàn)槊看胃拇a都要編譯,如果整個(gè)項(xiàng)目都要重新編譯開發(fā)體驗(yàn)很差,可以用熱重載只編譯被修改的模塊。
- 組件庫文檔:可以用 stroybook。如果是 vue 組件,可以考慮用 Vue Press。
- Tree shaking:丟掉一些引入了但沒有使用的模塊。
結(jié)尾
簡單來說,前端工程化是對前端開發(fā)流程的改良,是效率工具。