本文將圍繞我半年來在移動前端工程化作的一些工做作的總結,主要從 localstorage緩存和版本號管理 , 模塊化 , 靜態資源渲染方式 三個方面總結手機百度前端一年內沉澱的解決方案,但願對你們移動開發有所幫助。php
可能由於以前項目節奏緊,人力不足緣由,一部分phper承擔了前端的工做,因而暴漏了一些問題。css
從第一次在廠子寫代碼開始,就被前輩告訴移動頁面,因此的靜態資源都要內嵌,即寫在script
和style
內,這樣的好處是,網絡狀況很差的時候,減小http請求。由於2G等網絡不穩定的狀況下,多開一個http請求,對手機資源消耗是巨大的,好比咱們在手機信號很差的地方,訪問網絡,耗電量會急劇增高。html
可是隨着3G,甚至4G的普及,實際統計顯示,手機百度上2G用戶不到30%,因此上面提到的這種一刀切
的方案是不妥的。前端
第二個問題是沒有規範和模塊化的問題。你們寫碼都是 意識流 ,除了都是用zepto.js
以外,沒有沉澱下模塊。碰到之前寫過的代碼,都是ctrl+c
+ ctrl+v
。這種粗放的方式,雖然能夠暫時解決問題,可是當出現以前的一段代碼不能知足需求的時候(好比新版app發佈,以前的代碼須要作兼容和升級),須要遍歷全部的代碼,挨個修改,麻煩!html5
第三個問題是前端角色問題,如今組內的開發是先後端分離的,使用smarty模板,由於產品是hybridAPP,因此較傳統前端,增長了客戶端RD的聯調成本。前端幾乎都是在聯調和等待的狀態,跟後端聯調smarty數據接口和客戶端聯調js接口。有時候必需要等接口出來聯調經過了以後,才能繼續寫碼,形成了人力的浪費。如何解放前端人力,解決開發聯調耦合的問題迫在眉睫。web
FIS 是廠子用的一套前端集成解決方案,從開發、調試到打包上線各個環節都覆蓋了。用成龍大哥的話就是:「抱着試一試的心態,後來發現很黑很亮很柔。。。無論本身用,還推薦給其餘團隊使用。」chrome
Fex那邊不少文章在說FIS,我本身也寫過一篇《FIS和FISP的使用心得》,因此這裏就不贅餘,直接重點說下我基於FIS作的一些解決方案。json
第一部分提到的高度耦合的工做流程,分別使用fis本地聯調和chrome擴展來切斷phper、crd跟fe的聯調線路,達到提早自測,提早跑通整個流程。後端
FIS的本地調試功能能夠用於解決phper和FEer的問題,分別有模擬smarty模板數據,模擬Ajax接口等功能。咱們將rewrite規則和聯調的模擬數據,分別寫在了server.conf
和test文件夾
下前端工程化
關於FIS的本地聯調工做,就很少說了,FIS的官網文檔有詳細的說明
爲了解決客戶端注入js接口的方法,咱們經過chrome擴展來實現了。經過chrome的content script方式,在頁面渲染以前提早注入模擬webview的js,這樣頁面在下載渲染的時候在調用js接口就不會報錯。
除了模擬webview接口以外,還將手機百度APP開發中經常使用的工具,和調試功能都集成到這個chrome擴展中。整體的效果以下圖:
chrome擴展的開發過程當中,碰見了不少困難,最後經過查資料一一解決了,整個工具開發就用了一個週末的時間,以後是零零碎碎的需求。由於更新比較頻繁,還引入了自動檢測更新的功能。
由於上面說的緣由,頁面用到的靜態資源都是嵌入到頁面中,這種渲染的方式咱們叫作inline模式。
inline模式每次都下發全量代碼的方式的確蛋疼,影響頁面速度。不難想到後來你們都用了localstorage來緩存inline的代碼,這種渲染方式能夠叫:localstorage+inline
的方式。
手機上的 webview 對 html5 的 localstorage作了不錯的支持,通過咱們抽樣統計,手機百度的搜索結果頁面用戶中,大約有76%支持localstorage。嗯,作localstorage緩存。
如今有不少localstorage的解決方案,是每次都下發一個版本號信息的config文件,頁面加載完畢後,拿着這個config文件跟緩存的localstorage文件校驗版本號,發現有有更新,則二次拉取新的內容,再緩存新內容和新版本號到localstorage。
在移動上,咱們想避免此次二次拉取,因而咱們採用cookie的方式來存儲版本號信息,這樣一次訪問,http請求頭會將cookie帶到後端,後端直接判斷版本號,而且下發代碼。
具體方案以下:
固然,這種解決方案相對簡單,相信不少移動前端團隊也在使用,也會有人說:「咱們都用外鏈。」 前面說了,咱們產品網絡比較複雜,只能爲了2G用戶作了妥協
上線以後,由於頁面內嵌的js和css都緩存到localstorage,頁面大小變小了,的確用戶訪問速度有了很大的提升。嗯,看上去很好~
可是,這又是一個 一刀切 的方案:
如今也許部分童鞋就想到了,爲啥很少存幾個版本號cookie,那樣不就能夠多緩存一些代碼嗎?
cookie多了,http請求頭會變大,http請求頭太大,會對速度產生很大影響,當cookie總量超過800字節,速度會陡升,加上咱們用的域名不少兄弟團隊都在使用,若是放開口子任其發展,最後必定會一發不可收拾。
PS:年前參與一個速度優化項目,其中一個優化方式就是減小cookie,減小請求頭中的cookie,在慢速網絡的速度提高有明顯提升!
ok,繼續探索……
上面的localstorage版本號解決方案,是將md5值存在一個cookie,一個md5值32位,即便使用一半也16位,加上cookie的key,怎麼也要20個字節以上,咱們能不能利用20~100個字節,儘可能緩存更多的緩存文件版本號信息呢?
因而咱們開始了localstorage版本號細化的工做。
這樣作了以後,就是用腳本作緩存文件自動更新版本號了,開始想到的是經過svn hook的方式,當有新的ci時,計算md5值,寫入一個版本號config文件。上線時比較線上config和svn中的config,若是不同就升版本號。可是每次ci都作一次的方式又畫蛇添足、略顯蛋疼,最終的方案是在上線腳本中作了一些工做,沒有使用svn hook:
上面的解決方案仍是不夠完美,總感受存的東西仍是少,因此又作了一個 多維度cookie版本方案 。
咱們把cookie當作能夠兩個維度來存儲: 域名 和 路徑 。
域名A.baidu.com下,有三個產品:新聞、視頻和小說,分別放在三個path:
那麼新聞、視頻和小說,各自有各自的通用代碼,好比:通用樣式,通用js組件。這樣咱們在設置cookie的時候指定相應的path,則能夠實現多維度緩存
爲了實現localstorage的緩存,咱們增長了FISLocalstorage
類來處理cookie,下發緩存代碼,將FIS擴展smarty的 <%html%>
標籤進行了修改,增長了localstorage
屬性,即下面代碼就能夠將頁面開啓緩存:
<%html localstorage="true"%> //something~ <%/html%>
爲了解決重複代碼的問題,咱們開始結合FIS來作模塊化,像seajs、requirejs這些CMD、AMD框架,是後加載的,即用什麼就拉取什麼,屬於異步模塊。js爲了實現異步模塊,而大量的代碼在處理模塊依賴關係。在移動上,咱們不但願這樣,咱們但願在後端維護模塊的依賴關係,當我require一個模塊的時候,會按照依賴關係,依次輸出。
我寫了一個Bdbox的AMD規範的模塊化基礎庫,而後在FIS編譯時,包裹AMD的define
外層,而且能夠生成一張加載資源表,當使用<%widget%>
、<%require%>
和<%script%>標籤內使用require
這些smarty擴展標籤時,會經過php來動態維護模塊依賴。
關於FIS的模塊化和靜態資源管理,廠子FIS開發團隊同窗有一篇文章《如何高效地管理網站靜態資源》
如今頁面要引入 moduleA
模塊,而 moduleA
依賴於 moduleB
和 moduleC
,moduleB
和 moduleC
又有本身的依賴模塊,若是不先輸出 moduleB
和 moduleC
的依賴模塊,直接執行 moduleA
的 define
函數會報錯的,由於 moduleA
模塊依賴的moduleB
和 moduleC
尚未達到 ready
的狀態。
有時候甚至更加複雜的依賴關係:
這時候經過《如何高效地管理網站靜態資源》文章提到的,FIS編譯後會獲得的模塊依賴關係表:map.json
,來作動態模塊依賴管理。
經過修改fis編譯腳本,將模塊依賴文件內容放到map.json
中,當使用smarty擴展語法標籤的時,php會自動讀取map.json
,而後將依賴解析出來,提早將moduleA依賴的模塊都在其 code>define
以前引入,因此下面的兩種代碼寫法:
```smarty
<%require name="common:bdbox/moduleA"%>`
<%或者%>
<%script%>
var moduleA = require('common:bdbox/moduleA'); <%/script%>
```
實際輸出的html代碼是:
<script> define('common:bdbox/moduleB', function(){ //A依賴模塊B }); define('common:bdbox/moduleC', function(){ //A依賴模塊C }); define('common:bdbox/moduleA', function(){ //模塊A var C = require('common:bdbox/moduleC'); var B = require('common:bdbox/moduleA'); }); var moduleA = require('common:bdbox/moduleA'); </script>
對於不是模塊的js或者css文件,若是使用了<%require%>
,則主動使用file_get_contents
來讀取內容。
common:bdbox/moduleA
,common是命名空間,一個項目會由不少頁面模塊(此模塊是產品template模塊,不是前端模塊)組成,經過命名空間能夠快速定位對應的map.json, 而bdbox/moduleA
是實際的AMD模塊名上面全部的關於靜態資源管理的解決方案,都是圍繞 一刀切 的方案在作優化,而沒有利用http自己的cache,實際上:在3G、wifi甚至4G的環境中,http cache的方案,在易用性和兼容性上面要比localstorage+inline
內嵌靜態資源的方式要好。
並且從手機百度真實的用戶網絡類型統計來講,3G+wifi已經達到75%以上,若是能結合wise團隊提供的ip測速庫和公司的CDN服務,會有一種更好的解決方案,進一步來講,若是能夠根據網絡類型和用戶真實網絡速度,自由選擇在localstorage+inline
和CDN方案之間切換就更好了。因而咱們作到了!一種新的渲染方式出現了:CDN+combo
。
再說這種渲染方式以前,先梳理下上面提到的一些名詞:
一刀切
的優化版,將inline的公共靜態資源利用html5 的localstorage緩存作本地存儲好,繼續那模塊化說的moduleA模塊依賴moduleB和moduleC來講,通過 tag模式 ,會輸出下面的html:
<script src="http://xxx/bdbox/moduleC.js"></script> <script src="http://xxx/bdbox/moduleB.js"></script> <script src="http://xxx/bdbox/moduleA.js"></script> <script> var moduleA = require('common:bdbox/moduleA'); </script>
這樣模塊化的代碼常常成了網頁的瓶頸,由於模塊化存在,形成了更多的外鏈!下面咱們須要一個CDN+combo服務,來合併http請求。
由於smarty的擴展語法,結合以前生成的map.json
,咱們實現了模塊化依賴關係後端自動處理依賴,而後選擇最合理的輸出順序。這時候咱們不是直接輸出對應的tag或者inline內容,而是將它合併到一個combo服務對應的URL,統一輸出!
<script src="cdn-combo-server?file=bdbox/moduleC,bdbox/moduleB,bdbox/moduleA"></script>
如何根據用戶網絡環境智能切換渲染方式呢?我繼續改造了smarty的<%html%>
標籤,添加屬性rendermode
,經過wise測速庫和手機百度客戶端傳給咱們的網絡類型,選擇不一樣的rendermode方式:
<%if ($slow_network || $nettype=='2G') && $support_localstorage %> <%html rendermode="inline" localstorage="true"%> <%elseif $fast_network%> <%html rendermode="combo"%> <%else%> <%html rendermode="inline"%> <%/if%> //…… <%/html%>
上面的方案,咱們若是逐個頁面去寫代碼,改方案,想一想就蛋疼,因此咱們拆分了父子模板,從框架自己來分,一個module對應一個父模板,其餘子模板使用smarty的extends標籤實現繼承關係。
通過模板拆分後,子模板專一於作業務,父模板專一於作解決方案,並且也方便了抽樣和統計。
試想一下,若是2015年,用戶都用上了4G,那麼咱們須要將父模板的rendermode改爲 rendermode="combo"
就能夠所有切到 CDN+combo
的渲染方式上,這得減小了多少工做量啊:)