nicenote/public/js/lsloader.js
2018-01-31 15:53:10 +08:00

410 lines
15 KiB
JavaScript

/** !
* Created by EYHN on 2017/4/17. https://github.com/EYHN
* 修改自 sexdevil/LSLoader https://github.com/sexdevil/LSLoader
*/
(function () {
window.lsloader = {
jsRunSequence: [], //js 运行队列 {name:代码name,code:代码,status:状态 failed/loading/comboJS,path:线上路径}
jsnamemap: {}, //js name map 防fallback 重复请求资源
cssnamemap: {} //css name map 防fallback 重复请求资源
};
/*
* 封装localStorage get set remove 方法
* try catch保证ls写满或者不支持本地缓存环境能继续运行js
* */
lsloader.removeLS = function (key) {
try {
localStorage.removeItem(key)
} catch (e) { }
};
lsloader.setLS = function (key, val) {
try {
localStorage.setItem(key, val);
} catch (e) {
}
}
lsloader.getLS = function (key) {
var val = ''
try {
val = localStorage.getItem(key);
} catch (e) {
val = '';
}
return val
}
versionString = "/*" + (window.materialVersion || 'unknownVersion') + "*/";
lsloader.clean = function () {
try {
var keys = [];
for (var i = 0; i < localStorage.length; i++) {
keys.push(localStorage.key(i))
}
keys.forEach(function (key) {
var data = lsloader.getLS(key);
if (window.oldVersion) {
var remove = window.oldVersion.reduce(function(p,c) {
return p || data.indexOf('/*' + c + '*/') !== -1
}, false)
if (remove) {
lsloader.removeLS(key);
}
}
})
} catch (e) {
}
}
lsloader.clean();
/*
* load资源
* name 作为key path/分割符/代码值 作为value ,存储资源
* 如果value值中取出的版本号和线上模版的一致,命中缓存用本地,
* 否则 调用requestResource 请求资源
* jsname 文件name值,取相对路径,对应存在localStroage里的key
* jspath 文件线上路径,带md5版本号,用于加载资源,区分资源版本
* cssonload css加载成功时候调用,用于配合页面展现
* */
lsloader.load = function (jsname, jspath, cssonload, isJs) {
if (typeof cssonload === 'boolean') {
isJs = cssonload;
cssonload = undefined;
}
isJs = isJs || false;
cssonload = cssonload || function () { };
var code;
code = this.getLS(jsname);
if (code && code.indexOf(versionString) === -1) { //ls 版本 codestartv* 每次换这个版本 所有ls作废
this.removeLS(jsname);
this.requestResource(jsname, jspath, cssonload, isJs);
return
}
//取出对应文件名下的code
if (code) {
var versionNumber = code.split(versionString)[0]; //取出路径版本号 如果要加载的和ls里的不同,清理,重写
if (versionNumber != jspath) {
console.log("reload:" + jspath)
this.removeLS(jsname);
this.requestResource(jsname, jspath, cssonload, isJs);
return
}
code = code.split(versionString)[1];
if (isJs) {
this.jsRunSequence.push({ name: jsname, code: code })
this.runjs(jspath, jsname, code);
} else {
document.getElementById(jsname).appendChild(document.createTextNode(code));
cssonload();
}
} else {
//null xhr获取资源
this.requestResource(jsname, jspath, cssonload, isJs);
}
};
/*
* load请求资源
* 根据文件名尾部不同加载,js走runjs方法,加入运行队列中
* css 直接加载并且写入对应的<style>标签,根据style的顺序
* 保证css能正确覆盖规则 css 加载成功后调用cssonload 帮助控制
* 异步加载样式造车的dom树渲染错乱问题
* */
lsloader.requestResource = function (name, path, cssonload, isJs) {
var that = this
if (isJs) {
this.iojs(path, name, function (path, name, code) {
that.setLS(name, path + versionString + code)
that.runjs(path, name, code);
})
} else {
this.iocss(path, name, function (code) {
document.getElementById(name).appendChild(document.createTextNode(code));
that.setLS(name, path + versionString + code)
}, cssonload)
}
};
/*
* iojs
* 请求js资源,失败后调用jsfallback
* */
lsloader.iojs = function (path, jsname, callback) {
var that = this;
that.jsRunSequence.push({ name: jsname, code: '' })
try {
var xhr = new XMLHttpRequest();
xhr.open("get", path, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
if (xhr.response != '') {
callback(path, jsname, xhr.response);
return;
}
}
that.jsfallback(path, jsname);
}
};
xhr.send(null);
} catch (e) {
that.jsfallback(path, jsname);
}
};
/*
* iocss
* 请求css资源,失败后调用cssfallback
* */
lsloader.iocss = function (path, jsname, callback, cssonload) {
var that = this;
try {
var xhr = new XMLHttpRequest();
xhr.open("get", path, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
if (xhr.response != '') {
callback(xhr.response);
cssonload();
return;
}
}
that.cssfallback(path, jsname, cssonload);
}
};
xhr.send(null);
} catch (e) {
that.cssfallback(path, jsname, cssonload);
}
};
lsloader.iofonts = function (path, jsname, callback, cssonload) {
var that = this;
try {
var xhr = new XMLHttpRequest();
xhr.open("get", path, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
if (xhr.response != '') {
callback(xhr.response);
cssonload();
return;
}
}
that.cssfallback(path, jsname, cssonload);
}
};
xhr.send(null);
} catch (e) {
that.cssfallback(path, jsname, cssonload);
}
};
/*
* runjs
* 运行js主方法
* path js线上路径
* name js相对路径
* code js代码
* */
lsloader.runjs = function (path, name, code) {
//如果有 name code ,xhr来的结果,写入ls 否则是script.onload调用
if (!!name && !!code) {
for (var k in this.jsRunSequence) {
if (this.jsRunSequence[k].name == name) {
this.jsRunSequence[k].code = code;
}
}
}
if (!!this.jsRunSequence[0] && !!this.jsRunSequence[0].code && this.jsRunSequence[0].status != 'failed') {
//每次进入runjs检查jsRunSequence,如果第一项有代码并且状态没被置为failed,执行并剔除队列,回调
var script = document.createElement('script');
script.appendChild(document.createTextNode(this.jsRunSequence[0].code));
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
this.jsRunSequence.shift();
//如果jsSequence还有排队的 继续运行
if (this.jsRunSequence.length > 0) {
this.runjs();
}
} else if (!!this.jsRunSequence[0] && this.jsRunSequence[0].status == 'failed') {
/*每次进入runjs检查jsRunSequence,如果第一项存在并且状态为failed,用script标签异步加载,
* 并且该项status置为loading 其他资源加载调用runjs时候就不会通过这个js项,等候完成
*/
var that = this;
var script = document.createElement('script');
script.src = this.jsRunSequence[0].path;
script.type = 'text/javascript';
this.jsRunSequence[0].status = 'loading'
script.onload = function () {
that.jsRunSequence.shift();
//如果jsSequence还有排队的 继续运行
if (that.jsRunSequence.length > 0) {
that.runjs();
}
};
document.body.appendChild(script);
}
}
/*
* tagLoad 用script标签加载不支持xhr请求的js资源
* 方法时jsRunSequence队列中添加一项name path为该资源,但是status=failed的项
* runjs调用检查时就会把这个项当作失败取用script标签请求
* */
lsloader.tagLoad = function (path, name) {
this.jsRunSequence.push({ name: name, code: '', path: path, status: 'failed' });
this.runjs();
}
//js回退加载 this.jsnamemap[name] 存在 证明已经在队列中 放弃
lsloader.jsfallback = function (path, name) {
if (!!this.jsnamemap[name]) {
return;
} else {
this.jsnamemap[name] = name;
}
//jsRunSequence队列中 找到fail的文件,标记他,等到runjs循环用script请求
for (var k in this.jsRunSequence) {
if (this.jsRunSequence[k].name == name) {
this.jsRunSequence[k].code = '';
this.jsRunSequence[k].status = 'failed';
this.jsRunSequence[k].path = path;
}
}
this.runjs();
};
/*cssfallback 回退加载
* path 同上
* name 同上
* cssonload 同上
* xhr加载css失败的话 使用link标签异步加载样式,成功后调用cssonload
*/
lsloader.cssfallback = function (path, name, cssonload) {
if (!!this.cssnamemap[name]) {
return;
} else {
this.cssnamemap[name] = 1;
}
var link = document.createElement('link');
link.type = 'text/css';
link.href = path;
link.rel = 'stylesheet';
link.onload = link.onerror = cssonload;
var root = document.getElementsByTagName('script')[0];
root.parentNode.insertBefore(link, root)
}
/*runInlineScript 运行行内脚本
* 如果有依赖之前加载的js的内联脚本,用该方法执行,
* scriptId js队列中的name值,可选
* codeId 包含内连脚本的textarea容器的id
* js队列中添加name code值进入,运行到该项时runjs函数直接把代码append到顶部运行
*/
lsloader.runInlineScript = function (scriptId, codeId) {
var code = document.getElementById(codeId).innerText;
this.jsRunSequence.push({ name: scriptId, code: code })
this.runjs()
}
/*loadCombo combo加载,顺序执行一系列js
*
* jslist :[
* {
* name:名称,
* path:线上路径
* }
* ]
* 遍历jslist数组,按照顺序加入jsRunSequence
* 其中,如果本地缓存成功,直接写入code准备执行
* 否则status值为comboloading code写入null 不会执行
* 所有comboloading的模块拼接成一个url请求线上combo服务
* 成功后执行runcombo方法运行脚本
* 失败的话所有requestingModules请求的js文件都置为failed
* runjs会启用script标签加载
*/
lsloader.loadCombo = function (jslist) {
var updateList = '';// 待更新combo模块列表
var requestingModules = {};//存储本次更新map
for (var k in jslist) {
var LS = this.getLS(jslist[k].name);
if (!!LS) {
var version = LS.split(versionString)[0]
var code = LS.split(versionString)[1]
} else {
var version = '';
}
if (version == jslist[k].path) {
this.jsRunSequence.push({ name: jslist[k].name, code: code, path: jslist[k].path }) // 缓存有效 代码加入runSequence
} else {
this.jsRunSequence.push({ name: jslist[k].name, code: null, path: jslist[k].path, status: 'comboloading' }) // 缓存无效 代码加入运行队列 状态loading
requestingModules[jslist[k].name] = true;
updateList += (updateList == '' ? '' : ';') + jslist[k].path;
}
}
var that = this;
if (!!updateList) {
var xhr = new XMLHttpRequest();
xhr.open("get", combo + updateList, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
if (xhr.response != '') {
that.runCombo(xhr.response, requestingModules);
return;
}
} else {
for (var i in that.jsRunSequence) {
if (requestingModules[that.jsRunSequence[i].name]) {
that.jsRunSequence[i].status = 'failed'
}
}
that.runjs();
}
}
};
xhr.send(null);
}
this.runjs();
}
/*runcombo
* comboCode 服务端返回的用/combojs/注释分隔开的js代码
* requestingModules 所有被combo请求的modules map
* requestingModules:{
* js文件name : true
* }
* combo服务返回代码后,用分隔符把所有js模块分隔成数组,
* 用requestingModules查找jsRunSequence中该模块对应的项,
* 更改该项,code为当前代码,status改为comboJS
* 所有combo返回的模块遍历成功后,runjs()
* runjs会把所有有代码的项当作成功项执行
*/
lsloader.runCombo = function (comboCode, requestingModules) {
comboCode = comboCode.split('/*combojs*/');
comboCode.shift();//去除首个空code
for (var k in this.jsRunSequence) {
if (!!requestingModules[this.jsRunSequence[k].name] && !!comboCode[0]) {
this.jsRunSequence[k].status = 'comboJS';
this.jsRunSequence[k].code = comboCode[0];
this.setLS(this.jsRunSequence[k].name, this.jsRunSequence[k].path + versionString + comboCode[0]);
comboCode.shift();
}
}
this.runjs();
}
})()