loada 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a69f44bcf2fc9eb668f80fa257cc1a64c63d1c0f
4
+ data.tar.gz: 282b21259d8ee5f8a3a3f57be5498e64f8886687
5
+ SHA512:
6
+ metadata.gz: b43a7aaeb2a8b03a748c5b39dde9a0fdec9e4edf74e02b478b5079004b928efe97f3038c9ae17ec466d33e9140e6d3cae385a251f52280ae7a51ae29ee2dcb28
7
+ data.tar.gz: 9d3130489c18dde76b39af8d9f5ef332272213fdf85f824e87d1de06731fbd165127ef634548faab2803eeca07cacce39636f43d24f19dcce80e1484710ebda5
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .grunt
2
+ node_modules
3
+ index.html
4
+ components
5
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: node_js
2
+ node_js:
3
+ - 0.8
4
+ before_script:
5
+ - npm install -g grunt-cli
6
+ - ./node_modules/.bin/bower install
7
+ script: "grunt test"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
data/Gruntfile.coffee ADDED
@@ -0,0 +1,106 @@
1
+ module.exports = (grunt) ->
2
+
3
+ grunt.loadNpmTasks 'grunt-contrib-connect'
4
+ grunt.loadNpmTasks 'grunt-contrib-coffee'
5
+ grunt.loadNpmTasks 'grunt-contrib-jasmine'
6
+ grunt.loadNpmTasks 'grunt-contrib-watch'
7
+ grunt.loadNpmTasks 'grunt-contrib-uglify'
8
+ grunt.loadNpmTasks 'grunt-coffeelint'
9
+ grunt.loadNpmTasks 'grunt-release'
10
+
11
+ grunt.initConfig
12
+ watch:
13
+ source:
14
+ files: ['src/**/*.coffee']
15
+ tasks: ['coffee:source']
16
+ specs:
17
+ files: ['spec/**/*.coffee']
18
+ tasks: ['coffee:specs']
19
+
20
+ coffee:
21
+ source:
22
+ files:
23
+ 'lib/loada.js': 'src/loada.coffee'
24
+
25
+ specs:
26
+ expand: true
27
+ src: ["spec/**/*.coffee"]
28
+ dest: '.grunt'
29
+ ext: '.js'
30
+
31
+ coffeelint:
32
+ source:
33
+ files:
34
+ src: ['src/**/*.coffee']
35
+ options:
36
+ 'max_line_length':
37
+ level: 'ignore'
38
+
39
+ connect:
40
+ specs:
41
+ options:
42
+ port: 8888
43
+
44
+ release:
45
+ options:
46
+ bump: false
47
+ add: false
48
+ commit: false
49
+ push: false
50
+
51
+ jasmine:
52
+ core:
53
+ options:
54
+ host: 'http://localhost:8888/'
55
+ keepRunner: true
56
+ outfile: "index.html"
57
+ vendor: 'components/sinonjs/sinon.js'
58
+ specs: ".grunt/spec/**/*_spec.js"
59
+ helpers: ".grunt/spec/helpers/**/*.js"
60
+ src: "lib/loada.js"
61
+
62
+ uglify:
63
+ core:
64
+ files:
65
+ 'lib/loada.min.js': 'lib/loada.js'
66
+
67
+ grunt.registerTask 'default', ['coffee', 'connect', 'jasmine:core:build', 'watch']
68
+
69
+ grunt.registerTask 'test', ['coffee', 'connect', 'coffeelint', 'jasmine', 'bowerize']
70
+
71
+ grunt.registerTask 'publish', ['test', 'publish:ensureCommits', 'release', 'publish:gem']
72
+
73
+ grunt.registerTask 'bowerize', ->
74
+ bower = require './bower.json'
75
+ meta = require './package.json'
76
+
77
+ bower.version = meta.version
78
+ require('fs').writeFileSync 'bower.json', JSON.stringify(bower, null, 2)
79
+
80
+ grunt.registerTask 'publish:ensureCommits', ->
81
+ complete = @async()
82
+
83
+ grunt.util.spawn {cmd: "git", args: ["status", "--porcelain" ]}, (error, result) ->
84
+ if !!error || result.stdout.length > 0
85
+ console.log ""
86
+ console.log "Uncommited changes found. Please commit prior to release or use `--force`.".bold
87
+ console.log ""
88
+ complete false
89
+ else
90
+ complete true
91
+
92
+ grunt.registerTask 'publish:gem', ->
93
+ meta = require './package.json'
94
+ complete = @async()
95
+
96
+ grunt.util.spawn {cmd: "gem", args: ["build", "loada.gemspec"]}, (error, result) ->
97
+ return complete false if error
98
+
99
+ gem = "loada-#{meta.version.replace('-', '.')}.gem"
100
+ grunt.log.ok "Built #{gem}"
101
+
102
+ grunt.util.spawn {cmd: "gem", args: ["push", gem]}, (error, result) ->
103
+ return complete false if error
104
+ grunt.log.ok "Published #{gem}"
105
+ grunt.file.delete gem
106
+ complete(true)
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # The Loada
2
+
3
+ [![NPM version](https://badge.fury.io/js/loada.png)](http://badge.fury.io/js/loada)
4
+ [![Build Status](https://travis-ci.org/inossidabile/loada.png?branch=master)](https://travis-ci.org/inossidabile/loada)
5
+
6
+ The kickass in-browser assets (JS/CSS/Text) loader with the support of localStorage caching and progress tracking. The Loada is here to give you control over assets loading order, parallelism, cache expiration and actual code evaluation.
7
+
8
+ Use it to:
9
+
10
+ * Make beautiful preloader of a big RICH browser application
11
+ * Manually control assets cache
12
+ * Preload and manage your code partially in the background (templates, extensions, etc...)
13
+
14
+ ## Installation
15
+
16
+ The Loada is available through [Bower](http://bower.io) as `loada`.
17
+
18
+ Alternatively you can download [minified version](https://raw.github.com/inossidabile/loada/master/lib/loada.min.js). But you should not. Seriously. Use package managers. Please.
19
+
20
+ ## Basic usage
21
+
22
+ The Loada splits your dependencies into sets. Each set is an instance of The Loada. Each set is an atomic cache part: if the number of cached assets changes – unused caches get busted on the next run keeping your **localStorage** space usage low. You can i.e. have *main* set, the core of application, and *templates* that will be treated and cached separately.
23
+
24
+ ---
25
+
26
+ Create a set to start:
27
+
28
+ ```coffee
29
+ loader = Loada.set('dependencies') # name of the set defaults to `*` if omitted.
30
+ ```
31
+
32
+ To load an asset with the default options run
33
+
34
+ ```coffee
35
+ loader.require url: '/assets/application.css'
36
+ ```
37
+
38
+ To load several assets one after another (they depend on each other?) run
39
+
40
+ ```coffee
41
+ loader.require {url: '/assets/jquery.js'}, {url: '/assets/application.js'}
42
+ ```
43
+
44
+ And to load an external library that shouldn't be cached at **localStorage** (but has to be kept at
45
+ the browser cache) run
46
+
47
+ ```coffee
48
+ loader.require {url: 'http://../facebook.js', localStorage: false}
49
+ ```
50
+
51
+ According to our requires, the assets will be loaded in the following groups (:
52
+
53
+ * **/assets/application.css** in parallel with
54
+ * ( **/assets/jquery.js** and then **/assets/appliation.js** ) in parallel with
55
+ * **http://../facebook.js**
56
+
57
+ And now that we have our dependencies specified, load'em!
58
+
59
+ ```coffee
60
+ loader.load
61
+ # During downloading you will get this callback called from time to time
62
+ progress: (percents) -> # ...
63
+
64
+ # And this one will run as soon as all assets are around
65
+ complete: -> # ...
66
+ ```
67
+
68
+ ## Progress tracker limitations and advanced usage
69
+
70
+ The Loada will try to make progress ticks as often as possible. There are some things you should keep in mind to sort it out though:
71
+
72
+ * You can pass in `size` to the asset options to specify download size. This is the most efficient scenario so if you have a chance to count size programmatically – do that.
73
+ * The Loada will run separate `HEAD` query for every asset with unknown size expecting server to return `Content-Length`.
74
+ * During downloading it will tick exact percentage for assets with known size and two points at start and finish for every other.
75
+
76
+ So in the worst case when The Loada was not able to find sizes from any source, you will get percentage split to the number of loading assets. In the best case, when all sizes are known, ticks will happen every 100ms and will be pretty detailed.
77
+
78
+ **Important note**: The Loada WILL NOT make separate `HEAD` queries unless you pass `progress` callback. So if you don't need the progress tracking feature – you are not hitting the overhead. In the case when you passed `progress` in and still want to avoid `HEAD` queries (resulting into "per-library" percentage), pass `0` as the `size` value to every asset.
79
+
80
+ ## Options and Methods
81
+
82
+ ### Instance-level options
83
+
84
+ ```coffee
85
+ set = Loada.set 'name',
86
+ prefix: 'foobar'
87
+ localStorage: false
88
+ ```
89
+
90
+ * **prefix**: changes localStorage keys prefix (defaults to `loada`)
91
+ * **localStorage**: globally turns off the localStorage cache and (unfortunately) detailed progress tracking
92
+
93
+ **Important note**: The Loada is only able to work with JS with localStorage mode turned off.
94
+
95
+ ### Asset-level options
96
+
97
+ ```coffee
98
+ set.require
99
+ url: '/assets/application.js'
100
+ key: 'app'
101
+ type: 'js'
102
+ revision: 1
103
+ expires: 5
104
+ cache: true
105
+ require: true
106
+ size: 0
107
+ ```
108
+ * **url**: URL to download asset from
109
+ * **key**: key to use to store asset within the set (defaults to `url`)
110
+ * **type**: type of the asset: `css`, `js` or `text` (The Loada tries to parse URL to get extension if omitted)
111
+ * **revision**: asset revision to control cache expiration manually – cache will get busted if new value of this option doesn't match the stored one
112
+ * **expires**: number of hours to keep asset for (defaults to forever cache)
113
+ * **cache**: pass `false` in to turn localStorage caching off for this particular asset
114
+ * **require**: whether asset should be automatically required or just downloaded and cached
115
+ * **size**: downloadable size of asset to help The Loada with detailed progress tracking
116
+
117
+ ### Instance-level methods
118
+
119
+ #### **get**
120
+
121
+ Gets raw asset source by its key
122
+
123
+ ```coffee
124
+ source = set.get 'app'
125
+ ```
126
+
127
+ #### **expire**
128
+
129
+ Manually triggers expiration check for current storage
130
+
131
+ ```coffee
132
+ set.expire()
133
+ ```
134
+
135
+ #### **clear**
136
+
137
+ Busts all cache of the current set
138
+
139
+ ```coffee
140
+ set.clear()
141
+ ```
142
+
143
+ ## History
144
+
145
+ Loada is an extraction from [Joosy](http://joosy.ws) core.
146
+
147
+ ## Credits
148
+
149
+ * Boris Staal ([@_inossidabile](http://twitter.com/#!/_inossidabile)) [![endorse](http://api.coderwall.com/inossidabile/endorsecount.png)](http://coderwall.com/inossidabile)
150
+
151
+ ## LICENSE
152
+
153
+ It is free software, and may be redistributed under the terms of MIT license.
data/bower.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "loada",
3
+ "version": "0.1.3",
4
+ "main": "lib/loada.min.js",
5
+ "ignore": [
6
+ "Gruntfile.coffee",
7
+ "package.json",
8
+ "spec",
9
+ ".travis.yml",
10
+ ".gitignore",
11
+ "lib/loada.rb",
12
+ "loada.gemspec",
13
+ "Gemfile"
14
+ ],
15
+ "devDependencies": {
16
+ "sinonjs": "~1.7.1"
17
+ }
18
+ }
data/lib/loada.js ADDED
@@ -0,0 +1,321 @@
1
+ (function() {
2
+ var $head,
3
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
4
+ __slice = [].slice;
5
+
6
+ $head = document.getElementsByTagName("head")[0];
7
+
8
+ this.Loada = (function() {
9
+ Loada.prototype.Progress = (function() {
10
+ function _Class(count, progressCallback) {
11
+ this.count = count;
12
+ this.progressCallback = progressCallback;
13
+ this.data = {};
14
+ }
15
+
16
+ _Class.prototype.set = function(key, percent) {
17
+ this.data[key] = Math.min(percent, 100.0);
18
+ return typeof this.progressCallback === "function" ? this.progressCallback(this.total()) : void 0;
19
+ };
20
+
21
+ _Class.prototype.total = function() {
22
+ var key, percent, total, _ref;
23
+ total = 0;
24
+ _ref = this.data;
25
+ for (key in _ref) {
26
+ percent = _ref[key];
27
+ total += Math.round(percent / this.count * 100) / 100;
28
+ }
29
+ return total;
30
+ };
31
+
32
+ return _Class;
33
+
34
+ })();
35
+
36
+ Loada.set = function() {
37
+ return (function(func, args, ctor) {
38
+ ctor.prototype = func.prototype;
39
+ var child = new ctor, result = func.apply(child, args);
40
+ return Object(result) === result ? result : child;
41
+ })(this, arguments, function(){});
42
+ };
43
+
44
+ function Loada(set, options) {
45
+ var k, v;
46
+ this.set = set;
47
+ this._loadGroup = __bind(this._loadGroup, this);
48
+ this.options = {
49
+ prefix: 'loada',
50
+ localStorage: true
51
+ };
52
+ this.requires = {
53
+ input: [],
54
+ set: {},
55
+ length: 0
56
+ };
57
+ this.set || (this.set = '*');
58
+ if (options) {
59
+ for (k in options) {
60
+ v = options[k];
61
+ this.options[k] = v;
62
+ }
63
+ }
64
+ this.key = "" + this.options.prefix + "." + this.set;
65
+ this.setup();
66
+ }
67
+
68
+ Loada.prototype.setup = function() {
69
+ if (this.options.localStorage) {
70
+ this.storage = localStorage[this.key] || {};
71
+ if (typeof this.storage === 'string') {
72
+ return this.storage = JSON.parse(localStorage[this.key]);
73
+ }
74
+ }
75
+ };
76
+
77
+ Loada.prototype.save = function() {
78
+ return localStorage[this.key] = JSON.stringify(this.storage);
79
+ };
80
+
81
+ Loada.prototype.clear = function() {
82
+ return delete localStorage[this.key];
83
+ };
84
+
85
+ Loada.prototype.get = function(key) {
86
+ var _ref;
87
+ return (_ref = this.storage[key]) != null ? _ref.source : void 0;
88
+ };
89
+
90
+ Loada.prototype.expire = function() {
91
+ var byDate, byExistance, byRevision, key, library, now, _ref, _results,
92
+ _this = this;
93
+ now = new Date;
94
+ byDate = function(library) {
95
+ return library.expirationDate && new Date(library.expirationDate) <= now;
96
+ };
97
+ byExistance = function(library) {
98
+ return !_this.requires.set[key];
99
+ };
100
+ byRevision = function(library) {
101
+ return _this.requires.set[key].revision !== library.revision;
102
+ };
103
+ _ref = this.storage;
104
+ _results = [];
105
+ for (key in _ref) {
106
+ library = _ref[key];
107
+ if (byDate(library) || byExistance(library) || byRevision(library)) {
108
+ _results.push(delete this.storage[key]);
109
+ } else {
110
+ _results.push(void 0);
111
+ }
112
+ }
113
+ return _results;
114
+ };
115
+
116
+ Loada.prototype.require = function() {
117
+ var libraries, library, now, _i, _len, _ref;
118
+ libraries = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
119
+ for (_i = 0, _len = libraries.length; _i < _len; _i++) {
120
+ library = libraries[_i];
121
+ library.key || (library.key = library.url);
122
+ library.type || (library.type = (_ref = library.url) != null ? _ref.split('.').pop() : void 0);
123
+ if (library.localStorage == null) {
124
+ library.localStorage = true;
125
+ }
126
+ if (library.require == null) {
127
+ library.require = true;
128
+ }
129
+ if (library.expires) {
130
+ now = new Date;
131
+ library.expirationDate = now.setTime(now.getTime() + library.expires * 60 * 60 * 1000);
132
+ }
133
+ if (library.type === 'js' || library.type === 'css' || library.type === 'text') {
134
+ this.requires.set[library.key] = library;
135
+ } else {
136
+ console.error("Unknown asset type for " + library.url + " – skipped");
137
+ }
138
+ }
139
+ this.requires.length += libraries.length;
140
+ return this.requires.input.push(libraries);
141
+ };
142
+
143
+ Loada.prototype.load = function(callbacks) {
144
+ var loaders, progress,
145
+ _this = this;
146
+ callbacks || (callbacks = {});
147
+ if (this.options.localStorage) {
148
+ this.expire();
149
+ }
150
+ progress = new this.Progress(this.requires.length, callbacks.progress);
151
+ loaders = 0;
152
+ return this._ensureSizes(callbacks.progress != null, function() {
153
+ var group, _i, _len, _ref, _results;
154
+ _ref = _this.requires.input;
155
+ _results = [];
156
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
157
+ group = _ref[_i];
158
+ loaders++;
159
+ _results.push(_this._loadGroup(group.slice(0), progress, function() {
160
+ loaders--;
161
+ if (loaders === 0) {
162
+ _this.save();
163
+ return typeof callbacks.success === "function" ? callbacks.success() : void 0;
164
+ }
165
+ }));
166
+ }
167
+ return _results;
168
+ });
169
+ };
170
+
171
+ Loada.prototype._ensureSizes = function(perform, callback) {
172
+ var key, library, requests, _ref, _ref1, _results,
173
+ _this = this;
174
+ if (!perform) {
175
+ _ref = this.requires.set;
176
+ for (key in _ref) {
177
+ library = _ref[key];
178
+ library.size = 0;
179
+ }
180
+ return callback();
181
+ }
182
+ requests = 0;
183
+ _ref1 = this.requires.set;
184
+ _results = [];
185
+ for (key in _ref1) {
186
+ library = _ref1[key];
187
+ _results.push((function(library) {
188
+ if (library.size == null) {
189
+ requests++;
190
+ return _this._ajax('HEAD', library.url, function(xhr) {
191
+ var size;
192
+ requests--;
193
+ size = xhr.getResponseHeader('Content-Length');
194
+ if (size) {
195
+ size = parseInt(size);
196
+ }
197
+ library.size = size || 0;
198
+ if (requests === 0) {
199
+ return callback();
200
+ }
201
+ });
202
+ }
203
+ })(library));
204
+ }
205
+ return _results;
206
+ };
207
+
208
+ Loada.prototype._loadGroup = function(group, progress, callback) {
209
+ var library, method,
210
+ _this = this;
211
+ library = group.shift();
212
+ if (!library) {
213
+ return callback();
214
+ }
215
+ if (this.options.localStorage && this.storage[library.key] && library.localStorage) {
216
+ if (progress != null) {
217
+ progress.set(library.key, 100);
218
+ }
219
+ if (this.storage[library.key].require) {
220
+ this._inject(this.storage[library.key]);
221
+ }
222
+ return this._loadGroup(group, progress, callback);
223
+ } else {
224
+ method = this.options.localStorage ? "_loadAJAX" : "_loadInline";
225
+ return this[method](library, progress, function() {
226
+ if (_this.options.localStorage) {
227
+ _this.storage[library.key] = library;
228
+ }
229
+ if (library.require) {
230
+ _this._inject(library);
231
+ }
232
+ return _this._loadGroup(group, progress, callback);
233
+ });
234
+ }
235
+ };
236
+
237
+ Loada.prototype._loadAJAX = function(library, progress, callback) {
238
+ var poller, xhr,
239
+ _this = this;
240
+ xhr = this._ajax('GET', library.url, function(xhr) {
241
+ library.source = xhr.responseText;
242
+ clearInterval(poller);
243
+ if (progress != null) {
244
+ progress.set(library.key, 100);
245
+ }
246
+ return callback();
247
+ });
248
+ if (library.size > 0) {
249
+ return poller = setInterval((function() {
250
+ var percent;
251
+ percent = Math.round(xhr.responseText.length / library.size * 100 * 100) / 100;
252
+ return progress != null ? progress.set(library.key, percent) : void 0;
253
+ }), 100);
254
+ }
255
+ };
256
+
257
+ Loada.prototype._loadInline = function(library, progress, callback) {
258
+ var done, proceed, script;
259
+ if (library.type !== 'js') {
260
+ console.error("Attempt to load something other than JS without localStorage.");
261
+ console.error("" + library.url + " is not loaded!");
262
+ if (progress != null) {
263
+ progress.set(library.key, 100);
264
+ }
265
+ return callback();
266
+ }
267
+ script = document.createElement("script");
268
+ done = false;
269
+ proceed = function() {
270
+ if (!done && ((this.readyState == null) || this.readyState === "loaded" || this.readyState === "complete")) {
271
+ done = true;
272
+ if (progress != null) {
273
+ progress.set(library.key, 100);
274
+ }
275
+ if (typeof callback === "function") {
276
+ callback();
277
+ }
278
+ return script.onload = script.onreadystatechange = null;
279
+ }
280
+ };
281
+ script.onload = script.onreadystatechange = proceed;
282
+ script.src = library.url;
283
+ return $head.appendChild(script);
284
+ };
285
+
286
+ Loada.prototype._inject = function(library) {
287
+ var script, style;
288
+ if (library.type === 'js') {
289
+ script = document.createElement("script");
290
+ script.defer = true;
291
+ script.text = library.source;
292
+ return $head.appendChild(script);
293
+ } else if (library.type === 'css') {
294
+ style = document.createElement("style");
295
+ style.innerHTML = library.source;
296
+ return $head.appendChild(style);
297
+ }
298
+ };
299
+
300
+ Loada.prototype._ajax = function(method, url, callback) {
301
+ var xhr;
302
+ if (window.XMLHttpRequest) {
303
+ xhr = new XMLHttpRequest;
304
+ } else {
305
+ xhr = new ActiveXObject('Microsoft.XMLHTTP');
306
+ }
307
+ xhr.open(method, url, 1);
308
+ xhr.onreadystatechange = function() {
309
+ if (xhr.readyState > 3) {
310
+ return typeof callback === "function" ? callback(xhr) : void 0;
311
+ }
312
+ };
313
+ xhr.send();
314
+ return xhr;
315
+ };
316
+
317
+ return Loada;
318
+
319
+ })();
320
+
321
+ }).call(this);