本文将围绕我半年来在移动前端工程化作的一些工做作的总结,主要从 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
的渲染方式上,这得减小了多少工做量啊:)