Saturday, February 23, 2013

An In House, Yep Nope Like, JS Loader

Two days ago I've submitted a JavaScript loader to 140byt.es since this loader, once minified, fits into 136 bytes. The reason the entry is 140 in the site is that I've left on purpose /**/ to easily spot where you should put JS files to load.

And That's Not It

Once we have an Array, we have also .concat(), and once we have concat, we have the ability to decide, inline, what should be in the menu:
var
  menu = [].concat(
    // starter
   'onrefusedbeef' in window ?
      'caprese' : 'salami',
    // main course
    (function(window){
      return !!window.bigAppetite;
    }(this)) ?
      [
        'pasta',
        'salad',
        'bred'
      ] :
      'salad',
    // dessert
    ['coockies'].concat(
      this.extraCalories ?
        ['chocolate', 'sour cream'] :
        [] // nothing to add
    )
  )
;

// check the menu, chef
alert(menu.join("\n"));
You can play with different kind of menus, simply polluting the case with whatever i needed.
this.bigAppetite = true;
this. onrefusedbeef = null;
this.extraCalories = true;
The cute part about .concat() is the ability to pass empty arrays, when no extra value should be added to the list, just one element at the time, as example strings, or a list of elements, with the ability to create nested list thanks to nested [].concat().
In few words, Array#concat() suites perfectly with a list of files to include

Real JS Use Cases

Assuming we don't have partial shims in every single file, assuming we want to bootstrap whatever intakes to have a normalized environment, and do nothing otherwise, here an example:
// to loadvar
  OK = [],
  toLoad = OK.concat(
    Object.create ? OK : 'es5-sham.js',
    OK.forEach ? OK : 'es5-shim.js',
    Function.bind ? OK : 'bindOnlyShim.js',
    window.JSON ? OK : 'json2.js',
    [ // library list
      'lib.js',
      'main.js'
    ]
  );

// check this out
alert(toLoad.join("\n"));
In most modern browsers above snippet will alert only main files while in many Android browsers, the list will have Function#bind() only shim included, since everything else is there so Array extras are not needed. In older browsers we'll have Array extras, only after Object extras so that Array extras could use ES5 way to define extras ... in few words, the key here is to have scripts aware of their own dependencies instead of include partial shims all over and over ... this is because this little loader loads one script per time and this is ideal when scripts are ordered by dependencies.

All Together

Here how the script will look like, as last tag in your document body, in order to have exactly what you need, in exactly the order you need it.
<!DOCTYPE html>
<html>
<body>
<!-- everything you need -->
<script>
!function(b){

// extra functions, test, variables here
// it's a closure !

function c(){if(d=e.shift())a=b.body.appendChild(b.createElement("SCRIPT")),a.onload=c,a.src=d}var K=[],e=K.concat(

  Object.create ? K : 'es5-sham.js',
  K.forEach ? K : 'es5-shim.js',
  Function.bind ? K : 'bindOnlyShim.js',
  window.JSON ? K : 'json2.js',
  [ // library list
    'lib.js',
    'main.js'
  ]

),a,d;c()}(document);
</script>
</body>
</html>

Best Practices For Best Performance

  • do not repeat mini/partial/shims/polyfills per each file, do not try to make every file stand alone. In this way you serve, and load, only what's needed
  • decide statically, if necessary, dependencies in a logical order. If a file is shimming everything, and you want to trust that file, put that before any other that might try to put a shim if the library is not under your control so that file will use the already shimmed function you trust. As example, if any library contains a tiny JSON parser, put the official JSON polyfill before that file if needed. Same is for any library that tries to shim Array extras, put es5-shims before, and of course, only if needed
  • aggregate everything that cannot be left out, unless your library is 2 Mb gzipped, or unless you have lazy loaded stuff ... but for everything else, really, there's no need to serve 100 tiny files, put them together as part of your library
  • put main app logic a part and forget DOMContentLoaded or $(window).ready(), since this tiny loader works when the body and everything else is on the DOM ... did I tell you to put the loader at the very end of the page? :-)
Enjoy!

1 comment:

Avenida Gez said...

when you say ...
var n = Object.create(null);
n.__proto__ = {};

for (var k in n) console.log(k); // __proto__ !!!
Object.keys(n); // ["__proto__"] !!!

Got it? So, __proto__ is enumerable in some browser, is not in some other but it will be in all future browsers. Let's go on with examples ...

Take this into account...
__proto__ is a reference, you are destroying it in an assigment like that.
prototype is a constructor.
At the moment of an equity declaration you are calling a constructor too, which by the way is used with "new" to replicate (inherit) another object, having the same constructor (prototype) they will have the same reference (pointer if you wish to call it that way) called __proto__
Since variables are created automatically in declarations and assigments, depending how you do it, or __proto__ becomes lost, or becomes trapped in a closure, en either case you can't recover it.