慎用manifest

感谢分享:http://mweb.baidu.com/p/%E6%85%8E%E7%94%A8manifest.html

Html5已经被提过很久了,各种新属性出来,人们也都欢呼了一把,今天要讨论的是一个html5的新属性——manifest,可能有很多人已经用过该方法来优化自己的网站了。今天来分享下我们在使用manifest的时候遇到的问题。

1.概念

该特性为HTML5的新特性,能够让web程序指定可以缓存在本地的资源,以及缓存策略,使得程序能够在离线时仍然能够运行,也可以使程序在线时也可以从本地加载资源而不用重新从网络加载。

2.基本写法

要用manifest,就要了解其基本写法

通过在页面的html标签中中引入:

<html manifest=”/static/index/innovation/cache.manifest”>

Cache.manifest的文件写法:

  1. CACHE MANIFEST
  2. # v2012 1203v3
  3. http://m.baidu.com/static/index/i.gif
  4. http://m.baidu.com/static/hb/hot.gif
  5. http://m.baidu.com/static/index/logo_index2.png
  6. NETWORK:
  7. *
  8. http://m.baidu.com/su
  9. http://loc.map.baidu.com/wloc
  10. http://m.baidu.com/static/tj.gif

第一行一定要写上CACHE MANIFEST,然后注释写后面,以后通过修改注释,来实现manifest的更新。

A.路径写法不支持带端口,所以最好用相对路径吧,要不然线下测试机上开发,有端口不好调试。

B.*号通配符有时候好像并不好使。如果在某些设备上出现问题,可以排查一下是不是这个导致的。

C.#以后的内容虽然是注释,但是不能放到第一行,第一行必须是如上的大写字母。

1.离线后的页面

页面离线后,好处显而易见,页面可以秒开了(启动速度快)。你会感觉页面加载的速度快了很多。但是也有一些问题。

A.页面都离线了,如何更新?首先,你可以修改下manifest文件来更新这个页面,这个我就不介绍 了,但是作为文章内容页面离线以后,就会存储在本地了,如果你是一篇文章的话,那么这个文章的内容页就被存下来了,你如果以相同的url去访问,不管你文 章里面的数据更新没有,这个离线下来的页面都不会更新了(除非你更新manifest文件)。所以,所有的动态数据,你都得用ajax方式去获取,就像客 户端一样,离线的页面应该是一个没有数据的空壳,然后通过ajax去拉去数据填补这个空壳。然后要注意的是,ajax的请求地址,要写到manifest的network中,要不然,你可以试试。

B.页面的参数如何携带?,还是刚才那个问题,文章页的壳如果缓存了,怎么样传数据过来标识这个特点页面 呢?比如m.baidu.com/app?a=1&b=2,通常我们用一些参数来标记这个页面,通过参数来渲染页面内容,但是manifest对 于上面的方式,会认为不同的参数表示不同的页面。如果你吧内容页做成一个无数据的空壳,这种传参的方式显然不行,好在不一样的hash页 面,manifest会认为是同一个页面,比如m.baidu.com/app#detail-111111与m.baidu.com /app#detail-222222会认为和m.baidu.com/app是同一个缓存页面。这样我们就可以通过hash传值了,当然,你也可以通过 其它方式传值,比如写入cookie,写入localstorage方式等等。

4.离线页面的更新

网站更新了,如果更新用户本地的离线页面呢?与很多文章中说的一样,先上线你的文件,然后修改一下页面中引入的 cache.manifest文件即可,比如修改下注释,修改后,如果再访问页面,就会先去校验manifest时候有更新,如有更新,再次刷新页面的时 候,页面就会更新了。

A.长尾问题,就像前面说到的一样,如果你的manifest文件更新了,你访问页面,需要刷新一次,更 新的页面才能load进来,那么这样就有一个问题,如果你的后端数据,就是给js ajax接口的数据变化了,你对应的js也修改了。那么你修改manifest上线的时候,第一次开页面,页面就会bug了。再刷一次页面,就好了。那 么,这个第一次访问的bug,是我们不想看到的。而且你不能知道用户什么时候第二次再来访问你的页面,所以你的页面一旦使用manifest离线,就像客 户端一样,这样就出现了长尾问题。还好,manifest有一些js接口,可以来判断,load更新文件。

B.刷新页面

copy了api一段代码:

  1.  1 当前文档对应的applicationCache对象 
  2. window.applicationCache
  3. 2 cache.status 属性,返回当前离线应用的状态
  4.        UNCACHED (数值0):未启用离线应用
  5.        IDLE (数值1):已开启离线应用,但本地缓存的资源是最新的,并且未标记为废弃资源
  6.        CHECKING (数值2):当前更新缓存的状态为“检查中”
  7.        DOWNLOADING (数值3):当前更新缓存的状态为“下载资源中”
  8.        UPDATEREADY (数值4):当前更新缓存的状态为“更新完毕”
  9.        OBSOLETE (数值5):已开启离线应用,但缓存资源都已标记为废弃
  10. 3事件处理器:
  11.   事件名称接口执行时机下一步将执行
  12.   checkingEvent检查是否需要更新,或者在第一次下载manifest文件时。 最先执行的事件noupdate, downloading, obsolete, error
  13.   noupdateEventmanifest文件未修改,不需要更新Last event in sequence.
  14.   downloadingEvent需要更新缓存,或者第一次下载资源时progress, error, cached, updateready
  15.   progressProgressEvent下载资源中progress, error, cached, updateready
  16.   cachedEvent资源已下载完成,并且已完成缓存最后的事件
  17.   updatereadyEvent资源更新完毕,并且可以用swapCache()来启用新的缓存。最后的事件.
  18.   obsoleteEvent加载manifest文件时遇到401或404错误,所以缓存将被删除。最后的事件.
  19. errorEvent加载manifest文件时遇到401或404错误,将中断缓存网页。最后的事件.
  20. 2 常用api
  21. window.applicationCache.update()  //update方法调用时,下载资源到本地如果没有cache需要更新,则抛出 INVALID_STATE_ERR 异常。
  22. window.applicationCache.swapCache()  //updateready后,更新到最新的应用缓存更新缓存到最新的资源如果没有新的资源,则抛出INVALID_STATE_ERR异常并不会使本次 加载的页面立即重新加载资源,仅有在执行该方法后刷新页面才能看到最新的资源。

有了js的api,一切都好办了,我们可以干很多事情了。现在你可以用js来判断页面的状态了,可能你已经想到,判断状 态后,可以刷新一下页面,那么,就算数据发生了修改,这种处理方式也是ok的,没错,这样确实解决了问题。你也可以在设置了manifest的页面中加入 这些方法,然后修改下文件,看看会触发哪些事件,刷新页面的唯一问题就是页面会闪动一下,这点体验当然是不好的。因此,我们想到了loading页面。

  1. var appCache=window.applicationCache;
  2. console.log(appCache.status);
  3. appCache.addEventListener(“obsolete”,function(){
  4.        console.log(“Obsolete,status:”+appCache.status);
  5.    },false);
  6. appCache.addEventListener(“cached”,function(){
  7.        console.log(“chache,status:”+appCache.status);
  8.    },false);
  9. appCache.addEventListener(“checking”,function(){
  10.        console.log(“checking,status:”+appCache.status);
  11.    },false);
  12. appCache.addEventListener(“downloading”,function(){
  13.        console.log(“downloading,status:”+appCache.status);
  14.    },false);
  15. appCache.addEventListener(‘noupdate’, function(){
  16.        console.log(‘Noupdate,Status:’ + appCache.status);
  17.    }, false);
  18. appCache.addEventListener(‘updateready’, function(){
  19.        console.log(‘updateready,Status:’ + appCache.status);
  20.    }, false);
  21. appCache.addEventListener(“error”,function(){
  22.        console.log(“error”);
  23.     },false);

C.Loading页面

制作一个loading页面,用来检测manifest文件有没有更新如果发现有更新,则更新资源,然后再跳到真实的首 页。这个实现起来就比较容易了,在loading页面你可以加一些效果,比如加载效果等等,给用户一个预知。让我们看看manifest修改过和没有修改 过各种状态的差别。保证你的manifest文件存在有效,如下:

  1. CACHE MANIFEST
  2. #test11123aaadfsaasdadffdsfaaaffd
  3. http://m.baidu.com/static/js/zepto-event-ajax.js
  4. testb.html
  5. NETWORK:
  6. *

Html文件如下:

  1. <!DOCTYPE html>
  2. <html manifest=”notes.manifest”>
  3. <head>
  4.     <title>离线存储demo</title>
  5.     <meta http-equiv=”Content-Type” content=”text/html;charset=UTF-8″>
  6. </head>
  7. <body>
  8. Hello world!
  9. <script type=”text/javascript” src=”http://m.baidu.com/static/js/zepto-event-ajax.js”></script>
  10. <script type=”text/javascript”>
  11.    var appCache=window.applicationCache;
  12.    console.log(appCache.status);
  13.    appCache.addEventListener(“obsolete”,function(){
  14.        console.log(“Obsolete,status:”+appCache.status);
  15.    },false);
  16.    appCache.addEventListener(“cached”,function(){
  17.        console.log(“chache,status:”+appCache.status);
  18.    },false);
  19.    appCache.addEventListener(“checking”,function(){
  20.        console.log(“checking,status:”+appCache.status);
  21.    },false);
  22.    appCache.addEventListener(“downloading”,function(){
  23.        console.log(“downloading,status:”+appCache.status);
  24.    },false);
  25.    appCache.addEventListener(‘noupdate’, function(){
  26.        console.log(‘Noupdate,Status:’ + appCache.status);
  27.    }, false);
  28.    appCache.addEventListener(‘updateready’, function(){
  29.        console.log(‘updateready,Status:’ + appCache.status);
  30.    }, false);
  31.     appCache.addEventListener(“error”,function(){
  32.        console.log(“error”);
  33.     },false);
  34. </script>
  35. </body>
  36. </html>

如果页面没有发生变化:那么以上代码会输出:

111

如果对应的manifest有修改,需要更新文件,那么上面的代码会输出:

222

如果更新了manifest,会触发downloading和updateready事件。你可以根据这两个事件来处理 一些逻辑了,比如在downloading给用户一些提示,正在加载,updateready就跳转到真正的页面,那么这个时候,页面就已经加载好了,就 不用再刷新才生效页面了。比如:

  1. appCache.addEventListener(‘updateready’, function(){
  2.        console.log(‘updateready,Status:’ + appCache.status);
  3.        location.href=”testb.html”;
  4.    }, false);

该页面在loading页面上,其实主要的内容页面在testb.html上。

D.Loading页面的问题

这时,你可能认为,这个不错,解决了所有问题。效果也ok,也没有第一次加载页面可能出错的状况。如果对于一个单页面来 说,这样确实没问题了,但是如果你是一个完整的站,问题又来了,你不可能给网站每个页面都加loading,就算每个模版页都用一个loading,如果 用户进入了你某个页面,如果把你某个页面存到了桌面快捷方式。那么下次启动,可能还是会挂,必须刷一次页面才能好,所以,你可能想到,整个页面都要通过 js去build了……,然后又要保持build页面的js要在页面渲染前发生更改,那么,你可以在每个壳模版页面判断manifest的逻辑,然后动态载入的js,通过动态载入的jsbuild整个html页面,那么这样就木有bug了,不过这种处理方式确实恶心了,如果你的模版页的html壳更新不多,仅仅会更新里面的js逻辑的话,那你可以在改html壳中动态载入js即可,也可以直接刷一下页面来解决。

但是这毕竟不是一个app,如果你的站点突然接到需求要更新得很频繁,比如一天要更新一次,那么,这个manifest 离线的意义就仅仅在于第二次载入的时候可以秒开。除此之外,我也想不到其他好处。然后用户如果一天刚好访问一次的话。这个状态好像还没有不用 manifest离线的状态好。那么,我需要把manifest下线……

5.Manifest的回滚与下线

其实,回滚与下线表现上来说,差不多,当然,我这里所提到的回滚,是第一次上线manifest的回滚,一般如果你上线 manifest出现了问题,比如造成的线上严重的bug,那么,这是时候,一般公司就会有回滚机制,用以前的代码来覆盖现在的代码。但是你一旦上线了 manifest离线存储,回滚就没那么容易了,就是从有manifest的状态到无manifest的状态,其实这点和manifest下线一样。很多 人都知道每次更新了静态文件,更新一下manifest多刷两次页面,页面就更新了。如果manifest文件没有了呢,我们还是拿上面的那个页面做测 试。

第一次刷新会这样:

333

第二次再刷新是这样:

444

删除manifest以后,浏览器校验manifest文件就会返回一个404,那么这时,manifest就会失效, 再刷页面,页面就会又开始自动更新了。也不会缓存了,是不是很兴奋。这不是很简单么?删掉manifest文件就能回滚了,就能下线了。不过现在好像 404不流行了,如果你删掉了服务器上某个静态文件,服务器会302跳转到一个错误页,那么这时候,你在怎么刷新页面,页面都不会在更新了。悲剧了……用 户手机浏览器中的页面再也更新不了了。此时你只能再次更新manifest让其强制更新。可能你还想到,我删除了manifest文件,把html中 manifest引用也删除不就行了么。这个也是不行的。浏览器缓存了该地址的离线信息,除非你清除浏览器的缓存,否则这招无效。

555

所以,如果可以,就可以利用manifest文件404把manifest下掉。

如果这招行不通呢?就是没法返回404。那么你上线manifest的时候就要注意了。前文中提到了loading,那 么你得再loading中处理好load manifest文件失效的情况。其实处理方式也很简单,就是跳到本来该跳的页面,然后带上一个参数(前面已经说过,带参数与带hash的区别)也就可以 很轻松解决这个问题了,但是需要注意一定,找不到manifest文件,第一次刷新和第二次刷新所触发的事情不一样,第一次会触发obsolete事件, 第二次再刷新,就触发error事件,不过这样也好办,我们在这两个事件中,将链接都跳到一个带参数的页面就好了(在error、obsolete中跳到 页面带参数,在updateready、noupdate里面直接跳到页面)。所以,利用manifest的js api然后配合删除manifest文件(此时就可以不管页面是不是302或者404了),就可以下线掉manifest文件了,如果用户不清缓存,那段 曾经被离线的代码就会起作用,会让用户永远页面带上参数。

  1.  appCache.addEventListener(“downloading”,function(){
  2.        console.log(“downloading,status:”+appCache.status);
  3.        console.log(“downing”);
  4.    },false);
  5.    appCache.addEventListener(‘noupdate’, function(){
  6.        console.log(‘Noupdate,Status:’ + appCache.status);
  7.        location.href=”testb.html”;
  8.    }, false);
  9.    appCache.addEventListener(‘updateready’, function(){
  10.        console.log(‘updateready,Status:’ + appCache.status);
  11.        location.href=”testb.html”;
  12.    }, false);
  13.     appCache.addEventListener(“error”,function(){
  14.        console.log(“error”);
  15.        location.href=”testb.html?a=1″;
  16. },false);
  17.  appCache.addEventListener(“obsolete”,function(){
  18.        console.log(“Obsolete,status:”+appCache.status);
  19.        location.href=”testb.html?a=2″;
  20.    },false);

5.适用场景小结

上线,下线,回滚,长尾等等问题都考虑过了,现在我们应该可以正确使用manifest了,现在来讨论一个问题,什么样 的页面适合使用manifest呢。关于这点,目前我们还没有具体的数据证明,但是通过manifest离线的页面,第二次访问时启动速度快了很多,而 且,如果处理得当,还是有一些收益的,每次更新manifest文件以后,manifest中未修改的文件是不会被重新下载的,会返回304,而且页面中的其他静态文件,如css和img等,也能享受到传统缓存的方式。

如果你的应用是一个html5的游戏、或者是一个html的app应用,明显,采用manifest会收到很好的效果, 第二次及以后加载速度都非常快,而且你的应用如果不依赖网络的话,就算用户断网了,也可以正常使用,如html5游戏,你完全可以离线来带来更好的用户体 验。然而对于一些传统的站点,也想运用一下manifest新特性,如果你的站点每天都有上线更新,每天都有上线,从manifest的机制上来说,这个 是不怎么适合用manifest离线的,静态资源本来就可以自己缓存,引入manifest还会带来一些维护成本已经上面提到的一些问题。但是你的站点开 发完成以后,更新频率比较低,做完一版以后很长时间内不做升级(这点类似客户端),那么,你的站点就很适合用manifest离线来优化用户体 验,manifest虽然带来了一些好处,但是也有很多问题。

另外,在使用过程中,某些情况下,会出现浏览器无法更新manifest的情况:目前chrome 23.0.1271.97已经找到稳定复现方法。其中:
1.问题:chrome会无法更新静态文件(这些文件包括离线主页,静态文件。)

2.原因:bug情况下,修改了manifest文件以后,通过抓包,发现某些静态文件不会发生校验。 Manifest的机制是如果manifest文件发生过修改,是会校验资源列表中的静态文件的,如果文件发生过修改则会重新download这个文件, 如果资源没有发生修改,服务器会返回304 。但是实际情况是,浏览器不会校验一些文件了。导致文件无法更新。此时,通过chrome://appcache-internals/方式找到浏览器的 manifest缓存,然后删除,然后直接访问无法校验的静态文件。发现仍然可以访问,并且是未修改过的。后来发现是浏览器传统缓存缓存了这个文件。通过 chrome://cache/可以查看到缓存的文件并没有发生修改,造成无法更新。

3.解决方法:

1.把manifest离线资源与主文件跨域存放,但这个也会导致一个问题,就是会导致dns解析变多。

2.在将manifest的列表中的静态文件的文件头中加上no-store(加no-cache不行)。这个也会导致一个问题,就 是如果一个库文件,如zepto.js在m.baidu.com/static 下,manifest用到了,然后其他产品线也用到了,那么其他没用manifest的产品线就享受不了传统缓存了。

3.将manifest静态资源列表中的文件加上时间戳参数,每次修改静态文件,都主动修改下manifest中静态文件的时间戳。 可能有人会认为修改一次时间戳,manifest就会本地多存一次文件,其实不是的,发现列表中如果没有该文件了,浏览器会很高级,会自动删除这个文件。 不好的地方可能就是上线要多改一处地方。略微增加维护成本。

 

另外,有同学还碰到过浏览器将manifest文件缓存的情况,后来将这个文件的过期时间设置成了1s。

一路坑踩过来,还不知道有多少,而且一些bug出现后很难复现,总之慎用吧。

正确应用manifest来优化你的站点吧

posted @ 2015-07-17 17:16  江湖丶丿新进程  阅读(601)  评论(0)    收藏  举报