先後端分離和模塊化-58到家微信首頁重構之路

微信錢包內的58到家全新首頁已經上線,感興趣的同窗們能夠在微信中打開「個人->錢包->58到家」查看。javascript

58到家全新首頁提出重構主要是爲了解決如下問題:css

  1. 每一個城市開通的服務項目不一樣,有些內容是寫死在tpl中,維護很是頭疼;
  2. 開通新服務或者某些UI調整(好比更換服務項的圖片形成更改雪碧圖)時必須走代碼上線流程;
  3. 原有的前端切圖、後端寫邏輯的開發模式形成開發週期拉長和上線流程繁瑣;
  4. 原有配置後臺操做複雜,且可配置細節不完善;
  5. 首頁加載速度太慢,用戶體驗欠佳。

58到家目前兩年左右的發展期,整個技術生態還不完善。以上的問題有的是因爲創業初期遺留的歷史緣由形成,好比代碼寫死和粗糙的配置後臺;而有的問題是由落後的開發模式和協做模式形成的,好比先後端分工不明確、首頁加載速度慢。html

基於上文提到的問題,重構從如下幾方面入手:前端

  1. 完善配置後臺,細化可配置項;
  2. 數據驅動UI,輕量化tpl,內容更新無需上線流程;
  3. 先後端分離,縮短開發週期,簡化上線流程;
  4. 模塊化開發,提升加載速度,同事加強代碼的可維護性。

配置後臺能夠理解爲一個簡易的CMS系統,配置的內容是一些量化的字段,好比圖片地址、連接、價錢等等。此項目中本人並不負責配置後臺的開發,因此再也不班門弄斧。vue

下面詳細描述重構過程當中前端的解決方案。java

1. 技術選型

根據上文提到的問題,此項目中前端的技術選型以下:webpack

  • 客戶端(瀏覽器)
    • 使用Vue做爲渲染框架(數據驅動UI);
    • 圖片懶加載使用Vue-lazyload實現;
    • 模塊化方案使用CommonJS;
    • 由於首頁沒有不少的用戶交互功能,大部分是連接跳轉,因此不使用第三方的touch event工具;
  • 開發環境
    • 使用58到家前端工程框架boi做爲開發和構建工具;

看到以上的技術選型,可能會有讀者疑惑:不就是一個前端模板+模塊化方案嗎,有什麼值得介紹呢?git

首先,以上的技術選型的背景以下:github

  1. 目前58到家FE團隊統一使用vue做爲開發框架,組件易複用;
  2. 這次重構後的58到家首頁並不是SPA,選用vue的另一個緣由是爲了後續的SPA化作預備;
  3. 客戶端渲染html的缺點是首次進入頁面加載較慢,但利用瀏覽器緩存機制能夠另再次進入頁面的加載時間大大縮短;
  4. 選用CommonJS實現按需加載(load on demand),首屏之外的內容在首屏渲染完成以後加載;
  5. boi是58到家前端工程框架,以webpack爲構建內核,選用CommonJS另外一個緣由是webpack的原生支持。

2. 先後端分離方案

目前58到家的先後端協做模式仍然很原始,本次重構採用的先後端分離方案並不是是最優解,只能算是一種折中的過渡方案。總結有如下幾點:web

  1. 初始tpl中包含如下部分:
    • js、css引用;
    • 頁面初始數據;
    • vue組件容器;
    • 統計用初始數據。
  2. 客戶端採用vue做爲渲染html;
  3. js和css更新時,FE獨立部署靜態文件,RD須要將url更新時間戳;

下面分別簡單描述以上的幾點。

2.1 輕量化tpl

tpl的內容以下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
        <title>58到家</title>
        <link rel="stylesheet" href = "http://static.daojia.com/assets/project/wx_index/style/main.wx-index.css?2016082001">
    </head>
    <body>
        <div class="window">
            <app :data="data"></app>
        </div>
        <script type="text/javascript">
            // initial page data
            var pageData = {};
        </script>
        <script type="text/javascript">
            // for traker
            var bi_params = {pagetype:'activity'};
        </script>
        <script src="https://static.daojia.com/bi/buried_point/js/tracker.js"></script>
        <script src='http://static.daojia.com/assets/common/js/zepto.min.js'></script>
        <script src='http://static.daojia.com/assets/common/js/vue.min.js'></script>
        <script src = "http://static.daojia.com/assets/project/wx_index/js/main.wx-index.js?2016082001"></script>
    </body>
</html>
  • tracker.jszepto.min.jsvue.min.js是依賴的第三方文件,全局變量bi_params是bi統一用的初始字段;

  • <app :data="data"></app>是vue組件的容器;
  • tpl文件由RD維護,以上提到的兩點是固定不變的,不須要維護。RD須要維護的包括:
    1. main.wx-index.jsmain.wx-index.css時間戳
    2. 全局變量pageData。這是首屏的初始數據,之因此選擇以全局變量的方式暴露,而不是請求api,是爲了減小一次http請求,儘快渲染首屏。

tpl輕量化是爲了減小FE和RD的耦合,固然最佳的方式是tpl交由FE維護,可是目前58到家的開發模式並不適合。因此採用了折中的過渡方案。

使用url query做爲js和css文件的緩存策略也並不是最優解,理想的方案是使用hash指紋。可是hash指紋須要FE在編譯完成以後將hash值告知RD,而時間戳能夠任意修改爲與當前不一樣的值便可,減輕了溝通成本和失誤率。

2.2 客戶端渲染

選擇客戶端渲染有如下幾個優勢:

  1. tpl輕量化,先後端解耦;
  2. 初始html數據量很是小,可以快速到達客戶端。採用一些loading交互儘快給用戶視覺反饋;
  3. js文件初次請求以後緩存到本地,只要不更新版本,後續每次進入頁面後初次請求的數據就只有少許的html數據;
  4. 減少服務器(解析模板)壓力;

固然客戶端渲染也有一些缺點,好比:

  1. 性能比較差的設備執行渲染過程吃力,不過按照目前手機的迭代速度,這一點基本能夠忽略不計;
  2. SEO不友好。可是這個項目是微信錢包的服務,並不直接提供外部瀏覽器入口,SEO能夠不考慮。

具體到客戶端渲染的技術選型,其實從實現功能上來說隨意選用一種js模板工具便可,好比artTemplate。最終選擇vue的緣由有如下幾點:

  1. 數據驅動UI的方式利於編寫清晰的邏輯;
  2. 爲後續迭代作預備。後期有計劃將整站SPA化,vue+vuex是比較不錯的技術選型;
  3. 58到家FE團隊統一使用vue,部分業務組件可複用;

2.3 更新和緩存策略

這次重構採用的緩存策略仍然比較原始,好比前文提到的url加query的方式。這也是後續有待優化的一個重點。

3. 模塊化方案

3.1 客戶端模塊化方案

58到家首頁的內容很是多,大部分尺寸的手機須要三屏才能加載完成。一次性加載的用戶體驗確定不太順暢,按照主流的手機尺寸,將整站分紅三部分:首屏+次屏+尾屏。基本的加載流程以下圖:

簡單歸納以下:

  1. 用戶進入頁面後,客戶端發起第一次請求,服務端返回包含首屏json數據的html文檔;
  2. main.wx-index.js根據首屏json渲染首屏;
  3. 首屏渲染完畢以後或者用戶scroll到頁面底部觸發次屏js文件wx-index.themes.js的加載;
  4. wx-index.themes.js加載成功後發起jsonp請求次屏和尾屏數據;
  5. 渲染次屏;
  6. 次屏渲染完成以後或者用戶scroll到頁面底部觸發尾屏js文件wx-index.tail.js的加載;
  7. wx-index.tail.js加載成功後渲染尾屏;
  8. 至此,流程結束。

次屏的wx-index.themes.js和尾屏的wx-index.tail.js是按需加載的,是爲了減小首屏的請求數和數據體積。

按需加載功能使用require.ensureAPI實現。之因此選擇使用它有如下幾點考慮:

  1. 58到家前端工程框架boi構建內核基於webpack,webpack runtime內置require.ensureAPI的支持,不須要額外的模塊化工具
  2. AMD方案(好比require.js)的按需加載模塊不能定義模塊名稱(wx-index.themes.jsthemeswx-index.tail.jstail),而require.ensure能夠定義模塊名稱,使文件名更語義化;

下面簡單介紹一下如何結合vue和require.ensure實現按需加載和動態組件。

3.1.1 vue結合require.ensure實現按需加載和動態組件

回顧上文的tpl代碼能夠看出,頁面總體是一個vue組件。頂層組件是<app></app>。首屏組件是第一級子組件,次屏是第二級子組件,尾屏是第三級子組件。總體結構以下圖:

vue實現按需加載動態組件要考慮如下幾點:

  1. 組件容器位置;
  2. 組件數據如何傳遞。

對比上圖能夠看出子組件容器的位置:

  1. Themes組件做爲第一級子組件App的一個子組件,容器位置以下代碼:
    <div class="main"> <!-- activity banner --> <wx-activity v-if="data_activity" :data="data_activity"></wx-activity> <!-- nav --> <wx-nav v-if="data_nav&&data_nav.length!==0" :data="data_nav"></wx-nav> <!-- headline --> <wx-headline v-if="data_headline&&data_headline.length!==0" :data="data_headline"></wx-headline> <!-- service --> <wx-service v-if="data_service" :data="data_service" :test="test"></wx-service> <!-- fresh --> <wx-fresh v-if="data_fresh" :data="data_fresh"></wx-fresh> <!-- banner --> <wx-banner v-if="data_banner&&data_banner.length!==0" :data="data_banner"></wx-banner> <!-- themes --> <div class="wx-index__themes"> <themes></themes> </div> <wx-footer :notice="data.showReddot"></wx-footer> </div>
  2. Tails組件做爲第二級子組件Themes的一個子組件,容器位置以下代碼:
    <template> <template v-for="theme in data.themes"> <slider v-if="theme.type==='slider'" :data="theme"></slider> <single-slider v-if="theme.type==='singleSlider'" :data="theme"></single-slider> <list v-if="theme.type==='list'" :data="theme"></list> <grid v-if="theme.type==='grid'" :data="theme"></grid> </template> <div class="tail"> <tail></tail> </div> </template>

wx-index.themes.js加載成功,在渲染Themes組件以前須要請求次屏的數據,jsonp請求放在vue組件的activate鉤子函數內:

activate: function(done) {
        let _this = this;
        let _url = '/home/ajaxGetSecondIndexPage';
        let _cityId = pageData.cityId||$.fn.cookie('comm_cityid');
        let _openId = pageData.openId||'';

        $.ajax({
            url: _url,
            data: {
                cityId: _cityId,
                openId: _openId
            },
            dataType: 'jsonp',
            success: function(res){
                if(!res||!res.data){
                    return;
                }
                _this.data = Object.assign({},res.data);
                // 將底部固定模塊的數據寫入全局變量,以便懶加載所需
                window.dj_index_data_tail = Object.assign({},{
                    layidle: _this.data.layidle,
                    recommend: _this.data.recommend
                });
            },
            complete: function(){
                done();
            }
        });
    }

vue組件在activate鉤子函數返回done()以後纔會繼續執行後續工做。

請求成功以後將返回的數據賦值給vue組件的data,而後vue根據data渲染UI。

上述代碼有一點須要注意。你們看到代碼將一些數據賦值給了全局變量window.dj_index_data_tail,這些數據是尾屏的數據。因爲尾屏的數據量比較小,因此與次屏的數據合併成一個API。這個全局變量是爲了尾屏的Tail組件渲染使用。這就是上文提到的「組件數據如何傳遞」。

使用全局變量傳遞數據的方式當然不是很優雅,可是不失爲一個適合快速開發的方案。這也是後續迭代的優化點之一。

次屏渲染完畢以後觸發尾屏的加載,這個行爲實在Themes組件的ready鉤子函數內進行,以下:

ready: function(){
    let loadTail = function() {
        if(!window.isTailLoaded){
            // 主題推薦位渲染完成以後加載底部模塊
            require.ensure([], function(require) {
                require("../../_tail.js");
            }, 'tail');
            window.isTailLoaded = true;
        }
    };
    loadTail();
}

因爲以前將Tail組件的數據儲存在全局變量中,Tail組件的activate鉤子函數內能夠直接讀取次全局變量:

activate: function(done){
    this.data_layidle = window.dj_index_data_tail.layidle&&Object.assign({},window.dj_index_data_tail.layidle);
    this.data_rec = window.dj_index_data_tail.layidle&&Object.assign({},window.dj_index_data_tail.recommend);
    done();
}

以上,便完成了整個頁面的按需加載流程。固然,每種方案都不是最優解,但都是適用於目前狀態的一種比較好的方案,後續迭代中持續優化。

3.2 開發環境模塊化方案

開發環境的模塊化方案比較隨意,藉助於boi框架中的babel模塊,能夠將新規範的語法編譯爲瀏覽器適用的語法。

這次重構的開發環境的模塊化開發使用的是ES6 Modules,語法簡潔易懂,而且開發環境沒有加載動態模塊的需求,靜態的ES6 Modules徹底適合。

插播廣告:58到家前端工程框架boi支持多種模塊化方案,包括ES6 Modules、CommenJS和AMD。

4. 後續迭代需求

依前文所述,本次重構中的仍然有不少問題,這些問題是後續迭代中急需解決的。總結以下:

  • 工做流程優化
    1. 進一步解耦tpl層,實現先後端徹底分離;
  • 代碼優化
    1. 優化緩存策略,使用hash指紋代替url query;
    2. 優化vue組件間數據傳遞;
    3. 後臺可配置化引發的零散圖片太多的問題;
  • 用戶體驗優化
    1. 添加初始loading效果,加強用戶體驗;

5. 總結

58到家微信錢包重構項目告一段落,其中採用的諸多解決方案中有好的也有很差的。不過整體來講,這次重構中58到家技術團隊向前端工程化、先後端分離邁出了標誌性的一步。後續須要作的事情還不少,不論從項目自己,仍是從團隊總體架構的角度,都有很大的進步空間。也歡迎各位同行提出意見和建議。

相關文章
相關標籤/搜索