(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 |
--------------------------------------------------------------------------------