2

亲,如果你还在为你没网打开不网页而烦恼吗?
亲,你还在为你web服务器复杂的配置项而蛋疼吗?
不要998,manifest抱回家~
manifest自H5横空出世以来给前端网页的浏览带来了翻天覆地的变化,以前我们的网页必须在有网的前提下打开(主要还是打开HTML), 但是现在,我们可以offline 浏览。 可以算是实现web app的一个特技。
manifest的兼容性 IE9+. 由于是现代的技术,IE9以下的古老浏览器是不支持的。所以,manifest主要应用是针对现代浏览器或者手机端更多一些。

入门manifest

浏览器检测你是否使用manifest特技时,是检测html标签.

<html lang="en" manifest="usable.manifest">

当解析你的HTML时,发现存在manifest文件时,则会进行如下的操作:
manifest加载
(from alloy team)
manifest文件可以是任意后缀比如. usable.manifest||usable.mf等,但是他的MIMEtype必须设置正确.
记住,这个时候manifest会将HTML文件也一并保存,这需要注意。

书写manifest文件

一个简单的demo:

CACHE MANIFEST
#version 1.3
/public/static/index.css
/public/static/header.css
NETWORK:
*
FALLBACK:
/userInfo/ /404.html
#额外需要添加的缓存文件
CACHE:
images/logo1.png
images/logo2.png

基本样式就是上述

CACHE MANIFEST/CACHE

第一行必须是指定头即, "CACHE MANIFEST"(不能有其他的). 表示哪些文件需要缓存。如果是相对路径则是,在manifest文件所在的目录下。而且,不能使通配符!!!(tm 你是还不是傻). 所以一般而言只能一个一个配置.

CACHE MANIFEST
#相对于manifest文件所在的目录
    ./index.css 

注释: 注释使用#+"info"
可以对缓存文件性质进行适当的说明。缓存后的文件,就会被带上Expires的头,表示可以不经过服务器验证直接使用本地文件。所以,返回status Code 为 200.
另外,CACHE 定义的文件内容,和CACHE MANIFEST 是一个效果,只是跟在CACHE MANIFEST之后,就可以省略书写CACHE,你添加上也可以。

CACHE MANIFEST
#version 1.3
CACHE:
/favicon.ico

而且CACHE可以放在文中的任意位置,不过一般都是放开头,或者省略.

CACHE MANIFEST

# 缓存文件
index.html
css/style.css

NETWORK:
*

# 额外的需要缓存的文件
CACHE:
images/logo1.png
images/logo2.png
images/logo3.png

NETWORK

这里设置不使用缓存的文件,可以使用通配符"*"等。
* 表示,除了CACHE MANIFEST定义的文件之外的文件都不能被缓存。
当然也可以手动指定文件:

NETWORK
*
http://www.example.com/index.html
http://www.example.com/header.png
http://www.example.com/blah/blah

这些浏览器都不能直接使用缓存,即,可能会要求你重新验证,或者直接使用服务器文件。

FALLBACK

这个tag,可用可不用。 用来表示,指定文件无法加载时,使用另外的文件代替。参数有两部分构成,第一部分是指定资源(可能存在文件未加载),第二部分是替代资源

FALLBACK:
/index.html /404.html
/static/* /404.html
/images/* /NotFound.jpg

当index.html无法加载时,使用404.html代替. 这里有个要求,两个路径必须使用相对路径并且与清单文件同源。

SETTINGS

这算是一个附加属性吧。通常设置内容就只有:

SETTINGS:
prefer-online

表示,在有网的情况下,会先访问服务器的文件,看有没有更新,相当于设置了Cache-Control:max-age=0,must-revalidate; + ETag||Last-modified. 不过,比较stupid的是,只有FF(Opera 12)支持.

服务器设置manifest

而在服务器端,需要对manifest文件的MIME设置正确。这里以nginx为例, 具体设置一下MIME type

type{
  image/gif                             gif;  
  image/jpeg                            jpeg jpg;  
  application/x-javascript              js;  
  }

详情可以参考: manifest文件配置

自动生成manifest文件配置

这里以gulp为例。 可以在npm里面很容易找到gulp-manifest这个生成插件.
直接下载:

npm install gulp-manifest --save-dev

然后在gulpfile里面配置:

gulp.task('manifest', function(){
  gulp.src(['build/**'], { base: './' })
    .pipe(manifest({
      hash: true,
      preferOnline: true,
      network: ['*'],
      fallback:['/images/* /404.html']
      filename: 'app.manifest',
      exclude: 'app.manifest'  //不保存manifest,不过有没有效果一样
     }))
    .pipe(gulp.dest('./'));
});

接着就会在目录下生成app.manifest文件,里面就是一些基本的文件格式了。另外如果你想查看你电脑有多少网页是manifest,可以直接访问 chrome://appcache-internals/.

manifest的坑点

manifest对于单页应用可谓是如鱼得水,但是,到了多页应用的层面,他的bug真的是暴露无遗。

1.页面保存的复杂度,
2.文件的及时更新,
3.缓存文件的设置, 
4.死都会保存HTML,
5.文件下载出错,则这次更新缓存失败,
6.覆盖所有缓存头,除了Cache-Control:no-store
7.在Android 4.4的webview里,关闭之后会丢失cache
8.IE10不能很好的支持FALLBACK部分.

所以,appCache的bug也是非常多的。
例如,长尾更新问题,当你的页面保持在线的时候,是无法检测文件已经更新,除非你reload页面,但是用户并不知道你已经更新,所以这里我们需要引进js的提供的缓存检测API.

window.applicationCache

这是前端能够摸到缓存最真实的API。我们可以通过这个API接口获取到我们很多想要的东西:

var appcache = window.applicationCache;
console.log(appcache.status); //检查当前缓存状态
console.log(appcache.IDLE); //缓存状态常量,下面解释

常用的属性有:

属性名 explanation
status 当前缓存状态,为Number类型. 为0~5
UNCACHED(0) 浏览器未缓存文件
IDLE(1) 空闲状态,浏览器已经全部缓存
CHECKING(2) 页面正在检查当前离线缓存是否需要更新
DOWNLOADING(3) 页面正在下载需要更新的缓存文件
UPDATEREADY(4) 页面缓存更新完毕
OBSOLETE(5) 缓存已经过期

常用的方法:

window.applicationCache.update()  
//update方法调用时,页面会主动与服务器通信,检查页面当前的缓存是否为最新的,如不是,则下载更新后的资源

window.applicationCache.swapCache()  //updateready后,更新到最新的应用缓存

通常结合上述两个方法和相应的属性我们可以手动触发文件的更新(前提是 manifest文件改动).

var appCache = window.applicationCache;

appCache.update(); 
//检查更新

if (appCache.status == window.applicationCache.UPDATEREADY) { 
//如果存在更新,并且已经下载ok,则替换浏览器缓存
  appCache.swapCache();  
}

但是,此时页面并不能用上最新的文件,只是浏览器的缓存已经改变,网页实际内容还是原来的内容,还需要手动进行reload,才能进行更新文件

window.addEventListener('load', function(e) {

  window.applicationCache.addEventListener('updateready', function(e) {
    if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
      if (confirm('文件有更新,手否重新加载文件')) {
        window.location.reload();
      }
    } else {
      //如果,拒绝则不刷新网页
    }
  }, false);

}, false);

cache相关事件

相关事件有: checking,downloading,updateready,obsolete,cached,error,noupdate,progress.
对照上述的status就可以很容易知道每个事件对应的效果是神马。 需要说的就是:

progress: 当浏览器在下载资源时,每下载成功一次,就会触发一次
noupdate:当浏览器检查更新之后发现没有资源更新的时候触发这个事件
error: 更新出错时会触发,比如文件无法正常下载,manifest文件被删除.

其实,使用manifest的时候,无外乎就是3种常用状态

  1. 第一次访问页面时

  2. 再次访问页面时,没有更新

  3. 再次访问页面时,有更新

每次,触发的事件顺序为:

行为 事件顺序
第一次访问页面 checking->downloading->progress(多次)->cached
再次访问时,没有更新 checking->noupdate
再次访问时,有更新 checking->downloading->progress(多次)->updateready

上面看不懂没关系,我们可以看看更直观的Console的内容。

  1. 第一次访问页面时

checking->downloading->progress(多次)->cache

2\. 再次访问页面时,没有更新

checking->noupdate

3\. 再次访问页面时,有更新

checking->downloading->progress(多次)->updateready

浅谈manifest

其实,manifest就是为了离线应用而生的,但是由于设计之初,没有很好的规范,导致现在manifest的bug,真的超级多。
看到whatwg上面说的一句话,真的更加蛋疼.

This feature is in the process of being removed from the Web platform. (This is a long process that takes many years.) Using any of the offline Web application features at this time is highly discouraged. Use service workers instead.

意思就是让你不要用manifest,应该他迟早要被fire的,但是,这一天还有很多年,很多年。 另外一个替代方案就是使用SS,但是兼容性,真的极其差。几乎现在的浏览器都没有实现(除了布道师FF实现了部分). 现在我们真的很尴尬,不过,目前的情况而言,in my opinion, 是十分推荐使用的(也没有其他的办法了). 那该怎么做,才能将manifest的Bug减到最低呢?
推荐的做法是将逻辑页面和用户数据给分离开。 逻辑页面使用app cache,而用户数据可以保存在web Storage || indexDB 等浏览器数据库里,动态更新data时,使用web Socket,ajax,SSE等技术.


villainhr
7.8k 声望2.2k 粉丝