I = V / R; Ohm’s law explains Developer Advocacy Rapportive: A fantastic social email service with fantastic service!
Jul 29

Capability based JavaScript loading; JS libraries catch up to GWT

Ajax, JavaScript, Tech with tags: Add comments

ballandchain

Web developers have to walk around dragging a ball and chain. It is fantastic that we have a ridiculous install base (browsers, the Web runtimes) and a dynamic language in JavaScript.

The ball and chain though is the fact that we have to care so much about the payload of the application that we write. Less code means less bytes to download, and less for the JS runtime to load up.

This has bad consequences:

  • You are tempted to write hard to understand code
  • You have to balance functionality and code size much more than other environments
  • You end up getting tricky and come up with ways to dynamically load modules on demand
  • All of this time is time not spent on the app logic.

When you run into a problem like this in computer science what do you do? Build an abstraction!

GWT has done a great job here. It’s very nature requires a compilation step, and once you have to deal with that step…. you can do a lot at build time to change the above dynamics.

For example:

  • You can write nice explicit source code, and trust that the compiler will output optimized JS (which can look as ugly as sin for all anyone cares)
  • Your application can get faster with a new release of GWT, as the compiler gets better
  • A lot of advanced techniques such as code splitting, and deferred binding can be applied to allow logic to apply at build time as well as runtime.

Ignoring GWT, when you write a pure JS application for the Web, you are writing a cross platform application. Unlike clean portable C, you don’t have that compilation step to do clean up work for you on each platform, so you end up with a lot of runtime conditional logic.

You try to do object detection rather than user agent conditional, but still, you end up both downloading the code necessary to run in all environments, and you have the overhead of loading and executing that code.

Chances are you are smart and don’t do if() checks all the time, but maybe do so once when loading up. For large chunks of big differences, maybe you do something like:

var Foo = (sometest) ? function() {
    // do it like this
} : function() {
    // do it like that
};

If you take a look at some parts of popular JavaScript frameworks, you see that they are abstractions or fixes for various browsers. As browsers have stepped up to the plate recently, they have fixed a lot, and suddenly you take a step back. A lot of frameworks have a chunk of code to give us the ability to do smart things with CSS querying. Modern browsers support querySelectorAll (even with some bugs) and getElementsByClassName so a lot of the code is un-needed…. unless you care about the old browsers.

We shouldn’t have to download all of that cruft on the other side of the if() statement when we don’t use it!

The GWT compiler can output versions of your application targeted to a given browser. Only the fastest, minimal code, will get sent down. Deferred binding goes far beyond just browser type (can deal w/ locales and much more) too.

GWT isn’t the only horse in this race though.

Alex Russell started to sprinkle in some directives to Dojo that would enable you to do a build that is WebKit targeted. A major use case for all of this is making sure that you are sending down lean code in the mobile space. It can matter everywhere, but when you are dealing with grown up walkie talkies…. you want to really optimize.

And this brings us to YUI. I was really excited to see some of the features in the YUI 3.2.0 preview release. Great stuff for touch/gesture support, but what stood out for me was “YUI’s intrinsic Loader now supports capability-based loading”. A-ha!

I poked around the source to see how it was used. Here is one fake example that shows how it works. When you boot up YUI you can add in “conditions” which are in charge of working out whether to load something. You can currently tie on to user agent, or write a test function:

YUI({
    modules: {
        lib2: {
            requires: ['yui'],
            fullpath: 'js/lib2.js',
            condition: {
                trigger: 'node-base',
                ua: 'gecko'
            }
        },
 
        lib3: {
            requires: ['yui'],
            fullpath: 'js/lib3.js',
            condition: {
                trigger: 'event-base',
                test: function(Y, req) {
                    return Y.UA.gecko;
                }
            }
        }
    }
 
}).use('node', function (Y) { .... });

The YUI team uses this themselves to only load certain ugly DOM stuff for IE6 when needed:

"dom-style-ie": {
    "condition": {
        "trigger": "dom-style", 
        "ua": "ie"
    }, 
    "requires": [
        "dom-style"
    ]
},
 
YUI.add('dom-style-ie', function(Y) {
    // ....
});

This makes a ton of sense, especially for the libraries themselves to do this work. They are the ones that do the heavy lifting of cross browser ugliness, all to allow us to write to an API that works.

Beyond that, we can then ourselves split out code if it becomes a performance issue.

Of course, the value of the test() function is that you can do capability based testing (not just user agent testing…. hence the name!). This means that you can do a test say for Canvas support (as Zach mentions in the comments) document.createElement(’canvas’).getContext, and load up excanvas or another shim library if it doesn’t exist.

Using modernizr and ilk, we could build out “plugins” for YUI that auto load based on common capabilities. You can imagine running a YUI build, having it detect that you are using a capability, and then automatically load up a plugin that has the condition all loaded up. What a great user experience as a developer!

Maybe it makes sense to come up with a common pattern and conventions for dealing with this issue. How do you name your CSS/JS? Do we setup server side hooks so we don’t even need loaders necessarily?

I can certainly see a day where you may be asking the Google Ajax Library service for jquery.js, but it is returning jquery.ie.js to you.

6 Responses to “Capability based JavaScript loading; JS libraries catch up to GWT”

  1. Zach Leatherman Says:

    To me, this is no different than “dynamic conditional comments” if you’re triggering on nothing but user agent.

    The real power will come when you use feature detection.

    For example, when using canvas, load excanvas if document.createElement(’canvas’).getContext fails. Of course, excanvas only works in IE, but you get the idea.

  2. dion Says:

    Zach,

    Totally. It is “capability based js loading” after all. I tweaked the post to spell that out. I am particularly excited to see a series of capability plugins for YUI, and have a scanner as part of the build process which auto detects usage and puts in the conditions for you!

    Thanks for the note!

    Dion

  3. Gabriel Gilini Says:

    @Zach: It’s actually worse than conditional comments, as you can’t change a configuration on your browser to make it parse some IE conditional comment, unlike the user agent string.

    It is never a good idea to load scripts dynamically if they are imperative to your app. This sounds a lot like looking for a solution for a non-problem. Why not build a modular library, then select the modules you want loaded before downloading the script, a server-side builder would glue the pieces together and serve only what’s needed for your application. To be quite frank, most of times you only need event handling and measuring position/size of elements, that script shouldn’t be too big.

    The real problem is most libraries are so tangled up on the inside, that you couldn’t create a small build even if you wanted to.

  4. Dylan Says:

    Pete has been doing some work recently on the Dojo 1.6 branch with regards to better feature and capability detection… for example, here’s the Modernize test suite using that branch: http://dante.dojotoolkit.org/dojobox/trunk/plugd/tests/test_Modernizr.html

    The overall problem itself is inherently hard, because you don’t want to waste an extra request to detect features to then determine what to load, but you don’t want to send down the wire something as large as say support for VML for non-IE browsers. For example, Dojo targeted just to the features on recent WebKit versions is 75% smaller, before thinking about just the features you want to use in your app. But then it becomes interesting… for example, say you want to support WebSocket… Safari 5, Chrome 5, and Chrome 6 currently all work with different draft versions (74, 75, 76), and detecting for those glitches is a new form of fun.

    I guess my point is that overuse of user agent detection is considered harmful, but so is dogmatic purist belief that it is never useful. For example, if you’re creating an iPhone app or an embedded IE app with something like Dojo or YUI, targeting the user agent saves a lot of pain, though you still need a healthy does of feature detection after the fact.

    Getting Dojo’s build/compile system to work with feature detection helps us get the choice that’s needed for your app, and it looks like YUI is on the same path.

  5. Sebastian Says:

    What would be pretty cool would be to have some kind of loader script which does feature detection, locale detection, etc. And than loads a specific variant of the implementation. This could even be combined with a versioning so younadd the version information to the file name as well.

    To keep filenames short one might use hashing:
    nativeQSA=true&nativeJSON=true&version=1.3 => fdededc93bded.js

  6. Javascript Countdown Timer Says:

    very cool & good tip, thank you very much for sharing.

    But I think a live demo will be better, a demo worth 1k words :D

Leave a Reply

Spam is a pain, I am sorry to have to do this to you, but can you answer the question below?

Q: What is the number before 3? (just put in the digit)