├── .gitignore ├── demos ├── js │ ├── foo.js │ ├── baz.js │ ├── thunk.js │ └── bar.js ├── css │ └── demos.css ├── advanced.html ├── analytics.html └── basic.html ├── HISTORY.md ├── package.json ├── script.min.js ├── test └── index.js ├── script.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /demos/js/foo.js: -------------------------------------------------------------------------------- 1 | console.log('hello from foo! defining dependent vars...'); 2 | var bar = 'bar'; 3 | var baz = 'baz'; -------------------------------------------------------------------------------- /demos/js/baz.js: -------------------------------------------------------------------------------- 1 | console.log('loading baz...'); 2 | script.ready('main', function() { 3 | console.log('foo loaded: hello from ' + baz); 4 | window.thunky = 'ness'; 5 | }); -------------------------------------------------------------------------------- /demos/js/thunk.js: -------------------------------------------------------------------------------- 1 | console.log('loading thunk...'); 2 | script.ready('plugin', function() { 3 | console.log('bar & baz loaded'); 4 | console.log(thunkor + thunky); 5 | }); -------------------------------------------------------------------------------- /demos/js/bar.js: -------------------------------------------------------------------------------- 1 | console.log('loading bar...'); 2 | script.ready('main', function() { 3 | console.log('foo loaded: hello from ' + bar); 4 | window.thunkor = 'boosh'; 5 | }); -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## [v3.0.0] 2 | > Oct 5, 2016 3 | 4 | - Initial release under the `@rstacruz/scriptjs` fork. 5 | 6 | [v3.0.0]: https://github.com/rstacruz/script.js/tree/v3.0.0 7 | 8 | -------------------------------------------------------------------------------- /demos/css/demos.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 650px; 3 | margin: 10px auto; 4 | font: 300 16px 'helvetica neue', helvetica, arial; 5 | } 6 | h1 { 7 | border-bottom: 3px solid #333; 8 | } 9 | pre { 10 | overflow-x: auto; 11 | } -------------------------------------------------------------------------------- /demos/advanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | advanced $script.js example 5 | 6 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rstacruz/scriptjs", 3 | "description": "Asyncronous JavaScript loader and dependency manager", 4 | "keywords": [ 5 | "ender", 6 | "script", 7 | "dependency", 8 | "ajax", 9 | "jsonp", 10 | "loader" 11 | ], 12 | "version": "3.0.0", 13 | "homepage": "https://github.com/rstacruz/scriptjs", 14 | "author": "Dustin Diaz (http://dustindiaz.com)", 15 | "contributors": [ 16 | "Jacob Thornton (https://github.com/fat)", 17 | "Rico Sta. Cruz (http://ricostacruz.com)" 18 | ], 19 | "main": "index.js", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/rstacruz/scriptjs.git" 23 | }, 24 | "scripts": { 25 | "test": "tape test/*.js", 26 | "prepublish": "cat script.js | uglifyjs -c -m --define DEBUG=false --comments /!/ > script.min.js" 27 | }, 28 | "devDependencies": { 29 | "jquery": "3.1.1", 30 | "jsdom": "9.5.0", 31 | "jsdom-global": "2.1.0", 32 | "tape": "4.6.2", 33 | "tape-around": "2.2.0", 34 | "uglifyjs": "2.4.10" 35 | }, 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/rstacruz/scriptjs/issues" 39 | }, 40 | "directories": { 41 | "test": "test" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /script.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * scriptjs JS loader & dependency manager 3 | * https://github.com/rstacruz/scriptjs 4 | * (c) Dustin Diaz 2014 | Rico Sta. Cruz 2016 | License MIT 5 | */ 6 | !function(n,t){"undefined"!=typeof module&&module.exports?module.exports=t():"function"==typeof define&&define.amd?define(t):this[n]=t()}("$script",function(){function n(n,t){for(var e=0,o=n.length;o>e;++e)if(!t(n[e]))return c;return 1}function t(t,e){n(t,function(n){return e(n),1})}function e(r,u,f){function c(n){return n.call?n():s[n]}function l(){if(!--y){s[v]=1,g&&g();for(var e in m)n(e.split("|"),c)&&!t(m[e],c)&&(m[e]=[])}}r=r[a]?r:[r];var d=u&&u.call,g=d?u:f,v=d?r.join(""):u,y=r.length;return setTimeout(function(){t(r,function n(t,e){return null===t?l():(e||/^https?:\/\//.test(t)||!i||(t=-1===t.indexOf(".js")?i+t+".js":i+t),p[t]?(v&&(h[v]=1),2==p[t]?l():setTimeout(function(){n(t,!0)},0)):(p[t]=1,v&&(h[v]=1),void o(t,l)))})},0),e}function o(n,t){var e,o=u.createElement("script");o.onload=o.onerror=o[d]=function(){o[l]&&!/^c|loade/.test(o[l])||e||(o.onload=o[d]=null,e=1,p[n]=2,t())},o.async=1,o.src=r?n+(-1===n.indexOf("?")?"?":"&")+r:n,f.insertBefore(o,f.lastChild)}var i,r,u=document,f=u.getElementsByTagName("head")[0],c=!1,a="push",l="readyState",d="onreadystatechange",s={},h={},m={},p={};return e.get=o,e.order=function(n,t,o){!function i(r){r=n.shift(),n.length?e(r,i):e(r,t,o)}()},e.path=function(n){i=n},e.urlArgs=function(n){r=n},e.ready=function(o,i,r){o=o[a]?o:[o];var u=[];return!t(o,function(n){s[n]||u[a](n)})&&n(o,function(n){return s[n]})?i():!function(n){m[n]=m[n]||[],m[n][a](i),r&&r(u)}(o.join("|")),e},e.done=function(n){e([null],n)},e}); -------------------------------------------------------------------------------- /demos/analytics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | advanced $script.js example 5 | 6 | 7 | 8 | 9 | 10 |

Using with Google Analytics

11 |

As on twitter.com:

12 | 13 |
14 |           <script type="text/javascript">
15 |           var _gaq = _gaq || [];
16 |           _gaq.push(['_setAccount', 'UA-XXXXX-X']);
17 |           _gaq.push(
18 |             ['_trackPageview'],
19 |             ['_setDomainName', 'twitter.com']
20 |           );
21 | 
22 |           (function() {
23 |             var ga = document.createElement('script');
24 |             ga.type = 'text/javascript';
25 |             ga.async = true;
26 |             ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
27 |             var s = document.getElementsByTagName('script')[0];
28 |             s.parentNode.insertBefore(ga, s);
29 |           }());
30 |           </script>
31 |       
32 | 33 |

Which can be reduced to:

34 | 35 |
36 |           <script>
37 |           var _gaq=[['_setAccount','UA-XXXX-X'],['_trackPageview'],['_setDomainName','twitter.com']];
38 |           (function(d,t){
39 |               var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
40 |               g.async=1;
41 |               g.src='//www.google-analytics.com/ga.js';
42 |               s.parentNode.insertBefore(g,s)
43 |           }(document,'script'));
44 |           </script>
45 |       
46 | 47 |

And with $script.js:

48 | 49 |
50 |           <script>
51 |           $script("//www.google-analytics.com/ga.js", function(){
52 |               var t = _gat._getTracker ("UA-XXXXX-X");
53 |               t._setDomainName("twitter.com");
54 |               //t._initData();
55 |               t._trackPageview();
56 |           });
57 |           </script>
58 |       
59 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /demos/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $script.js demo 6 | 16 | 17 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | global.DEBUG = true 2 | 3 | require('jsdom-global')({ 4 | FetchExternalResources: ['script'], 5 | ProcessExternalResources: ['script'] 6 | }) 7 | 8 | var script = require('../index') 9 | var tape = require('tape') 10 | var around = require('tape-around') 11 | var $ = require('jquery') 12 | var JQUERY_URL = 'https://code.jquery.com/jquery-3.1.1.min.js' 13 | 14 | var test = around(tape) 15 | .before(function (t) { 16 | delete window.$ 17 | delete window.jQuery 18 | script.reset() 19 | $('script').remove() 20 | t.next() 21 | }) 22 | 23 | test('script()', function (t) { 24 | t.plan(1) 25 | 26 | script(JQUERY_URL, function () { 27 | t.equal($('script[src="' + JQUERY_URL + '"]').length, 1, 'script is loaded') 28 | t.end() 29 | }) 30 | }) 31 | 32 | test('script.ready', function (t) { 33 | t.plan(2) 34 | 35 | script.ready('jquery', function () { 36 | t.equal(typeof window.$, 'function', 'window.$ is available') 37 | t.end() 38 | }) 39 | 40 | script(JQUERY_URL, 'jquery', function() { 41 | t.pass('loaded from base callback') 42 | }) 43 | }) 44 | 45 | test('multiple files', function (t) { 46 | t.plan(3) 47 | 48 | script(['a.js', 'b.js'], function() { 49 | t.pass('loaded called') 50 | t.equal($('script[src="a.js"]').length, 1, 'a.js is loaded') 51 | t.equal($('script[src="b.js"]').length, 1, 'b.js is loaded') 52 | t.end() 53 | }) 54 | }) 55 | 56 | test('urlArgs', function (t) { 57 | script.urlArgs('key=value') 58 | script(['c.js'], function () { 59 | t.equal($('script[src="c.js?key=value"]').length, 1, 60 | 'loaded with urlArgs') 61 | t.end() 62 | }) 63 | }) 64 | 65 | test('order', function (t) { 66 | t.plan(1) 67 | script.order(['a', 'b', 'c'], 'ordered-id', function () { 68 | t.pass('loaded in order') 69 | }) 70 | script.ready('ordered-id', function () { 71 | t.end() 72 | }) 73 | }) 74 | 75 | test('script.done', function (t) { 76 | t.plan(2) 77 | var count = 0 78 | 79 | script.ready(['a', 'b'], function () { 80 | t.equal(++count, 2, 'ready([...]) called 2nd') 81 | t.end() 82 | }) 83 | 84 | script.ready('a', function () { 85 | t.equal(++count, 1, 'ready(...) called 1st') 86 | }) 87 | 88 | script.done('a') 89 | script.done('b') 90 | }) 91 | 92 | test('double callbacks', function (t) { 93 | var count = 0 94 | 95 | function load () { 96 | if (++count === 2) { 97 | t.pass('loaded callbacks twice') 98 | t.end() 99 | } 100 | } 101 | 102 | script('double-load', load) 103 | script('double-load', load) 104 | }) 105 | 106 | test('correctly count loaded scripts', function (t) { 107 | t.plan(2) 108 | 109 | script([ 110 | JQUERY_URL, 111 | 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.js' 112 | ], function () { 113 | t.equal(typeof window.$, 'function', 'loaded jquery from http') 114 | t.equal(typeof window.angular, 'object', 'loaded angular.js from http') 115 | done() 116 | }) 117 | }) 118 | 119 | test('adding .js', function (t) { 120 | t.plan(1) 121 | 122 | script.path('js/') 123 | script('foo', function () { 124 | t.equal($('script[src="js/foo.js"]').length, 1, 'script is loaded') 125 | t.end() 126 | }) 127 | }) 128 | 129 | test('adding .js on multiple files', function (t) { 130 | t.plan(2) 131 | 132 | script.path('js/') 133 | script(['foo', 'bar'], function () { 134 | t.equal($('script[src="js/foo.js"]').length, 1, 'script foo is loaded') 135 | t.equal($('script[src="js/bar.js"]').length, 1, 'script bar is loaded') 136 | t.end() 137 | }) 138 | }) 139 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * scriptjs JS loader & dependency manager 3 | * https://github.com/rstacruz/scriptjs 4 | * (c) Dustin Diaz 2014 | Rico Sta. Cruz 2016 | License MIT 5 | */ 6 | 7 | (function (name, definition) { 8 | if (typeof module != 'undefined' && module.exports) module.exports = definition() 9 | else if (typeof define == 'function' && define.amd) define(definition) 10 | else this[name] = definition() 11 | })('$script', function () { 12 | var doc = document 13 | , head = doc.getElementsByTagName('head')[0] 14 | , s = 'string' 15 | , f = false 16 | , push = 'push' 17 | , readyState = 'readyState' 18 | , onreadystatechange = 'onreadystatechange' 19 | , list = {} 20 | , ids = {} 21 | , delay = {} 22 | , scripts = {} 23 | , scriptpath 24 | , urlArgs 25 | 26 | function every(ar, fn) { 27 | for (var i = 0, j = ar.length; i < j; ++i) if (!fn(ar[i])) return f 28 | return 1 29 | } 30 | function each(ar, fn) { 31 | every(ar, function (el) { 32 | fn(el) 33 | return 1 34 | }) 35 | } 36 | 37 | function $script(paths, idOrDone, optDone) { 38 | paths = paths[push] ? paths : [paths] 39 | var idOrDoneIsDone = idOrDone && idOrDone.call 40 | , done = idOrDoneIsDone ? idOrDone : optDone 41 | , id = idOrDoneIsDone ? paths.join('') : idOrDone 42 | , queue = paths.length 43 | function loopFn(item) { 44 | return item.call ? item() : list[item] 45 | } 46 | function callback() { 47 | if (!--queue) { 48 | list[id] = 1 49 | done && done() 50 | for (var dset in delay) { 51 | every(dset.split('|'), loopFn) && !each(delay[dset], loopFn) && (delay[dset] = []) 52 | } 53 | } 54 | } 55 | setTimeout(function () { 56 | each(paths, function loading(path, force) { 57 | if (path === null) return callback() 58 | 59 | if (!force && !/^https?:\/\//.test(path) && scriptpath) { 60 | path = (path.indexOf('.js') === -1) ? scriptpath + path + '.js' : scriptpath + path 61 | } 62 | 63 | if (scripts[path]) { 64 | if (id) ids[id] = 1 65 | return (scripts[path] == 2) ? callback() : setTimeout(function () { loading(path, true) }, 0) 66 | } 67 | 68 | scripts[path] = 1 69 | if (id) ids[id] = 1 70 | create(path, callback) 71 | }) 72 | }, 0) 73 | return $script 74 | } 75 | 76 | function create(path, fn) { 77 | var el = doc.createElement('script'), loaded 78 | el.onload = el.onerror = el[onreadystatechange] = function () { 79 | if ((el[readyState] && !(/^c|loade/.test(el[readyState]))) || loaded) return 80 | el.onload = el[onreadystatechange] = null 81 | loaded = 1 82 | scripts[path] = 2 83 | fn() 84 | } 85 | el.async = 1 86 | el.src = urlArgs ? path + (path.indexOf('?') === -1 ? '?' : '&') + urlArgs : path 87 | head.insertBefore(el, head.lastChild) 88 | } 89 | 90 | $script.get = create 91 | 92 | $script.order = function (scripts, id, done) { 93 | (function callback(s) { 94 | s = scripts.shift() 95 | !scripts.length ? $script(s, id, done) : $script(s, callback) 96 | }()) 97 | } 98 | 99 | $script.path = function (p) { 100 | scriptpath = p 101 | } 102 | $script.urlArgs = function (str) { 103 | urlArgs = str 104 | } 105 | $script.ready = function (deps, ready, req) { 106 | deps = deps[push] ? deps : [deps] 107 | var missing = [] 108 | !each(deps, function (dep) { 109 | list[dep] || missing[push](dep) 110 | }) && every(deps, function (dep) {return list[dep]}) ? 111 | ready() : !function (key) { 112 | delay[key] = delay[key] || [] 113 | delay[key][push](ready) 114 | req && req(missing) 115 | }(deps.join('|')) 116 | return $script 117 | } 118 | 119 | $script.done = function (idOrDone) { 120 | $script([null], idOrDone) 121 | } 122 | 123 | /** 124 | * Resets custom settings. Used for tests. 125 | * @private 126 | */ 127 | 128 | if (typeof DEBUG !== 'undefined' && DEBUG) { 129 | $script.reset = function () { 130 | scriptpath = undefined 131 | urlArgs = undefined 132 | list = {} 133 | ids = {} 134 | delay = {} 135 | scripts = {} 136 | } 137 | } 138 | 139 | return $script 140 | }) 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scriptjs 2 | 3 | > Async JavaScript loader & dependency manager 4 | 5 | `scriptjs` is an asynchronous JavaScript loader and dependency manager with an astonishingly impressive lightweight footprint. Like many other script loaders, scriptjs allows you to load script resources on-demand from any URL and not block other resources from loading (like CSS and images). Furthermore, it's unique interface allows developers to work easily with even the most complicated dependencies, which can often be the case for large, complex web applications. 6 | 7 | ## Fork information 8 | 9 | :warning: This is a fork by [@rstacruz](https://github.com/rstacruz) of script.js originally by [Dustin Diaz](http://github.com/ded), formerly in [ded/script.js](https://github.com/ded/script.js). This fork adds no functionality, but cleans up the code a bit. 10 | 11 | - Removes the smoosh-based build system 12 | - Replaces the browser tests with jsdom-based unit tests 13 | - Ender support is removed 14 | - Documentation is improved 15 | - Renamed from `$script.js` to the less-ambiguous `scriptjs` 16 | 17 | ## Installation 18 | 19 | scriptjs is available via npm as `@rstacruz/scriptjs`. It's then available as `require('@rstacruz/scriptjs')` for use in Webpack/Browserify/Brunch. 20 | 21 | ``` 22 | npm install --save @rstacruz/scriptjs 23 | ``` 24 | 25 | ## Standalone builds 26 | 27 | Standalone builds are available here: 28 | 29 | - (latest) 30 | - (specific version) 31 | 32 | You can access it as `window.$script`. 33 | 34 | ## Browser Support 35 | 36 | * IE 6+ 37 | * Opera 10+ 38 | * Safari 3+ 39 | * Chrome 1+ 40 | * Firefox 2+ 41 | 42 | ## Examples 43 | 44 | old school - blocks CSS, Images, AND JS! 45 | 46 | ``` html 47 | 48 | 49 | 50 | ``` 51 | 52 | middle school - loads as non-blocking, but has multiple dependents 53 | 54 | ``` js 55 | $script('jquery.js', function () { 56 | $script('my-jquery-plugin.js', function () { 57 | $script('my-app-that-uses-plugin.js') 58 | }) 59 | }) 60 | ``` 61 | 62 | new school - loads as non-blocking, and ALL js files load asynchronously 63 | 64 | ``` js 65 | // load jquery and plugin at the same time. name it 'bundle' 66 | $script(['jquery.js', 'my-jquery-plugin.js'], 'bundle') 67 | 68 | // load your usage 69 | $script('my-app-that-uses-plugin.js') 70 | 71 | 72 | /*--- in my-jquery-plugin.js ---*/ 73 | $script.ready('bundle', function() { 74 | // jquery & plugin (this file) are both ready 75 | // plugin code... 76 | }) 77 | 78 | 79 | /*--- in my-app-that-uses-plugin.js ---*/ 80 | $script.ready('bundle', function() { 81 | // use your plugin :) 82 | }) 83 | ``` 84 | 85 | ## Exhaustive list of ways to use $script.js 86 | 87 | ``` js 88 | $script('foo.js', function() { 89 | // foo.js is ready 90 | }) 91 | 92 | 93 | $script(['foo.js', 'bar.js'], function() { 94 | // foo.js & bar.js is ready 95 | }) 96 | 97 | 98 | $script(['foo.js', 'bar.js'], 'bundle') 99 | $script.ready('bundle', function() { 100 | // foo.js & bar.js is ready 101 | }) 102 | 103 | // create an id and callback inline 104 | $script(['foo.js', 'bar.js'], 'bundle', function () { 105 | // foo.js & bar.js is ready 106 | }) 107 | 108 | 109 | $script('foo.js', 'foo') 110 | $script('bar.js', 'bar') 111 | $script 112 | .ready('foo', function() { 113 | // foo.js is ready 114 | }) 115 | .ready('bar', function() { 116 | // bar.js is ready 117 | }) 118 | 119 | 120 | var dependencyList = { 121 | foo: 'foo.js' 122 | , bar: 'bar.js' 123 | , thunk: ['thunkor.js', 'thunky.js'] 124 | } 125 | 126 | $script('foo.js', 'foo') 127 | $script('bar.js', 'bar') 128 | 129 | // wait for multiple depdendencies! 130 | $script.ready(['foo', 'bar', 'thunk'], function () { 131 | // foo.js & bar.js & thunkor.js & thunky.js is ready 132 | }, function(depsNotFound) { 133 | // foo.js & bar.js may have downloaded 134 | // but ['thunk'] dependency was never found 135 | // so lazy load it now 136 | depsNotFound.forEach(function(dep) { 137 | $script(dependencyList[dep], dep) 138 | }) 139 | }) 140 | 141 | 142 | // in my-awesome-plugin.js 143 | $script.ready('jquery', function() { 144 | //define awesome jquery plugin here 145 | $script.done('my-awesome-plugin') 146 | }) 147 | 148 | // in index.html 149 | $script('jquery.js', 'jquery') 150 | $script('my-awesome-plugin.js') 151 | $script.ready('my-awesome-plugin', function() { 152 | //run code here when jquery and my awesome plugin are both ready 153 | }) 154 | ``` 155 | 156 | ## API 157 | 158 | ### $script.path() 159 | 160 | Optionally to make working with large projects easier, there is a path variable you can set to set as a base. 161 | 162 | ``` js 163 | $script.path('/js/modules/') 164 | $script(['dom', 'event'], function () { 165 | // use dom & event 166 | }); 167 | ``` 168 | 169 | Note that this will include all scripts from here on out with the base path. If you wish to circumvent this for any single script, you can simply call $script.get() 170 | 171 | ``` js 172 | $script.path('/js/modules/') 173 | $script(['dom', 'event'], function () { 174 | // use dom & event 175 | }) 176 | 177 | $script.get('http://example.com/base.js', function () { 178 | 179 | }) 180 | ``` 181 | 182 | ### $script.urlArgs() 183 | As of 2.5.5 it's possible to concat URL arguments (i.e. a query string) to the script path. 184 | This is especially useful when you're in need of a cachebuster and works as follows: 185 | 186 | ```js 187 | $script.urlArgs('key=value&foo=bar'); 188 | ``` 189 | 190 | Please note that Squid, a popular proxy, doesn’t cache resources with a querystring. This hurts performance when multiple users behind a proxy cache request the same file – rather than using the cached version everybody would have to send a request to the origin server. So ideally, [as Steve Souders points out](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/), you should rev the filename itself. 191 | --------------------------------------------------------------------------------