2
0
Fork 0
es5-ff-spa-loader/es5-ff-spa-loader.js

300 lines
9.3 KiB
JavaScript
Raw Normal View History

2015-12-23 00:16:54 +00:00
'use strict';
/*
* Copyright (c) 2015-2016, Willem Cazander
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
* the following disclaimer in the documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// TODO:
// conv to req queue
// split js/css so css can be done later
// set tag.media = 'only you';
// add media in resouces
// add userAgant match in resouces
// NOTE: add ordered inject, for now JS in non-cache mode needs to be aggragated server side
// add browser supported
var FFSpaLoader = (function () {
var options = {};
var utilDebug = function (message) {
if (options.debug !== true) {
return;
}
console.log('FFSpaLoader.'+message);
};
var utilHttpFetch = function (url, cb) {
var startTime = new Date().getTime();
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState == 4) {
utilDebug('utilHttpFetch url: '+url+' done in '+(new Date().getTime()-startTime)+' ms.');
cb(httpRequest);
}
}
httpRequest.open('GET', url, true);
httpRequest.send();
};
var cacheHasItem = function (resource) {
if (options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined) {
return false;
}
var result = options.cacheGetFn(options.cachePrefix + resource.hash) !== null;
utilDebug('cacheHasItem resource: '+resource.url+' result: '+result);
return result;
}
var cacheGetItem = function (resource) {
if (options.cacheGetFn === undefined) {
throw new Error('no caching');
}
utilDebug('cacheGetItem resource: '+resource.url);
return JSON.parse(options.cacheGetFn(options.cachePrefix + resource.hash));
}
var cachePutRequest = function (resource, httpRequest) {
if (options.cacheSetFn === undefined) {
throw new Error('no caching');
}
var cacheKey = options.cachePrefix + resource.hash;
var data = httpRequest.responseText;
utilDebug('cachePutData url: '+resource.url+' key: '+cacheKey);
options.cacheSetFn(cacheKey, JSON.stringify({
resource: resource,
data: data,
dataDate: new Date().getTime()
}));
// Add all cache keys in central list so we can clear the cache item if resources are removed.
var cacheListKey = options.cachePrefix + 'cache-list';
var cacheListStr = options.cacheGetFn(cacheListKey);
if (cacheListStr === null) {
cacheListStr = '[]';
}
var cacheList = JSON.parse(cacheListStr);
cacheList.push(cacheKey);
options.cacheSetFn(cacheListKey, JSON.stringify(cacheList));
}
var cacheCleanup = function (resources) {
if (options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined) {
return;
}
var cacheListKey = options.cachePrefix + 'cache-list';
var cacheListStr = options.cacheGetFn(cacheListKey);
if (cacheListStr === null) {
return;
}
var cacheList = JSON.parse(cacheListStr);
utilDebug('cacheCleanup TODO cacheList: '+cacheList.length+' resources: '+resources.length);
// TODO: impl for removes in resource lists
}
var createResource = function (resource, cb) {
utilDebug('createResource url: '+JSON.stringify(resource));
if (cacheHasItem(resource)) {
if (cacheGetItem(resource).resource.hash === resource.hash) {
cb();
return;
}
utilDebug('createResource hash mismatch request fetch: '+resource.url);
} else {
utilDebug('createResource cache miss request fetch: '+resource.url);
}
utilHttpFetch(resource.url, function(httpRequest) {
if (httpRequest.status == 200) {
cachePutRequest(resource, httpRequest);
} else {
console.warn('error loading '+resource.url);
}
cb();
});
}
var createResources = function(resources, cb) {
var startTime = new Date().getTime();
utilDebug('createResources');
var resourceStack = resources;
var resourceLoader = function() {
resourceStack = resourceStack.slice(1);
if (resourceStack.length === 0) {
utilDebug('createResources done in '+(new Date().getTime()-startTime)+' ms.');
cb();
} else {
createResource(resourceStack[0],resourceLoader);
}
};
createResource(resourceStack[0],resourceLoader);
}
var injectResources = function(resources, cb) {
var startTime = new Date().getTime();
utilDebug('injectResources');
resources.forEach(function (resource) {
var item = cacheGetItem(resource);
var tag = null;
if (resource.type === 'css') {
tag = document.createElement('style');
tag.type = 'text/css';
} else {
tag = document.createElement('script');
tag.type = 'text/javascript';
}
utilDebug('injectResources resource: '+JSON.stringify(resource));
tag.appendChild(document.createTextNode(item.data));
//document.getElementsByTagName('head')[0].appendChild(tag);
var ref = document.getElementsByTagName('script')[0];
ref.parentNode.insertBefore(tag, ref); // note in reverse order
});
utilDebug('injectResources done in '+(new Date().getTime()-startTime)+' ms.');
cb();
}
var injectLinkResources = function(resources, cb) {
var startTime = new Date().getTime();
utilDebug('injectLinkResources');
resources.forEach(function (resource) {
utilDebug('injectLinkResources resource: '+JSON.stringify(resource));
var tag = null;
if (resource.type === 'css') {
tag = document.createElement('link');
tag.type = 'text/css';
tag.rel = 'stylesheet';
tag.href = resource.url;
}
if (resource.type === 'js') {
tag = document.createElement('script');
tag.type = 'text/javascript';
tag.src = resource.url;
}
if (tag !== null) {
var startTime2 = new Date().getTime();
document.getElementsByTagName('head')[0].appendChild(tag);
utilDebug('inject time in '+(new Date().getTime()-startTime2)+' ms.');
//var ref = document.getElementsByTagName('script')[0];
//ref.parentNode.insertBefore(tag, ref); // TODO reverser order
} else {
utilDebug('unknow resource type: '+resource.type);
// TODO add err
}
});
utilDebug('injectLinkResources done in '+(new Date().getTime()-startTime)+' ms.');
cb();
}
var startDefaults = function () {
if (options.url === undefined) {
throw new Error('No url defined');
}
if (options.debug === undefined) {
options.debug = false;
}
if (options.cachePrefix === undefined) {
options.cachePrefix = 'sync-';
}
}
var startDone = function (startTime) {
utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.');
if (options.resultFn) {
options.resultFn();
}
}
var start = function (opt) {
if (opt === undefined) {
throw new Error('need options');
}
var startTime = new Date().getTime();
options = opt;
startDefaults();
var injectAsLink = options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined;
utilDebug('start options: '+JSON.stringify(options)+' injectAsLink: '+injectAsLink);
var resources = [];
utilHttpFetch(options.url, function(httpRequest) {
if (httpRequest.status == 200) {
JSON.parse(httpRequest.responseText).data.resources.forEach(function (r) {
resources.push(r);
utilDebug('build resources add: '+JSON.stringify(r));
});
if (injectAsLink) {
injectLinkResources(resources, function () {
startDone(startTime);
});
} else {
createResources(resources, function () {
injectResources(resources, function () {
cacheCleanup(resources);
startDone(startTime);
});
});
}
} else {
console.log('app err'); // TODO: fix async load error code path
}
})
};
var getOrAskUrl = function (opt) {
};
// TODO: getOrAskUrl: getOrAskUrl,
return {
start: start
};
})();
var autoStartFFSpaLoader = function () {
var scripts = document.getElementsByTagName('SCRIPT');
if (scripts && scripts.length>0) {
for (var i in scripts) {
if (scripts[i].src && scripts[i].src.match(/.*ffSpaLoaderUrl.*/)) {
var query = scripts[i].src;
var queryParameters = {};
query.split('?').forEach(function(part) {
var item = part.split("=");
queryParameters[item[0]] = decodeURIComponent(item[1]);
});
// TODO: remove debug here
FFSpaLoader.start({
debug: true,
injectAsLink: true,
url: queryParameters.ffSpaLoaderUrl
});
break;
}
}
}
};
autoStartFFSpaLoader(); // TODO: make private