5
\$\begingroup\$

I just want to know if this is reasonably good implementation of including local/remote JavaScript files.

The function takes a dictionary with (string) .src whitespace-separated list of urls to include/execute in global context, and (func) .done/.error/.load callbacks to run in corresponding cases, resolves URLs to absolute, temporarily inserts <script> blocks in page header, caches loaded addresses, and attaches few static properties: (object) .defaults and (func) .ls/.reload to main includejs() function.

// #includejs
;((function (name, def) {
 this[name] = def(document);
}).call(
 this, "includejs", function (doc) {
 // will be used to reference private includejs() version
 var _include;
 // holds cached script urls
 var imported = {};
 // no-op callback default
 var pass = function () {};
 // helpers
 var _ = {
 // <a> helper element to resolve urls to absolute
 anchor: doc.createElement("a"), 
 // calculates difference of two arrays
 // used by includejs() to filter new urls to load
 arrdiff: function (a1, a2) {
 return a1.filter(_.cbuniq).filter(_.cbdiff, a2);
 }, 
 // calculates intersection of two arrays
 // used by includejs.reload() to filter cached urls
 arrinters: function (a1, a2) {
 return a1.filter(_.cbuniq).filter(_.cbinters, a2);
 }, 
 cbdiff: function (node) {
 //this: a2[]
 return this.indexOf(node) == -1;
 }, 
 cbinters: function (node) {
 //this: a2[]
 return this.indexOf(node) != -1;
 }, 
 cbpropcp: function (name) {
 //this: {src{}, target{}}
 this.target[name] = this.src[name];
 }, 
 cbrmprop: function (name) {
 //this: target{}
 try {
 delete this[name];
 } catch (e) {}
 }, 
 cbuniq: function (node, idx, arr) {
 return idx <= arr.indexOf(node);
 }, 
 // shallow copies an (object) node
 // used by includejs.ls() to list cached urls
 cp: function (node) {
 var nodecp = {};
 _.keys(node).forEach(_.cbpropcp, {src: node, target: nodecp});
 return nodecp;
 }, 
 // default settup
 defs: {
 src : "", 
 done : pass, 
 error : pass,
 load : pass
 }, 
 // removes passed properties from (object) node
 // used in includejs.reload() to filter cached urls
 del: function (node) { //, ...props
 _.arrinters(_.keys(imported), _.slc(arguments, 1))
 .forEach(_.cbrmprop, node);
 return node;
 }, 
 // helper for wssplit() for filter out empty strings
 fnid: function (node) {
 return node;
 }, 
 // <head> reference for temporarily injecting <script>-s
 h: doc.getElementsByTagName("head")[0], 
 // attaches properties of (object) items to (object) target
 // used in few places to assign object properties
 init: function (target, items) {
 for (var name in items) {
 if (items.hasOwnProperty(name)) {
 target[name] = items[name];
 }
 }
 return target;
 }, 
 isfn: function (node) {
 return "function" == typeof node;
 }, 
 isplainobj: function (node) {
 return "[object Object]" == Object.prototype.toString.call(node);
 }, 
 isstr: function (node) {
 return "[object String]" == Object.prototype.toString.call(node);
 },
 keys: function (node) {
 return Object.keys(Object(node));
 }, 
 now: Date.now, 
 // calculates absolute url coresponding to given (string) url
 // not sure if this works on old ies
 path2abs: function (url) {
 _.anchor.href = ""+ url;
 return _.anchor.href;
 }, 
 // matches whitespace globaly
 // copy-pasted from es5-shim.js
 // https://github.com/es-shims/es5-shim.git
 rews: /[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+/g, 
 // used for converting dynamic arguments object to static array
 slc: function () {
 return Array.prototype.slice.apply(Array.prototype.shift.call(arguments), arguments);
 }, 
 // empties an object
 // used in includejs.reload() to empty private url cache 
 // forcing reload 
 vacate: function (node) {
 for (var name in node) {
 if (!Object.prototype.hasOwnProperty(name)) {
 delete node[name];
 }
 }
 return node;
 }, 
 // splits whitespace-separated string to it's components
 // returns array of uniqe (string) urls
 // used by includejs() to turn (string) .src urls to array
 wssplit: function (str) {
 return (""+ str).split(_.rews).filter(_.cbuniq).filter(_.fnid);
 }
 };
 // main function
 _include = function (settup) {
 var opts = {};
 _.isplainobj(settup) || (settup = {});
 // take only uncached absolute script urls
 opts.src = 
 _.arrdiff(
 _.wssplit(_.isstr(settup.src) ? settup.src : "").map(_.path2abs), 
 _.keys(imported)
 );
 opts.done = _.isfn(settup.done) ? settup.done : _.defs.done;
 opts.error = _.isfn(settup.error) ? settup.error : _.defs.error;
 opts.load = _.isfn(settup.load) ? settup.load : _.defs.load;
 // acts as counter to track download progress
 opts.i = 0;
 // holds <script> nodes for caching, and for callback clean-up afterward
 opts.s = [];
 // for tracking download progress, when opts.i == opts.len download is done
 opts.len = opts.src.length;
 if (opts.len) {
 opts.src.forEach(_requireloadcb, opts);
 } else {
 // asyncs _requireloadcompletecb()
 setTimeout(function () {
 _requireloadcompletecb(opts);
 });
 }
 return opts.src.slice(0);
 };
 // export
 // adds 'includejs' function identifier to global scope
 return _.init(_include, {
 defaults: _.defs, 
 // query cached urls
 ls: function () {
 return _.cp(imported);
 }, 
 // reloads cached .src urls 
 // takes same-format-object as includejs()
 reload: function (settup) {
 if (_.isstr(settup)) 
 settup = {src: settup};
 _.isplainobj(settup) || (settup = {});
 if (!settup.hasOwnProperty("src")) {
 settup.src = _.keys(imported).join(" ");
 _.vacate(imported);
 } else {
 _.del.apply(null, 
 [imported]
 .concat(_.wssplit(_.isstr(settup.src) ? settup.src : "").map(_.path2abs))
 );
 }
 _include(settup);
 }
 });
 // helpers
 // nulls .onload/.onerror handlers
 // detaches loaded <script> node
 // used by _requireloadcompletecb() to perform cleanup
 function _requiregcloadcb (nodescript) {
 nodescript.onload = null;
 nodescript.onerror = null;
 _.h.removeChild(nodescript);
 }
 // generates new <script> and appends it to <head> 
 // executing <script>.src file in global context
 // used by includejs() to download/execute script files
 function _requireloadcb (fileurl) {
 //this: {src, done, error, load, i, len, s}
 var opts = this;
 var nodescript = _.init(doc.createElement("script"), {
 onerror : function () { // ...e
 //this: <script>
 opts.i += 1;
 opts.error.apply(this, arguments);
 _requireloadcompletecb(opts);
 },
 onload : function () { // ...e
 //this: <script>
 opts.i += 1;
 imported[fileurl] = _.now();
 opts.load.apply(this, arguments);
 _requireloadcompletecb(opts);
 }, 
 defer : false, 
 src : fileurl, 
 type : "application/javascript"
 });
 //opts.s.push(_.h.removeChild(_.h.appendChild(nodescript)));
 opts.s.push(_.h.appendChild(nodescript));
 }
 // cleans up after scripts load/fail-to-load 
 // nulls .onload/.onerror handlers
 // empties settup (object) opts
 function _requireloadcompletecb (opts) {
 if (opts.i == opts.len) {
 opts.done.apply(doc, opts.src);
 opts.s.forEach(_requiregcloadcb);
 // opts.s.splice(0, 1/0);
 opts.s.splice(0);
 _.vacate(opts);
 }
 }
}
));
//
// use:
// 
// includejs({
// src: "lib/_.js //cdnjs.cloudflare.com/ajax/libs/modernizr/2.7.1/modernizr.min.js", 
// done: function (scripturls) {
// // _doStuff(scripturls);
// console.log("done", this, arguments);
// }, 
// load: function (e) {
// // _srcLoaded(this);
// console.log(e, this);
// }, 
// error: function (e) {
// // _srcFailed(this);
// console.log(e, this);
// }
// });
// 
// // console:
// load <script src="http://localhost/sites/xsite/lib/_.js" type="application/javascript">
// load <script src="http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.7.1/modernizr.min.js" type="application/javascript">
// done Document index.htm ["http://localhost/sites/xsite/lib/_.js", "http://cdnjs.cloudflare.../2.7.1/modernizr.min.js"]
// 
// console.log(includejs.ls());
// // console:
// Object { 
// http://localhost/sites/xsite/lib/_.js = 1394306633258, 
// http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.7.1/modernizr.min.js = 1394306633369
// }
// 
// includejs.reload({
// done: function (urls) {
// // _doStuffOnReload(urls);
// console.log("reloaded", this, arguments);
// }
// });
// 
// // console:
// reloaded Document index.htm ["http://localhost/sites/xsite/lib/_.js", "http://cdnjs.cloudflare.../2.7.1/modernizr.min.js"]
// 
konijn
34.2k5 gold badges70 silver badges267 bronze badges
asked Mar 8, 2014 at 18:52
\$\endgroup\$
3
  • \$\begingroup\$ There are many script loader libraries out there, some extremely small and simple. What new functionality are you offering over all these other offerings. Or put another way, what benefit do you have to reinventing a solution that has been offered in many tried/true libraries? Here's one for comparison: labjs.com. \$\endgroup\$ Commented Mar 8, 2014 at 19:19
  • \$\begingroup\$ Yes, there are dozens of tested solutions out there, there's nothing new and extraordinary revolutionary in this peace of code I've posted. I was hoping to get/implement critics for this implementation because it is critical in javascript utility library I'm working on... \$\endgroup\$ Commented Mar 8, 2014 at 19:53
  • \$\begingroup\$ I'd suggest that maybe you write up a list of features it has. As your question is now, we're left to try to discern from the code what it is supposed to do and then try to evaluate how well it does that. I rather doubt you will get much feedback without providing a lot more info. And, why can't you use an existing, tested, supported open source library that already exists in your own utility library? Why rewrite from scratch? \$\endgroup\$ Commented Mar 8, 2014 at 19:57

1 Answer 1

5
\$\begingroup\$

Your code is interesting,

you are clearly smart and know JavaScript, but this is a maintenance nightmare. I am assuming you will not take my advice to heart but here goes:

  • Do not abbreviate: isfn -> isFunction, isstr -> isString, cbpropcp -> ? ,slc -> slice, _requiregcloadcb -> ? etc. etc. etc. etc.
  • Also, use lowerCamelCase
  • Also, avoid _xx for private variables, as per Crockford
  • Name functions for what they do, not how they are used:

    fnid: function (node) {
     return node;
    }, 
    

    fnid is a terrible name, if possible I would refactor the code so that I would not need this function. A better name might be value ?

  • JSHint could not find anything wrong, except that your event handlers do not use e, so you do not need to declare e as a parameter
  • You use cbrmprop and similar functions only once, consider in-lining them
  • settup -> setup ;)
  • opts.s.splice(0, 1/0); -> opts.s.splice();
  • defer : false, -> I think this should have been an option
answered Mar 10, 2014 at 13:57
\$\endgroup\$
2
  • \$\begingroup\$ Fully private variables as per Crockford: javascript.crockford.com/private.html -- I see that as problematic though because each time you create a new object, a new set of functions and variables are created when really only the variables should be duplicated. Not having such private variables means lighter code... right? \$\endgroup\$ Commented Mar 11, 2014 at 7:05
  • \$\begingroup\$ It's more that _ does not do anything in the language, and it's one more version of Hungarian notation in a way. Trying to find a link.. \$\endgroup\$ Commented Mar 11, 2014 at 11:22

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.