webapp之路--百度手机前端经验(转)

  看了之后收获很大,分享一下:

本文将围绕我半年来在移动前端工程化做的一些工作做的总结,主要从localstorage缓存和版本号管理模块化静态资源渲染方式三个方面总结手机百度前端一年内沉淀的解决方案,希望对大家移动开发有所帮助。

一年前存在的问题

可能因为之前项目节奏紧,人力不足原因,一部分phper承担了前端的工作,于是暴漏了一些问题。

粗暴的一刀切

从第一次在厂子写代码开始,就被前辈告诉移动页面,所以的静态资源都要内嵌,即写在scriptstyle内,这样的好处是,网络情况不好的时候,减少http请求。因为2G等网络不稳定的情况下,多开一个http请求,对手机资源消耗是巨大的,比如我们在手机信号不好的地方,访问网络,耗电量会急剧增高。

但是随着3G,甚至4G的普及,实际统计显示,手机百度上2G用户不到30%,所以上面提到的这种一刀切的方案是不妥的。

不成规矩

第二个问题是没有规范和模块化的问题。大家写码都是意识流,除了都是用zepto.js之外,没有沉淀下模块。碰到以前写过的代码,都是ctrl+c + ctrl+v。这种粗放的方式,虽然可以暂时解决问题,但是当出现之前的一段代码不能满足需求的时候(比如新版app发布,之前的代码需要做兼容和升级),需要遍历所有的代码,挨个修改,麻烦!

高度耦合的工作流程

第三个问题是前端角色问题,现在组内的开发是前后端分离的,使用smarty模板,因为产品是hybridAPP,所以较传统前端,增加了客户端RD的联调成本。前端几乎都是在联调和等待的状态,跟后端联调smarty数据接口和客户端联调js接口。有时候必须要等接口出来联调通过了之后,才能继续写码,造成了人力的浪费。如何解放前端人力,解决开发联调耦合的问题迫在眉睫。

引入FIS解决方案

FIS是厂子用的一套前端集成解决方案,从开发、调试到打包上线各个环节都覆盖了。用成龙大哥的话就是:“抱着试一试的心态,后来发现很黑很亮很柔。。。不管自己用,还推荐给其他团队使用。”

Fex那边很多文章在说FIS,我自己也写过一篇《FIS和FISP的使用心得》,所以这里就不赘余,直接重点说下我基于FIS做的一些解决方案。

解决联调成本

第一部分提到的高度耦合的工作流程,分别使用fis本地联调和chrome扩展来切断phper、crd跟fe的联调线路,达到提前自测,提前跑通整个流程。

FIS本地调试

FIS的本地调试功能可以用于解决phper和FEer的问题,分别有模拟smarty模板数据,模拟Ajax接口等功能。我们将rewrite规则和联调的模拟数据,分别写在了server.conftest文件夹

关于FIS的本地联调工作,就不多说了,FIS的官网文档有详细的说明

chrome扩展模拟webview接口

为了解决客户端注入js接口的方法,我们通过chrome扩展来实现了。通过chrome的content script方式,在页面渲染之前提前注入模拟webview的js,这样页面在下载渲染的时候在调用js接口就不会报错。

除了模拟webview接口之外,还将手机百度APP开发中常用的工具,和调试功能都集成到这个chrome扩展中。总体的效果如下图:

注入js模拟手机百度客户端js接口界面

选项页面之配置手机百度user-agent

icon点击popup页面,自动生成当前url二维码,方便手机访问

chrome扩展的开发过程中,遇见了很多困难,最后通过查资料一一解决了,整个工具开发就用了一个周末的时间,之后是零零碎碎的需求。因为更新比较频繁,还引入了自动检测更新的功能。

内嵌静态资源做localstorage缓存

因为上面说的原因,页面用到的静态资源都是嵌入到页面中,这种渲染的方式我们叫做**inline模式**。

inline模式每次都下发全量代码的方式的确蛋疼,影响页面速度。不难想到后来大家都用了localstorage来缓存inline的代码,这种渲染方式可以叫:localstorage+inline的方式。

手机上的 webview 对 html5 的 localstorage做了不错的支持,经过我们抽样统计,手机百度的搜索结果页面用户中,大约有76%支持localstorage。嗯,做localstorage缓存。

localstorage缓存解决方案

现在有很多localstorage的解决方案,是每次都下发一个版本号信息的config文件,页面加载完毕后,拿着这个config文件跟缓存的localstorage文件校验版本号,发现有有更新,则二次拉取新的内容,再缓存新内容和新版本号到localstorage。

在移动上,我们想避免这次二次拉取,于是我们采用cookie的方式来存储版本号信息,这样一次访问,http请求头会将cookie带到后端,后端直接判断版本号,并且下发代码。

具体方案如下:

  • 使用cookie记录localstorage版本号信息
  • 上线时通过打包工具,将所有需要缓存的文件依次计算md5值之和string,然后对string取md5作为版本号
  • 用户访问页面的时候,将cookie带给后端程序,判断两个版本号是否相等,如果不相等就下发全量代码
  • 前端负责判断localstorage支持情况,不支持则写一个特定cookie值,支持则写入localstorage版本号
  • cookie过期时间是一周

当然,这种解决方案相对简单,相信很多移动前端团队也在使用,也会有人说:“我们都用外链。” 前面说了,我们产品网络比较复杂,只能为了2G用户做了妥协

上面解决方案的问题

上线之后,因为页面内嵌的js和css都缓存到localstorage,页面大小变小了,的确用户访问速度有了很大的提高。嗯,看上去很好~

但是,这又是一个一刀切的方案:

  • 业务层代码和基础层代码级别一样:像zepto这种一年更新一次都算多的基础层代码,会因为业务逻辑代码频发更改而重新下发
  • 对于一个域名只有一个页面的页面是个好办法,页面多了,公共的代码就少了
  • 对于一个版本号来说,不能将所有的页面缓存代码都控制住,最后的结果就是在不停的权衡究竟缓存的是什么

现在也许部分童鞋就想到了,为啥不多存几个版本号cookie,那样不就可以多缓存一些代码吗?

cookie多了,http请求头会变大,http请求头太大,会对速度产生很大影响,当cookie总量超过800字节,速度会陡升,加上我们用的域名很多兄弟团队都在使用,如果放开口子任其发展,最后一定会一发不可收拾。

PS:年前参与一个速度优化项目,其中一个优化方式就是减少cookie,减少请求头中的cookie,在慢速网络的速度提升有明显提高!

ok,继续探索……

localstorage细粒度缓存

上面的localstorage版本号解决方案,是将md5值存在一个cookie,一个md5值32位,即使使用一半也16位,加上cookie的key,怎么也要20个字节以上,我们能不能利用20~100个字节,尽量缓存更多的缓存文件版本号信息呢?

于是我们开始了localstorage版本号细化的工作。

  1. 梳理可以缓存的静态资源,将文件分为:基础层、通用层和业务逻辑层,缓存的主要是基础层和通用层的代码
  2. 指定cookie的value值格式,为了缓存更多的版本号信息,我们不再使用md5做版本号信息,而是规定了下面的格式:jA-V_cB-V,即jA和cB代表缓存的文件名,保持两位(j代表js,c代表css,t代表前端js模板文件,);V代表版本号,保持一位,版本号是36进制的,当版本号要超过一位时,从0开始重新记录;按照每周上线一次的情况,cookie时间是一周,36个版本号可以够我们用的
  3. 将需要缓存的文件统一放在一个路径下管理

这样做了之后,就是用脚本做缓存文件自动更新版本号了,开始想到的是通过svn hook的方式,当有新的ci时,计算md5值,写入一个版本号config文件。上线时比较线上config和svn中的config,如果不一样就升版本号。但是每次ci都做一次的方式又多此一举、略显蛋疼,最终的方案是在上线脚本中做了一些工作,没有使用svn hook:

  1. 对缓存文件路径下的文件做md5,生成一张map
  2. 去线上拉取最新的版本号config文件,跟第一步生成的map做比较,不一样则版本升高

localstorage多维度缓存

上面的解决方案还是不够完美,总感觉存的东西还是少,所以又做了一个多维度cookie版本方案

我们把cookie看成可以两个维度来存储:域名路径

举例

域名A.baidu.com下,有三个产品:新闻、视频和小说,分别放在三个path:

  • A.baidu.com/news
  • A.baidu.com/video
  • A.baidu.com/novel

那么新闻、视频和小说,各自有各自的通用代码,比如:通用样式,通用js组件。这样我们在设置cookie的时候指定相应的path,则可以实现多维度缓存

开启localstorage缓存

为了实现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 之前引入,所以下面的两种代码写法:

{%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来读取内容。

Q & A

  • 为啥不直接用seajs和requirejs?

    • 太大,逻辑复杂,不适合移动端
  • 为啥不用FIS自己的modjs,而自己重复造轮子?
    • Bdbox不仅仅是个AMD库,还是一个基础库,维护命名空间和工具类
  • 为什么命名不是标准的AMD规范?
    • 命名中的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模式:即所有的静态资源都内嵌到页面,最古老的一刀切方案
  • tag模式:即使用script和link标签,引入外链的js和css,pc上面常用,2G满网速不适合
  • localstorage+inline模式一刀切的优化版,将inline的公共静态资源利用html5 的localstorage缓存做本地存储
  • CDN+combo模式:即利用tag模式,将资源外链,结合CDN和http cache做好缓存,combo提供模块化代码的打包合并服务

好,继续那模块化说的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标签实现继承关系。

经过模板拆分后,子模板专注于做业务,父模板专注于做解决方案,而且也方便了抽样和统计。

其他

  • 规范方面,已经整理了详细的编码规范和js常见编码问题;
  • 引入jslint和csshint对代码质量进行把控
  • 前端文档,在js代码中增加jsdoc规范的注释,自动通过jsdoc生成前端文档

总结

  • FIS带给我们一整套的前端继承解决方案,是上面所有解决方案的骨架
  • 开发流程上,通过工具来解耦,减少联调等待时间,提高前端工作效率
  • 父子模板拆分,有利于父模板做解决方案
  • 拒绝一刀切的解决方案,做可扩展的解决方案
  • 最后,我们把上面所有的解决方案都放在一个单独的前端common模块中

试想一下,如果2015年,用户都用上了4G,那么我们需要将父模板的rendermode改成rendermode="combo"就可以全部切到CDN+combo的渲染方式上,这得减少了多少工作量啊:)

时间: 2024-11-08 20:51:52

webapp之路--百度手机前端经验(转)的相关文章

百度手机助手上传应用,360助手上架app,豌豆荚发布app多少钱

百度手机助手上传应用,360助手上架app,豌豆荚发布app多少钱 QQ 2205357007 对于安卓App推广而言,申请应用市场App首发是比较有效的App推广方式.如何申请App首发?选择独家App首发还是联合App首发,都需要根据实际情况综合考虑. 目前比较有效的App首发市场包括:360手机助手.腾讯应用宝.百度手机助手.小米.华为.魅族.OPPO,当然有余力的情况下,也可以去申请相对小众的应用市场比如豌豆荚.安智.淘宝手机助手.联想.搜狗.机锋.VIVO.金立等,下面泽思为大家介绍各

python爬取动态生成的网页——以百度手机助手为例

在爬取js动态生成的页面时,直接打开页面是获取不到内容的,比如,我在爬取百度手机助手的应用时,就遇到了这样一个问题.在搜索旅游类应用时,返回数据有几页的内容,但是不管你翻到第几页,查看源代码发现都一样,都是第一页内容的源代码.分析原因我觉得可能是这样的:假设百度应用一页内容有八个应用,你把查询提交后他把内容的前8个生成一个html,然后再你翻页时,通过js,ajax等方式替换原来的8个应用,比如你选择第五页时,把返回应用列表的33-40个应用替换原来的1-8的内容.(应该是ajax或者其他表单提

百度手机助手携浙江卫视 跨年夜唱响深圳湾体育中心(组图)

百度手机助手携浙江卫视 跨年夜唱响深圳湾体育中心(组图) (原标题:百度手机助手携浙江卫视 跨年夜唱响深圳湾体育中心(组图)) 12月31日,由国内最大安卓应用商店百度手机助手冠名的浙江卫视“奔跑吧2016”跨年演唱会将在深圳湾体育中心火热开唱.天后那英.林忆莲.白智英,摇滚兄弟许巍.郑钧,实力乐团羽泉兄弟.苏打绿.筷子兄弟等各类“好声音”齐聚舞台,鹿晗.Angelababy.邓超等跑男团和兄弟们将上演情景秀.撕名牌等好玩的游戏,现场还将公布“百度手机助手2015人气应用榜”.作为晚会独家冠名方

百度Web前端面试经历

今天面了百度的前端实习职位.一面.时间大概是50分钟.面试官是位很帅气的小伙子,非常友好的一个人.进门的时候他让我等一会,我瞄了一眼他的电脑屏幕,发现他在coding…… 9点50开始的面试. 面试官:自我介绍一下. 我:blablabla. 面试官:javascript的类型转换(比如"2"*1, "a"*1). 我:javascript会调用valueOf来转换为一个基本数据类型,在这种情况下,如果javascript不能通过valueOf转成一个number,

[转]关于Web前端开发,附:(百度web前端笔试面试题目)

关于Web前端及百度web前端笔试面试题目 随着各大互联网公司设立了Web前端开发工程师.设计工程师等职位,web前端越来越得到互联网企业的认可.而且其重视程度与地位也随着浏览器 端的富客户端的体现而日益提高. 眼前对HTML5的未来和走向,业内的预测是会和Flash.Silverlight等相结合,从而取代传统的客户端应用程序.而实现这个目标的客户端核 心工作是有Web前端工程师来完成的. 从另一个角度,对于web产品来说,交互和用户体验是产品的第一价值,这部分价值的体现就是在web前端.可以

9/23百度web前端开发一面内容

[9/23百度web前端开发一面内容]左定宽  右自适应iframe选择元素arr.reverse  arr浏览器兼容 IE 如何hackpng类型Spritecss选择器优先级  内联 import哪个高垂直居中 文字 和块级元素appcache manifest无法更新 长尾问题性能优化apply call 区别ajax实现 状态码html加载 repaint reflowli换行导致空白问题

百度手机卫士,简单粗暴至极(关于Stagefright高危漏洞)

以色列移动信息安全公司Zimperium研究人员约舒亚·德雷克(JoshuaDrake)在安卓系统中发现了一系列stagefright安全漏洞,影响当前约95%的Android设备.只需简单的一条彩信,黑客就可能完全控制用户手机.stagefright系列漏洞影响之大,危害之大,堪称移动界的"心脏滴血". 是不是很厉害的样子,其实现在很少用彩信了,只需要手动将手机的彩信自动下载关闭,避免系统自动下载风险彩信.遇到彩信,不要打开.直接与发彩信的人,先沟通一下,看是否是他发的,还是他根本不

仿百度手机助手标题栏透明度随ListView或ScrollView滚动改变的实现方法

有时做项目遇到ListView或ScrollView上方加图片和标题栏布局,在滚动时要求改变标题栏的透明度,即上滑标题栏出现,下拉标题栏变透明,类似百度手机助手6.0装机必备界面效果 要实现这种标题栏透明度随滚动渐变的动画很简单,我们只需要实现onScroll方法就可以,完成对HeaderView的位置检测,然后通过HeaderView的高度和显示的高度来计算比例,设置TitleBar的背景图片的透明度即可,此种实现方法与FadingActionBar是不同的.详细实现源码如下: public

百度手机浏览器能听会看,实现移动生活全程自然交互

百度百科中对于浏览器的标准定义是这样说的:浏览器是指可以显示网页服务器或者文件系统的HTML文件(标准通用标记语言的一个应用)内容,并让用户与这些文件交互的一种软件. 这个定义表明了浏览器软件的最重要功能,即传递信息.PC时代,人们与互联网世界的交互离不开浏览器,是电脑上最重要的应用之一.在移动互联网时代,人们的信息获取处于随时随地的移动中,PC时代习惯于"只做搬运工"的浏览器绑住了用户的双手. 人工智能改变移动互联网信息传递方式 百度手机浏览器并未拘泥于PC时代对浏览器的传统定义,和