在筆者所在的前端開發團隊中,採用先後端分離方案是在整個業務線穩定後進行的。業務前期主要採用後端套模板的方式,現階段是採用基於Vue
的單頁開發模式。javascript
這會出現一種情形,產品在不斷迭代過程當中,因爲以前線上前端代碼並不是工程化項目,後面新需求可能是另起Vue
項目來進行編碼上線,前端在整個業務線上沒有統一的工程,項目過多分佈散亂而且不易優化管理。(項目指根據新需求建立的項目代碼,工程指一套代碼下包含多個項目。後文以此約定。)針對這種狀況下咱們作出一些嘗試,將目前存在的多個項目整合成一個工程,統一入口。css
固然咱們更但願整合成統一工程後能夠實現後續新需求接入無痛化。下文則主要圍繞項目整合過程當中遇到的些許問題,分享一些可行解決方案。簡要從幾個方向,代碼層級化分、組件處理、路由處理、數據狀態維護、其餘優化等來簡述。html
在前端開發中咱們首先會面對如何將代碼及靜態資源劃分目錄層級放置問題。好比在前端洪荒時代一般會以img、css、js
命名不一樣目錄。那麼在結合Vue
相關技術棧以及多項目整合場景下,如何劃分目錄才能保證代碼層次合理呢?前端
談到這個問題的時候,咱們能夠首先思考下整個工程具體須要哪些相關功能。在拋開具體源碼內部結構狀況下,主要有構建腳本、構建配置、Mock數據、項目文檔、項目源碼等。在不結合具體技術棧的狀況下,這也是前端在工程化方面大體目錄。vue
具體到項目源碼內部目錄結構,按照不一樣功能模塊,主要作了如下層次劃分:java
以項目src源碼下組件相關目錄爲例,以下圖所示:webpack
├── pages // 路由組件目錄
│ ├── README.md
│ ├── base // 全局基礎路由組件目錄
│ │ └── SuccessPage.vue
│ ├── period_process // 項目A路由級別組件目錄
│ │ ├── ChooseTime.vue // 項目A選擇時間路由組件
│ │ ├── xxx
│ └── period_suborder // 項目B路由級別組件目錄
│ └── xxx
|
├── components // 子組件目錄
│ ├── README.md
│ ├── base // 全局基礎子組件目錄
│ │ ├── AddressInfo.vue
│ │ └── xxx
│ ├── period_process // 項目A子組件目錄
│ │ ├── base // 項目基礎A子組件目錄
│ │ │ ├── Picker.vue
│ │ │ └── xxx
│ │ ├── choose_time // 項目A選擇時間路由組件子組件目錄
│ │ │ ├── Cleaner.vue
│ │ │ └── xxx
│ │ ├── xxx
│ └── period_suborder // 項目B子組件目錄
│ └── xxx
複製代碼
固然項目目錄結構不是一層不變的,可根據業務場景及技術棧靈活處理。但原則上避免一個文件從頭寫到尾出現綿長代碼狀況,形成後續迭代可閱讀性差、很差維護問題。良好的代碼層次能夠簡述爲將相同功能模塊聚合同一目錄並拆分出獨立文件。web
談及組件部分,組件拆分粒度永遠是一個繞不開的話題。首先大的方面分爲路由組件(頁面組件)和相應頁面子組件。路由組件爲配置路由時組件,組件內部拆分不一樣子組件進行引用。路由組件及子組件分別拆分相應業務組件和基礎組件。vue-router
頁面組件拆分過程當中,咱們採用將相關功能模塊代碼拆分爲子組件。將頁面劃分若干子組件(功能模塊),每一個子組件完成一個子功能。以下圖所示:後端
在組件劃分時,咱們將子組件拆分業務組件和基礎組件。在項目整合的過程當中,遇到業務子組件因被後續多個項目使用須要提高爲基礎組件狀況。但起初編碼過程當中僅考慮做爲業務子組件,內部數據源多依賴Vuex
,在將其提高爲基礎組件時須要作大量工做將數據來源改成props
對象,修改數據源操做改成emit
觸發事件機制。
這裏更好的處理方式則是但願在編寫子組件時,內部數據源儘量依賴於父組件傳遞的props
對象。將須要修改數據源行爲經過emit
方式提高至父組件內操做。
單頁web應用在處理不一樣view時提出前端router概念。對應單一項目需求時,咱們能夠很從容設置默認路由入口來解決。可是對於多入口的多項目工程,則須要一些思量。同時因爲筆者所處公司APP在處理URL跳轉時默認不帶hash,那麼在URL訪問上則沒有辦法經過附加hash路由來跳轉相應視圖。
咱們採用的解決方式是,在URl訪問時不攜帶hash,後端同窗會在不一樣項目的入口html
文件中放置PAGE_TYPE
變量,前端根據PAGE_TYPE
變量跳轉相應路由組件。PAGE_TYPE
變量對應於當前待訪問的路由。經過結合後端在頁面中渲染的路由標誌量,解決訪問時路徑問題。
在此基礎上,還須要考慮頁面刷新以及跳轉外鏈後退狀況。在非入口路由頁面刷新會根據PAGE_TYPE
變量重置進入入口路由頁。這種狀況須要判斷當前URL是否存在hash路由標誌,優先獲取當前連接hash值進行跳轉。具體僞代碼以下所示:
let routeType = window.PAGE_TYPE // 獲取初始化路由變量
let routeName = getHashRouter() // 獲取當前路由名稱
if (routeName) {
router.push({path: `/${routeName}`})
} else {
switch (parseInt(routeType)) {
case 0:
router.push({path: '/index'})
break
case 1:
router.push({path: '/projectB'})
break
default:
router.push({path: '/index'}) // 默認路由入口
}
}
複製代碼
另一個值得考慮的問題是,隨着項目不斷增長在打包構建應用時,js文件會變得愈來愈大,影響頁面加載速度。將非入口路由組件異步加載是一種比較高效的解決方案。結合Vue
的異步組件和Webpack
的代碼分割(code splitting
)特性能夠輕鬆實現路由組件懶加載。具體語法可參考vue-router
官方文檔。
非入口路由組件異步加載,可減小首次加載JS文件大小。但隨之而來的問題是,假如用戶選擇點擊按鈕進行路由跳轉時,須要異步獲取JS文件,等待異步組件加載完成後再跳轉。特別在跳轉以前還須要異步調用接口校驗,用戶等待時間無疑增長。咱們更但願能夠在用戶空閒時間預加載後續跳轉的異步組件。
具體預加載操做能夠在組件生命週期mounted
中手動觸發後續異步組件加載。還可將預加載操做聚合成mixin
文件,註冊成全局mixin
。埋點數據顯示後續路由組件跳轉時間約在300ms左右,屬於秒開範圍。示例以下:
mounted () {
// 預加載後續異步路由組件
import(/* webpackChunkName: 'chooseTime' */ '@/pages/period_process/ChooseTime.vue')
// 或使用 webpack 特定的 require.ensure 語法
// require.ensure(['@/pages/period_process/ChooseTime.vue'], null, 'chooseTime')
}
複製代碼
將非入口路由組件異步加載,並在用戶空閒時間實現預加載接下來路由組件。下降用戶等待時間,用戶體驗天然也就更好。
這裏說到的路由跳轉效果指在不一樣路由組件跳轉時所添加的過渡效果。Vue
默認的router-view
跳轉不存在動效,略感生硬。爲router-view
添加transition
是不錯的選擇。
不過在處理transition
過程當中,起初是將過渡效果添加至路由組件根節點上,但在某些安卓機型下跳轉會出現明顯的閃屏現象。解決方式是將transition
組件移至router-view
組件外統一處理。
能夠預料到的數據交互行爲主要有:
另外還但願全部數據層model和異步接口能夠抽取進行統一維護,爲此咱們引入了Vuex
。
Vuex
爲Vue
應用程序的狀態管理模式,採用集中式存儲管理應用的全部組件狀態。未引入Vuex
下常見的問題多個視圖依賴與同一狀態,多個視圖須要變動同一狀態。兩種情形下多經過組件間參數傳遞或採用事件同步狀態。引入Vuex
後,將組件共享狀態抽取成單例模式管理。定義並約定遵照必定的狀態管理的規則,代碼結構化更清晰更容易維護。固然若是不開發複雜單頁應用,使用Vuex
多是繁瑣冗餘。
除此咱們還須要考慮的是,因爲Vuex
數據狀態是存儲在JS變量中的,當頁面刷新時整個應用狀態會所有丟失。須要在state、mutations
讀取、存儲中添加本地持久化操做。封裝本地持久化存儲層Cache.js
,可選sessionStorage、localStorage、indexedDB
存儲方式。依據業務情形在mutations
文件中選擇相應方式作本地持久化操做。
Vuex + Cache
方式作數據狀態維護,並將相應代碼拆分獨立數據層,減小與業務代碼耦合程度。業務流程中只須要經過mapState、mapActions
方式獲取相應數據狀態或更新相應數據狀態。
多項目整合在一塊兒,各個項目中state
不免會出現變量名稱衝突,多個狀態變量相互污染現象。同時actions、mutations
操做也都暴露在全局狀態下。隨着項目不斷整合加入,這將會成爲一個定時炸彈。
很開森的是Vuex
有相應的解決方案。Vuex
中提出模塊(module)和命名空間(namespace)概念。Vuex
容許咱們將store
分割成模塊,而且可經過添加namespaced: true
將其變爲命名空間模塊。多個項目使用各自state
對象,數據耦合程度及被污染的可能性下降。
Vuex
還能夠作哪些好玩的事情?按照Vuex
所約定數據狀態存儲以及修改的規則,很容易將數據層按照相應層次拆分出來。這時異步請求則能夠進行統一聚合,封裝成全局狀態下的fetchData
action方法,在統一的異步請求中咱們作了如下工做:
如此下來,咱們業務組件再也不須要考慮異步請求中的重複提交、網絡異常等事情。單Vue
文件組件中中僅聚焦業程邏輯的實現。
解釋下,這裏無關邏輯是指與業務關聯性密切性不大的代碼。好比,前端作router
跳轉不一樣視圖時,須要考慮跳轉到相應視圖下設置當前視圖的頁面title
或發送當前視圖的pv
埋點。每一個路由級別組件幾乎都會寫相似於這些與業務邏輯無關的代碼。那麼如何提取拆分呢?
這裏提一個混合(mixin
)概念,mixin
是一種分發組件中可複用功能的靈活方式。經過mixins
或Vue.mixin()
語法接受一個混合對象或混合對象數組。混合對象能夠包含任意組件選項,另外同名鉤子函數將混合爲數組,混合對象的鉤子將在組件鉤子前調用。
結合Vue
中的mixin
語法,咱們能夠很容易作到將基礎與業務無關代碼拆分紅不一樣mixin
文件,經過Vue.mixin()
註冊爲全局混合對象。在不一樣mixin
文件編寫相應的組件生命週期函數作預加載組件、設置頁面title、發送PV等操做。
一言簡述之,在咱們整合項目過程當中發現以前的構建腳本在處理雪碧圖合併的過程當中將多個項目圖片合併成一張。這就會出現用戶訪問項目A過程當中,會將整個工程的雪碧圖資源下載,佔用流量並形成資源浪費。那如何分項目合併雪碧圖呢?
這裏咱們使用webpack-spritesmith
模塊作雪碧圖合併。經過實例化webpack-spritesmith
對象,傳遞自定義模板,分別構建出css、scss
文件(具體可參考官方文檔)。其實分項目構建不一樣雪碧圖與webpack
構建多個html
頁面作法相似。在webpack
的plugins
配置數組中push
多個webpack-spritesmith
實例,不一樣實例分別構建不一樣項目下的雪碧圖。這時不一樣項目業務組件即可分別引用相應的雪碧圖樣式文件。
將多個相關需求的項目整合到單一工程的過程當中,從前期詳細設計到後面多個項目接入上線,一直在踩坑填坑。本文也是針對一些咱們遇到的比較典型的問題拿出來分享。
在前端愈來愈追求工程化的今天,如何在工程化的基礎上將項目作到層次清晰、代碼簡潔、耦合更低、性能更優則是咱們要去思考的方向。
最後本文主要聚焦於基於Vue
的多個相關項目整合統一工程實踐解決方案。
倉促成文,若有錯誤,措辭不當,敬請斧正 :)