抖音前端團隊里的前端工程化
開篇語:抖音前端團隊通過幾年的實踐,積累了一些前端研發(fā)中的經(jīng)驗與理念,計劃通過比較輕松、簡潔的語言記錄下來分享給大家,希望對大家有幫助,有理解不妥的不吝賜教。
作為前端架構(gòu)的一部分,工程化是個永恒的話題,我們之所以老生常談這個事情,核心原因在于瀏覽器這個環(huán)境所支持的三種語言沒有在語言層面提供統(tǒng)一的模塊化支持,這使得大家都在考慮如何添加這些模塊特性,更好的支持好開發(fā)。而之所以模塊本身非常重要,是因為我們知道分而治之的道理。
以上這段話怎么理解呢,想象一下你所接觸的最基本語言 HTML、CSS、JS,對于這三種瀏覽器端標(biāo)準(zhǔn)語言在早前是不支持模塊引入的,如果你想對 JS 進行模塊拆分,你就得依靠 HTML 來串起來執(zhí)行,當(dāng)然 CSS 是具備這個能力的,HTML 在現(xiàn)在的標(biāo)準(zhǔn)中也逐步有了這樣的能力。HTML、CSS、JS 組合式的出現(xiàn),這又讓我們了解到,我們需要定義一類 UI 模塊,這里通常的叫法是 UI 組件。它們包含了?HTML、CSS、JS,我們通過工程化的手段來達(dá)到組件的工程化支持?,F(xiàn)在的瀏覽器已經(jīng)逐步支持 Web Component 來逐步磨平這些個不足。
為了實現(xiàn)分而治之,我們不得不實現(xiàn)一套組件化體系,這套體系一方面約束開發(fā),一方面完美地演繹分而治之的精髓。組件化體系解決了開發(fā)過程中復(fù)用的問題,當(dāng)然得想辦法解決運行的問題。比如當(dāng)我們通過組件的方式去完成一個站點的拆分工作,會發(fā)現(xiàn)拆分后的組件很多,達(dá)到了分而治之的目的但同時給運行帶來了不少的麻煩,這個麻煩我們是需要去解決的。所以我們的工程化體系里面有了打包(package)這個名詞,這個名詞幾乎是我們工程化體系中無法抹去的一個話題,也同樣是性能優(yōu)化體系里面一個非常重要的手段。
在組件化的體系里面,我們可以回想一下靜態(tài)編譯語言或者支持模塊化的語言對于模塊的一個特性,比如繼承、比如擴展、再比如依賴。同樣我們構(gòu)建的組件化體系中統(tǒng)一要解決這些問題。當(dāng)你在開發(fā)靜態(tài)編程語言 c 語言的時候,在編譯階段會有依賴錯誤產(chǎn)生,這時候你可能非常好解決這個問題,你可以按照錯誤去解決。另一個比較難以解決的問題是運行時發(fā)現(xiàn)有依賴的基礎(chǔ)庫錯誤,這時候就不好解決了。同樣在前端的組件化體系中也同樣有這樣的問題。
解決依賴是個非常麻煩的事情,一般我們會在構(gòu)建編譯的階段去搞定這個事情,如果靜態(tài)編譯想搞定這個事情需要有一套可識別的標(biāo)志。這就是剛才提到的組件化體系里面對于組件的引用的設(shè)定更準(zhǔn)確一些就叫語法(require、define、import),這也是組件化規(guī)范的一部分(比如 CommonJS、AMD、CMD、UMD),在當(dāng)下 ES6 默認(rèn)支持。一般都會使用路徑引入模塊。構(gòu)建時通過分析這些語法,來獲得依賴關(guān)系。依賴關(guān)系有兩個用途,一方面是在合并打包的時候作為參考,另一方面在瀏覽器端運行時提前去加載依賴;為了解決依賴問題,有很多名詞經(jīng)常出現(xiàn),比如依賴前置。
每個構(gòu)建工具在解決依賴這塊做法是不太一樣的,有些是從根節(jié)點搜索一棵樹,有些是先分析出依賴樹,最后再去根據(jù)收集到的依賴決策打包。實現(xiàn)方式有正則方式、也有 AST 方式,當(dāng)然這些都大同小異,如果感興趣也可以自己創(chuàng)造一種新的方式。
加載組件以及組件的依賴,在瀏覽器執(zhí)行階段可以有兩種方式,一種是同步加載,對于同步加載的情況一般的做法是在 HTML 上 script 標(biāo)簽進行外鏈,另外一種方式是通過 JS 動態(tài)生成 script 節(jié)點來外鏈或者直接 Ajax 或者 Fetch 請求代碼片段再執(zhí)行。這種方式也同樣適用于 CSS。加載工作主要是 Loader 來完成的,所以在組件化體系中有個 JS Loader 的概念。require.js、seajs 等都有這方面的支持。再往大里面延展 Loader 的功能,解決加載、處理依賴、保證執(zhí)行、保證作用域、保證各種方式的執(zhí)行代碼的都由一個 JS 框架完成。
分治是為了更好的開發(fā)、維護、調(diào)試,其最核心優(yōu)化的是研發(fā)的效率,合并(打包)最核心優(yōu)化的是瀏覽器端運行的效率。所以,我們工程化其中一個核心的目標(biāo)就是優(yōu)化效率。
開發(fā)階段,我們需要的是開發(fā)體驗的提升,希望用抽象的思維去看待一切,絕不去浪費時間在重復(fù)的事情上。這樣的情況下孕育了各種各樣的工具。比如腳手架、模擬服務(wù)、LiveReload、HMR 等等。
我們希望覆蓋整個研發(fā)流程,從方方面面進行效率的提升。這就是工具這個方向的使命。
站在瀏覽器的視角,運行快速、準(zhǔn)確是最終我們要達(dá)到的目標(biāo)。為了這個目標(biāo),根據(jù)網(wǎng)頁的特性,我們需要優(yōu)化整個鏈路,包括資源的加載、代碼的運行、瀏覽器渲染的研究、數(shù)據(jù)的獲取等等。積攢了非常多的經(jīng)驗后,把這些經(jīng)驗融合到我們的工程化體系里面,把這些優(yōu)化在業(yè)務(wù)開發(fā)中不可見,按照某種方式去做,然后就可以獲取到最佳性能,而不需要做額外的工作。這就是架構(gòu)本身的魅力,做到這一點需要有規(guī)范。
站在研發(fā)的視角,開發(fā)快、易于調(diào)試、快速上線這些是我們的目標(biāo),對于這些目標(biāo),我們同樣需要有一定的規(guī)范。規(guī)范我們代碼如何寫更易于維護,規(guī)范我們用什么語法更快速的能夠開發(fā)。
不管是組件化體系、運行時效率、研發(fā)效率都需要規(guī)范,這些規(guī)范整體歸納為我們的研發(fā)規(guī)范體系;除了規(guī)范體系,還有更多的基礎(chǔ)設(shè)施圍繞著效率展開,比如更好寫的語言、更高緯度的抽象、更高效的框架等等。
開發(fā)的場景里面,CSS 不好寫,我們就有了 Sass;JS 弱類型,我們就有了 Typescript;另外為了開發(fā)的更高效,我們有了一堆的框架,React、Vue、Angular 等等。
我們圍繞研發(fā)、瀏覽器兩個視角不停的在構(gòu)建我們的工程體系,希望于把一些繁雜的事情化簡、把一些固化的優(yōu)化對于研發(fā)透明。
固化的方式是有可能限制創(chuàng)造的,所以為了達(dá)到激發(fā)創(chuàng)造的可能,松耦合的工程架構(gòu)體系才更適用。松耦合的工程體系會造成更多的個性化,個性化就可能降低效率。所以做工程也是一個反復(fù)折中的過程,無法一蹴而就,一勞永逸。

