diff --git a/Gruntfile.js b/Gruntfile.js index 4c38beb..1b02801 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,7 +19,7 @@ module.exports = function(grunt) { }, uglify: { options: { - banner: '/*!yepnope<%= pkg.version %>|MIT*/\n' + banner: '/*!yepnope<%= pkg.version %>|New BSD*/\n' }, dist: { files: { diff --git a/README.md b/README.md index 332845e..56e6f3d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://api.travis-ci.org/SlexAxton/yepnope.js.png?branch=v2.0)](https://travis-ci.org/SlexAxton/yepnope.js) +[![Build Status](https://api.travis-ci.org/SlexAxton/yepnope.js.png?branch=master)](https://travis-ci.org/SlexAxton/yepnope.js) # Deprecation Notice @@ -28,7 +28,7 @@ For these reasons, we're also not going to include yepnope in the next version o A Script Loader For Your Conditional Builds -By [@SlexAxton](http://twitter.com/SlexAxton) and [@rlph](http://twitter.com/rlph) on Twitter for more updates. +By [@SlexAxton](http://twitter.com/SlexAxton) and [@rlph](http://twitter.com/rlph) ## Example diff --git a/dist/yepnope-2.0.0.js b/dist/yepnope-2.0.0.js new file mode 100644 index 0000000..c91a54c --- /dev/null +++ b/dist/yepnope-2.0.0.js @@ -0,0 +1,330 @@ +// yepnope.js +// v2.0.0 +// +// by +// Alex Sexton - @slexaxton - alexsexton[at]gmail.com +// Ralph Holzmann - @rlph - ralphholzmann[at]gmail.com +// +// http://yepnopejs.com/ +// https://github.com/SlexAxton/yepnope.js/ +// +// New BSD +// +// Consider inlining this script after minifying + +window.yepnope = (function (window, document, undef) { + // Yepnope's style is intentionally very flat to aid in + // minification. The authors are usually against too much + // self-minification, but in the case of a script loader, we're + // especially file size sensitive. + + // Some aliases + var sTimeout = window.setTimeout; + var firstScript; + var scriptsQueue = []; + var count = 0; + var toString = {}.toString; + + // This is just used for a race condition, + // so even if it fails it's not a huge risk + var isOldIE = !!document.attachEvent && !(window.opera && toString.call(window.opera) == '[object Opera]'); + + function noop(){} + + // Helper functions + function isObject(obj) { + return Object(obj) === obj; + } + + function isString(s) { + return typeof s == 'string'; + } + + // Loader Utilities + function uniq() { + return 'yn_' + (count++); + } + + function readFirstScript() { + if (!firstScript || !firstScript.parentNode) { + firstScript = document.getElementsByTagName('script')[0]; + } + } + + function isFileReady(readyState) { + // Check to see if any of the ways a file can be ready are available as properties on the file's element + return (!readyState || readyState == 'loaded' || readyState == 'complete' || readyState == 'uninitialized'); + } + + function runWhenReady(src, cb) { + cb.call(window); + } + + // Inject a script into the page and know when it's done + function injectJs(options, cb) { + var src; + var attrs; + var timeout; + + if (isString(options)) { + src = options; + } + else if (isObject(options)) { + // Allow rewritten url to take precedence + src = options._url || options.src; + attrs = options.attrs; + timeout = options.timeout; + } + + cb = cb || noop; + attrs = attrs || {}; + + var script = document.createElement('script'); + var done; + var i; + + timeout = timeout || yepnope.errorTimeout; + + script.src = src; + + // IE Race condition + // http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html + if (isOldIE) { + script.event = 'onclick'; + script.id = script.htmlFor = attrs.id || uniq(); + } + + // Add our extra attributes to the script element + for (i in attrs) { + script.setAttribute(i, attrs[i]); + } + + // Bind to load events + script.onreadystatechange = script.onload = function () { + + if ( !done && isFileReady(script.readyState) ) { + // Set done to prevent this function from being called twice. + done = 1; + + // Second half of IE race condition hack + if (isOldIE) { + try { + // By calling this here, we create a synchronous + // execution of the contents of the script + // and the execution of the callback below. + script.onclick(); + } + catch (e) {} + } + + // Just run the callback + runWhenReady(src, cb); + } + + // Handle memory leak in IE + script.onload = script.onreadystatechange = script.onerror = null; + }; + + // This won't work in every browser, but + // would be helpful in those that it does. + // http://stackoverflow.com/questions/2027849/how-to-trigger-script-onerror-in-internet-explorer/2032014#2032014 + // For those that don't support it, the timeout will be the backup + script.onerror = function () { + // Don't call the callback again, so we mark it done + done = 1; + cb(new Error('Script Error: ' + src)); + // We don't waste bytes on cleaning up memory in error cases + // because hopefully it doesn't happen often enough to matter. + // And you're probably already in an 'uh-oh' situation. + }; + + // 404 Fallback + sTimeout(function () { + // Don't do anything if the script has already finished + if (!done) { + // Mark it as done, which means the callback won't run again + done = 1; + + // Might as well pass in an error-state if we fire the 404 fallback + cb(new Error('Timeout: ' + src)); + // Maybe... + script.parentNode.removeChild(script); + } + }, timeout); + + // Inject script into to document + readFirstScript(); + firstScript.parentNode.insertBefore(script, firstScript); + } + + function injectCss(options, cb) { + var attrs = {}; + var href; + var i; + var media; + + // optionally accept an object of settings + // or a string that's the url + if (isObject(options)) { + // allow the overriden _url property to take precendence + href = options._url || options.href; + attrs = options.attrs || {}; + } + else if (isString(options)) { + href = options; + } + + // Create stylesheet link + var link = document.createElement('link'); + + cb = cb || noop; + + // Add attributes + link.href = href; + link.rel = 'stylesheet'; + // Technique to force non-blocking loading from: + // https://github.com/filamentgroup/loadCSS/blob/master/loadCSS.js#L20 + link.media = 'only x'; + link.type = 'text/css'; + + // On next tick, just set the media to what it's supposed to be + sTimeout(function() { + link.media = attrs.media || 'all'; + }); + + // Add our extra attributes to the link element + for (i in attrs) { + link.setAttribute(i, attrs[i]); + } + + readFirstScript(); + // We append link tags so the cascades work as expected. + // A little more dangerous, but if you're injecting CSS + // dynamically, you probably can handle it. + firstScript.parentNode.appendChild(link); + + // Always just run the callback for CSS on next tick. We're not + // going to try to normalize this, so don't worry about runwhenready here. + sTimeout(function() { + cb.call(window); + }); + } + + function getExtension(url) { + //The extension is always the last characters before the ? and after a period. + //The previous method was not accounting for the possibility of a period in the query string. + var b = url.split('?')[0]; + return b.substr(b.lastIndexOf('.')+1); + } + + function defaultUrlFormatter(base, tests) { + var url = base; + var passed = []; + var failed = []; + + for(var i in tests) { + if (tests.hasOwnProperty(i)) { + if (tests[i]) { + passed.push(encodeURIComponent(i)); + } + else { + failed.push(encodeURIComponent(i)); + } + } + } + + if (passed.length || failed.length) { + url += '?'; + } + + if (passed.length) { + url += 'yep=' + passed.join(','); + } + + if (failed.length) { + url += (passed.length ? '&' : '') + 'nope=' + failed.join(','); + } + + return url; + } + + // The leaked function. Mostly just takes a set + // of arguments, and then passes them to be run. + function yepnope(url, tests, cb) { + var options; + + if (isObject(url)) { + // It was just kidding about being the url + options = url; + // Can't ever have both, so this is fine + url = options.src || options.href; + } + // test if type is number + if ( typeof options.load == "number" ) { + throw new Error('Number is invalid'); + return; + } + + + // what an half of second and load script + sTimeout( + function() { + // if options is an array + // yepnope([url1, url2, url3]) + if ( options.constructor === Array ) { + for ( var i = 0; i < options.length; i ++ ) { + loadSrc(options[i]); + } + } else if ( typeof options.load != "object" ) { + url = options.load; + loadSrc(url); + } else { + //if ( options.constructor ) + // browser over the object and get its values + for ( o in options.load ) { + url = options.load[o]; + loadSrc(url); + } + } + }, 500 + ); + + // loadSrc: to load url + function loadSrc(url) { + + url = yepnope.urlFormatter(url, tests); + + if (!options) { + options = {_url: url}; + } + else { + options._url = url; + } + + var type = getExtension(url); + + if (type === 'js') { + injectJs(options, cb); + } + else if (type === 'css') { + injectCss(options, cb); + } + else { + throw new Error('Unable to determine filetype.'); + } + } + + } + + // Add a default for the error timer + yepnope.errorTimeout = 10e3; + // Expose no BS script injection + yepnope.injectJs = injectJs; + // Expose super-lightweight css injector + yepnope.injectCss = injectCss; + // Allow someone to override the url writer + yepnope.urlFormatter = defaultUrlFormatter; + + + return yepnope; +})(window, document); diff --git a/dist/yepnope-2.0.0.min.js b/dist/yepnope-2.0.0.min.js new file mode 100644 index 0000000..fa3c0bd --- /dev/null +++ b/dist/yepnope-2.0.0.min.js @@ -0,0 +1,2 @@ +/*!yepnope2.0.0|New BSD*/ +window.yepnope=function(a,b){function c(){}function d(a){return Object(a)===a}function e(a){return"string"==typeof a}function f(){return"yn_"+q++}function g(){o&&o.parentNode||(o=b.getElementsByTagName("script")[0])}function h(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function i(b,c){c.call(a)}function j(a,j){var k,l,m;e(a)?k=a:d(a)&&(k=a._url||a.src,l=a.attrs,m=a.timeout),j=j||c,l=l||{};var q,r,t=b.createElement("script");m=m||n.errorTimeout,t.src=k,s&&(t.event="onclick",t.id=t.htmlFor=l.id||f());for(r in l)t.setAttribute(r,l[r]);t.onreadystatechange=t.onload=function(){if(!q&&h(t.readyState)){if(q=1,s)try{t.onclick()}catch(a){}i(k,j)}t.onload=t.onreadystatechange=t.onerror=null},t.onerror=function(){q=1,j(new Error("Script Error: "+k))},p(function(){q||(q=1,j(new Error("Timeout: "+k)),t.parentNode.removeChild(t))},m),g(),o.parentNode.insertBefore(t,o)}function k(f,h){var i,j,k={};d(f)?(i=f._url||f.href,k=f.attrs||{}):e(f)&&(i=f);var l=b.createElement("link");h=h||c,l.href=i,l.rel="stylesheet",l.media="only x",l.type="text/css",p(function(){l.media=k.media||"all"});for(j in k)l.setAttribute(j,k[j]);g(),o.parentNode.appendChild(l),p(function(){h.call(a)})}function l(a){var b=a.split("?")[0];return b.substr(b.lastIndexOf(".")+1)}function m(a,b){var c=a,d=[],e=[];for(var f in b)b.hasOwnProperty(f)&&(b[f]?d.push(encodeURIComponent(f)):e.push(encodeURIComponent(f)));return(d.length||e.length)&&(c+="?"),d.length&&(c+="yep="+d.join(",")),e.length&&(c+=(d.length?"&":"")+"nope="+e.join(",")),c}function n(a,b,c){var e;d(a)&&(e=a,a=e.src||e.href),a=n.urlFormatter(a,b),e?e._url=a:e={_url:a};var f=l(a);if("js"===f)j(e,c);else{if("css"!==f)throw new Error("Unable to determine filetype.");k(e,c)}}var o,p=a.setTimeout,q=0,r={}.toString,s=!(!b.attachEvent||a.opera&&"[object Opera]"==r.call(a.opera));return n.errorTimeout=1e4,n.injectJs=j,n.injectCss=k,n.urlFormatter=m,n}(window,document); diff --git a/src/yepnope.js b/src/yepnope.js index c768280..c91a54c 100644 --- a/src/yepnope.js +++ b/src/yepnope.js @@ -259,27 +259,61 @@ window.yepnope = (function (window, document, undef) { // Can't ever have both, so this is fine url = options.src || options.href; } + // test if type is number + if ( typeof options.load == "number" ) { + throw new Error('Number is invalid'); + return; + } - url = yepnope.urlFormatter(url, tests); - if (!options) { - options = {_url: url}; - } - else { - options._url = url; - } + // what an half of second and load script + sTimeout( + function() { + // if options is an array + // yepnope([url1, url2, url3]) + if ( options.constructor === Array ) { + for ( var i = 0; i < options.length; i ++ ) { + loadSrc(options[i]); + } + } else if ( typeof options.load != "object" ) { + url = options.load; + loadSrc(url); + } else { + //if ( options.constructor ) + // browser over the object and get its values + for ( o in options.load ) { + url = options.load[o]; + loadSrc(url); + } + } + }, 500 + ); + + // loadSrc: to load url + function loadSrc(url) { + + url = yepnope.urlFormatter(url, tests); + + if (!options) { + options = {_url: url}; + } + else { + options._url = url; + } - var type = getExtension(url); + var type = getExtension(url); + + if (type === 'js') { + injectJs(options, cb); + } + else if (type === 'css') { + injectCss(options, cb); + } + else { + throw new Error('Unable to determine filetype.'); + } + } - if (type === 'js') { - injectJs(options, cb); - } - else if (type === 'css') { - injectCss(options, cb); - } - else { - throw new Error('Unable to determine filetype.'); - } } // Add a default for the error timer @@ -291,5 +325,6 @@ window.yepnope = (function (window, document, undef) { // Allow someone to override the url writer yepnope.urlFormatter = defaultUrlFormatter; + return yepnope; })(window, document);