-
-
Save addyosmani/1866817 to your computer and use it in GitHub Desktop.
/* Basket.js | |
* A script-loader that handles caching scripts in localStorage | |
* where supported. | |
* http://addyosmani.com/ | |
* Credits: Addy Osmani, Mathias Bynens, Ironsjp. | |
* Copyright (c) 2012 Addy Osmani; | |
* Licensed MIT, GPL | |
*/ | |
; (function (w, d) { | |
function basketLoader() { | |
var | |
_storagePrefix = "ll-", | |
_localStorage = function (a, b) { | |
try { | |
return (a = localStorage).setItem(b, a), a.removeItem(b), !0 | |
} catch (c) {} | |
}(), | |
scripts = [], | |
scriptsExecuted = 0, | |
waitCount = 0, | |
waitCallbacks = [], | |
// Minimalist Cross-browser XHR | |
// from https://gist.github.com/991713 | |
getXMLObj = function (s, a) { | |
a = [a = "Msxml2.XMLHTTP", a + ".3.0", a + ".6.0"]; | |
do | |
try { | |
s = a.pop(); | |
return new(s ? ActiveXObject : XMLHttpRequest)(s) | |
} catch (e) {; | |
} | |
while (s) | |
}, | |
getUrl = function (url, callback) { | |
var xhr = getXMLObj(); | |
xhr.open("GET", url, true); | |
xhr.onreadystatechange = function (e) { | |
if (xhr.readyState === 4) { | |
callback(xhr.responseText); | |
} | |
}; | |
xhr.send(); | |
}, | |
injectScript = function (text) { | |
var script = d.createElement("script"), | |
head = d.head || d.getElementsByTagName("head")[0]; | |
script.appendChild(d.createTextNode(text)); | |
head.insertBefore(script, head.firstChild); | |
}, | |
queueExec = function (waitCount) { | |
var script, i, j, callback; | |
if (scriptsExecuted >= waitCount) { | |
for (i = 0; i < scripts.length; i++) { | |
script = scripts[i]; | |
if (!script) { | |
// loading/executed | |
continue; | |
} | |
scripts[i] = null; | |
injectScript(script); | |
scriptsExecuted++; | |
for (j = i; j < scriptsExecuted; j++) { | |
if (callback = waitCallbacks[j]) { | |
waitCallbacks[j] = null; | |
callback(); | |
} | |
} | |
} | |
} | |
}; | |
return { | |
add: function (path) { | |
var key = _storagePrefix + path, | |
scriptIndex = scripts.length, | |
_waitCount = waitCount; | |
scripts[scriptIndex] = null; | |
if (_localStorage && _localStorage.getItem(key)) { | |
scripts[scriptIndex] = _localStorage.getItem(key); | |
queueExec(_waitCount); | |
} else { | |
getUrl(path, function (text) { | |
(_localStorage) && _localStorage.setItem(key, text); | |
scripts[scriptIndex] = text; | |
queueExec(_waitCount); | |
}); | |
} | |
return this; | |
}, | |
wait: function (callback) { | |
waitCount = scripts.length; | |
if (callback) { | |
(scriptsExecuted >= waitCount - 1) ? callback() : waitCallbacks[waitCount - 1] = callback; | |
} | |
return this; | |
} | |
}; | |
} | |
w['basket'] = basketLoader(); | |
})(this, document); |
<html> | |
<head> | |
<title>Test</title> | |
</head> | |
<body> | |
<script src="basket.js"></script> | |
<script> | |
basket | |
.add("jquery-1.7.1.min.js").wait() | |
.add("underscore-min.js") | |
.add("backbone-min.js").wait(function() { | |
alert("woot! \o/"); | |
}); | |
</script> | |
</body> | |
</html> |
Thanks for looking at this @mathiasbynens! I'll update accordingly :)
I would just steal the localStorage feature test from Modernizr.
Whyw['localLoad']
over w.localLoad
? Closure Compiler Advanced mode?
Why not just inject it into the body?
injectScript = function (text) {
var script = d.createElement("script");
script.appendChild(d.createTextNode(text));
d.body.appendChild(script);
},
@sindresorhus Injecting it into body
has the exact same issues as injecting it into head
. http://mths.be/ieoa
The localStorage
pattern I linked to results in smaller file size after minification compared to just using the Modernizr feature test + using localStorage
afterwards. I mentioned this because Addy asked me to look for possible file size / performance optimizations.
@mathiasbynens Didn't know about that that, neat... Oh, so happy I don't have to support IE7 anymore ;)
Just noticed the link. Interesting article (read some of your other posts too, good stuff ;)), though you don't really save that many bytes that it makes a difference.
Didn't mean hijack the gist, just found it interesting :)
Codegolfed the feature test if your really care about byte size savings:
var l=function(a,b){try{return(a=localStorage).setItem(b,a),a.removeItem(b),!0}catch(c){}}();
Thanks for the input, all. I'm wondering if beyond what's there at the moment if this project should be attempting to do anything else. I don't feel there's a huge amount of value in trying to turn it into (yet another) generic script loader, but if there are features you think are missing happy to take your feedback on board.
Noticed LabJS covers cacheBust and allowDuplicates options (which could be added) but don't know if thats OTT. Also wondering if I should consider expanding this into a generic localStorage asset cacher (e.g to cover templates too) or whether I should just leave that to something else.
In
injectScript
, thescript.type = "text/javascript";
line is not needed at all. Also, it’s probably better to append to the first<script>
element than to<head>
to avoid issues when the script is called from the<head>
.If you insist on using the
<head>
approach though,d.getElementsByTagName("head")[0]
could bed.head || d.getElementsByTagName("head")[0]
.The way
localStorage
is used here might throw an error.(_localStorage) ? _localStorage.setItem(key, text) : null;
could be(_localStorage) && _localStorage.setItem(key, text);
.If you want to save bytes, you could get rid of the
getLocalLoader
function and just usew['localLoad'] = localLoader();
instead ofw['localLoad'] = getLocalLoader();