1. 通过插件的方式让seajs支持了文件夹方式的版本管理(后续支持文件名形式);
2. 对js的存储(存于localStorage)可配置,支持存储js的单点更新(更新方式模拟manifest实现);
3. 插件内置combo支持,对combo请求亦可版本管理和获取后细粒度拆分存储。
插件源来 - 我们总是不停的碰到问题,接下来分析问题,并解决问题,又碰到问题 --
1. cookie + localStorage
2. applicationCache
1. 含有manifest属性的当前请求页无论如何都会被缓存(对于动态页而言很致命)
2. 更新需要建立在manifest文件的更新,文件更新后是需要页面再次刷新的(需要2次刷新才能获取新资源)
3. 更新是全局性的,无法单独更新某个文件(无法单点更新)
4. 最有效的方式是让manifest文件找不到 可以大部分解决掉由于2)出现的问题
5. 对于链接的参数变化是敏感的,任何一个参数的修改都会被(master)重新缓存(重复缓存含参页面)
6. 所有自定义UI事件都发生在load事件执行完毕后 (UI事件执行反馈结果太过延时)
再来回顾下manifest具体的执行:
1. 首先进行的过程是如同没有manifest属性时候是一致的(文档与资源文件的下载)
2. 接下来会触发manifest清单所列文件的下载
一般情况下我们都会设置 http expire header 以及 max-age ,这会什么时候生效呢?
1. 高效管理资源文件版本
2. 彻底磨灭二次访问的网络请求数(主要指 js 和 css 文件),消灭 304 所带来的各种开销(RTT, TCP Connection setup - 3way - handshake)
3. 节省用户流量,提升用户体验
1. 没有与之相关的cookie记录 ,判断为用户首次访问:则接下来资源文件会直接输出到页面(google gmail小组测试得出的最佳实践),与此同时设置到本地存储,接下来loader会直接从本地存储里面加载。
2. 存在含有版本信息的cookie记录,提交服务器与之对比之后发现无改变,则输出页面本身,前端loader自动调用存储在localStorage的资源文件。
3. 存在含有版本信息的cookie记录,提交服务器与之对比之后发现无改变,但是用户清除了缓存,也就是本地已经不存在相关资源文件存储信息,但此时页面根据cookie记录的信息也已经下来了没有相关脚本的html页面。此时就需要异步有序加载资源文件。loader能保证所有的东西正常有序加载
4. 存在含有版本信息的cookie记录,提交服务器与之对比之后发现有改变,则接下来资源文件会直接输出到页面,与此同时设置到本地存储,server端会记录每个版本改动了什么js或者css,这边会有各个版本的匹配设置过程。
5. 在一切都顺利但是比较极端的情况是,本地存储空间耗尽,此时需要单独发送请求,请求相关资源文件,予以外链支持,这也由loader实现。
1. version — 指明了app资源文件总的版本号,资源文件的更新必须建立在version发生改变的基础上
2. combo — 指明了当前是否需要combo服务的支持, true : 为支持 false : 为不支持
3. entry — 指明了资源文件的条目
1. 首先会从seajs.config中读取是否有需要预先加载preload的模块,存在则加载并执行,没有则正常执行流程
2. 接下来使用resolve方法,其实质将调用id2Uri方法解析'./a'的绝对路径,即在该过程中[‘./a’] —> [‘http://path/to/a.js’]
3. 接下来将执行Module._load(uris,callback),该方法主要会先判断那些资源文件还没有ready,如果全部资源文件都处于ready状态就执行callback(),在这其中还会做循环依赖的判断,以及对没有加载的js执行加载 ,经过该过程判断'http://path/to/a.js'并未被加载过
4. 创建模块a信息 cachedModules('http://path/to/a.js') = new Module('http://path/to/a.js',1)
5. 加载模块a , fetch('http://localhost/test/SEAJS/a.js',onFetched)
1. 首先会根据请求的uri -> http://path/to/a.js 会调用 map 去做一次匹配,如果map中已经存在相关规则,那么替换uri为匹配规则后的uri
2. 加载完毕后执行define ,保存meta信息,主要获取deps,和factory,即得到a的依赖b,并保存a的factory
3. define执行完后 紧接着 触发onload , onload事件执行中 得到 meta 信息缺失的 id 并加载b
4. ……之上过程再走一遍
6. 执行a.factory(compile过程,会逐个对所依赖的模块从内到外执行回调链),得到a的module.exports
1. 为支持文件描述引入manifest.js: 该文件类似于在applicationCache中的manifest文件,但是此manifest并非彼manifest,但是有一点是相通的,他们都有严格的格式要求,尽量扁平化。在该插件中,manifest需要显示指明当前app的version,是否需要combo支持,以及一个个资源条目(链接以及版本),在版本处理上,考虑了灵活性加入了两个特殊符号,#和!,#为不需要版本服务但是我可以通过修改后续的字符来达到存储资源文件的更新; !为需要版本服务同时需要通过版本号来更新但是它无需缓存它。
1.1. 进入应用,事先将会加载manifest文件,如果本地存储中不存在该文件,则认为首次进入站点,不遍历产生需更新列表,并将该文件存入manifest中,即为manifest本身(在fetch阶段会判断到底是否需要存储)
1.2. 页面再次刷新会重新请求manifest文件
1.3. 首先我会判断version是否修改,不修改程序就会认为此次访问不需要任何更新,所有文件全部从本地存储中读取;
1.4. 如果version前后发生了改变,则程序就会认为存储的资源文件发生更动,于是会通过比对存在本地的manifest文件和请求而来的manifest循环递归出哪些资源文件需求修正更新,并在接下来的fetch阶段直接从网络下载(单点更新原理来源于此)
2. 为支持版本控制扩展 Module._resolve() :重点代码
2.1 修正refUri: Module.resolve = function(id, refUri) {return resolve(id, getRealPath(refUri, storedManifest)) ,refUri为参考Uri(就像参照物,因为在seajs中允许在内部使用相对路径),因为我们加入了版本的概念所以在真实文件路径选择中其参照Uri会发生改变,举例:http://path/to/a/a.js 在manifest中我们使用的a.js版本为0.2,那么实际访问的a.js路径为http://path/to/a/0.2/a.js ,如果在a.js中存在依赖b.js,b的版本为0.1,且b.js的目录与目录a平行,即http://path/to/b/0.1/b.js,因为我们已经约定了在js文件中我们不使用具体的版本号,所以在a.js中b.js引用为 var b = require(‘../../b/b.js’)
2.1.1 在不修正refUri情况下,针对于b.js其refUri为http://path/to/a/a.js 则最终其访问的js将为 http://path/b/0.2/b.js
2.1.2 修正refUri情况下,针对于b.js其refUri为http://path/to/a/0.2/a.js 则最终其访问的js将为 http://path/b/0.2/b.js
3. 为支持对文件获取的控制扩展 Module._fetch: 该阶段主要针对js的获取阶段,对uri敏感,如果在js获取过程中,发现该uri条目已经存储在本地存储中了,并且并未出现在更新列表中,并且在版本控制字段中出现了 并未"!“ 即并未指定其不需要存储,那么该js将直接从本地存储中获取反之都将发起网络请求获取。
3.1 如果获取阶段,发现uri是一个combo请求类型,具体combo标记符可以由seajs.config中指定。如果是combo请求,那么将会根据manifest中的相关指定拆分combo请求,并且根据需要存储的js拆分成细粒度并存储于本地存储中。
4. 为支持对combo请求的控制扩展 Module._load: 该阶段主要有两个过程,假设seajs.use([‘./a.js,./b.js,.c/d.js,./c.css’],function(a,b,c){})
4.1 过程1:划分资源 - [[‘http://path/to’, [‘a.js’, ‘b.js’, ‘c/d.js’, ‘a.css’, ‘b.css’]]并且根据资源文件的类型分组[[‘a.js’, ‘c/d.js’, ‘b.js’], [‘a.css’, ‘b.css’]] 注:目前阶段不支持除js和css的资源分组,将会在调用Module._load(uris,callback)时被过滤
4.2 过程2:创建map规则 ,即诸如 ‘http://path/to/a.js’ ==> ‘http://path/to/??a.js,c/d.js,b.js’
转自:http://ux.etao.com/posts/449