looking for a full-immersion/face-to-face HTML5, JavaScript, and Mobile Web Development training in the heart of London? Book your spot
Showing posts with label loader. Show all posts
Showing posts with label loader. Show all posts

Sunday, January 15, 2012

Y U NO use libraries and add stuff

This is an early introduction to a project I have been thinking about for a while.
The project is already usable in github but the documentation is lacking all over the place so please be patient and I'll add everything necessary to understand and use yuno.

Zero Stress Namespace And Dependencies Resolver

Let's face the reality: today there is still no standard way to include dependencies in a script.
If we are using a generic JS loader, the aim is to simply download files and eventually wait for one or more dependency in order to be able to use everything we need.
The require logic introduced via node.js does not scale in the browser due synchronous nature of the method itself plus the sandbox not that easy to emulate in a browser environment.
The AMD concept is kinda OKish but once we load after dependencies, there is no way to implement a new one within the callback unless we are not exporting.
I find AMD approach surely the most convenient but still not the best one:
  1. we cannot implement a provide like procedure, whenever this could be handy or not
  2. it's not clear within the module code itself, what we are exporting exactly

Specially the last point means that AMD does not scale properly with already combined code because AMD relies in the module/folder structure itself ... so, cannot we do anything better than what we have so far?

The yuno Concept


Directly out of a well known meme, yuno logic is quite straightforward:
  • automagically resolved path, you point once to yuno.js file in your page and you are ready to go
  • compatible with already combined files (smart builder coming soon)
  • yuno.use() semantic method to define dependencies, if necessary
  • yuno.use().and() resolved callback to receive modules AMD style once everything has been loaded
  • yuno.add() standard ES5 way to define new namespaces, objects, properties, or constructors ( so no extra note in the documentation is needed )
  • cross referenced dependencies automagically resolved: if two different scripts needs same library, this will be loaded once for both
  • external url compatible, because you may want to include a file from some known CDN rather than put all scripts in your own host ( speed up common libraries download across different libraries that depend on same core, e.g/ jQuery )
  • modules, namespaces, or global objects, cannot be reassigned twice, which means if we are adding twice same thing we are doing it wrong, but if we are not aware of other script that added same thing before we have a notification
  • something else I may decide to add after this post
Here some example:

// define a jQuery plugin
yuno.use(
"jQuery",
"extraStuff"
).and(function (jQuery, extraStuff) {
yuno.add(jQuery.fn, "myPlugin", {value:function () {
// your amazing code here
}});
// we may opt for just this line
jQuery.fn.myPlugin = function () {};
// in order to export our plugin
// however, the purpose of yuno is to have
// a common recognizable way to understand
// what the module is about
// plus the "add" method is safer
});

Let's imagine that extraStuff contains similar code:

// define extraStuff
yuno.use(
"jQuery"
).and(function (jQuery) {
yuno.add(jQuery.fn, "extraStuff", {value:function () {
// your amazing code here
}});
});

Both plugin and extraStuff needs jQuery to be executed ... will jQuery be loaded twice? Nope, it's simply part of a queue of modules that needs to be resolved.
As soon as it's loaded/added once, every module that depends on jQuery will be notified so that if the list of dependencies is fully loaded, the callback passed to and will be executed.

Y U NO Add

Modules are only one part of the proposal since we may define a script where no external dependency is needed.

// note: no external dependency, just add
yuno.add(this, "MyFreakingCoolConstructor", {value:
function MyFreakingCoolConstructor() {
// freaking cool stuff here
}
});
// this points to the global object so that ...
MyFreakingCoolConstructor.prototype.doStuff = function () {
// freaking cool method
};

The yuno.add method reflects Object.defineProperty which means for ES5 compatible browsers getters, setters, and values, are all accepted and flagged as not enumerable, not writable, and not configurable by default.
Of course we can re-define this behavior but most likely this is what we need/want as default in any case ... isn't it?
For those browsers not there yet, the Object.defineProperty method is partially shimmed where __defineGetter/Setter__ or simply the value property will be used instead.
Bear in mind this shim may change accordingly with real needs but so far all mobile browsers should work as expected plus all Desktop browsers except IE less than 9 ... not so common targets for modern web sites.
Last, but not least, yuno logic does not necessarily need the add call so feel free to simply define your global object or your namespace the way you want.
However, as I have said before, the add method is a simple call able to make things more robust, to speedup and ensure notifications, and to use a standard, recognizable pattern, to define our own objects/functions being sure nobody did before thanks to defaults descriptor behavior which is not writable and not configurable indeed.

To DOs

This is just an initial idea of what the yuno object is able to do but few things are in my mind. On top of the list we have the possibility to shortener CDN calls via prefixes such "cdn:jQuery", as example, in order to use most common CDNs to load widely shared libraries.
Last, but not least, the reason I am writing this is because I am personally not that happy with any solution we have out there so if you are willing to contribute, please just leave a comment, thanks.

Sunday, August 14, 2011

Once Again On Script Loaders

It's a long story I would like to summarize in few concrete points ...

Three Ways To Include A Script In Your Page

First of all, you may not need loaders at all.
Most likely you may need an easy to go and cross platform build process, and JSBuilder is only one of them.

The Most Common Practice

This way lets users download and visualize content first but it lets developers start the JS logic ASAP as well without mandatory need to set DOMContentLoaded or onload event.

<!doctype html>
<head>
<!-- head content here -->
</head>
<body>
<!-- body content here -->
<script src="app.minzipped.js">/* app here */</script>
</body>
</html>


The "May Be Better" Practice

I keep saying that a web application that does not work without JavaScript should never be accessed by a user. As example, if a form won't submit without JavaScript what's the point to show it before it can possibly work? If your page strongly depends on JavaScript don't be afraid to let the user wait slightly more before the layout is visualized. The alternative is a broken experience as welcome. Accordingly, use the DOMContentLoaded listener over this ordered layout:

<!doctype html>
<head>
<!-- head content here -->
<script src="app.minzipped.js">/* app here */</script>
</head>
<body>
<!-- body content here -->
</body>
</html>

If you don't trust the DOMContentLoaded listener you can combine both layouts:

<!doctype html>
<head>
<!-- head content here -->
<script src="app.minzipped.js">/* app here */</script>
</head>
<body>
<!-- body content here -->
<script>initApp();</script>
</body>
</html>


The Optional "defer" Attribute

We can eventually try to avoid the blocking problem using a defer attribute. However, this attribute is not yet widely supported cross browser and the result may be unexpected.
Since this attribute is basically telling the browser to do not block downloads, in the very next future it could be specified both on head script or before the end of the body.
Everything I have said about possible broken UX is still valid so ... use carefully.

The Loading Practice

Classic example is twitter on mobile browsers and any native application with a loading bootstrap screen. Also Flash based websites use this technique since ages and users are used to it.
If the amount of javascript plus CSS and assets is massive, both precedent techniques will fail.
The first one will fail because the user doesn't know when the script will be loaded plus it's blocking so the page won't respond. Bye bye user.
The second approach will result into a too long waiting time over a blank page ... bye bye user.
This loading approach will entertain the user for a little while, it will be lightweight, fast to visualize, and it can hold the user up to "5 seconds" with cleared cache ( and hopefully much less next time with cache but if more we should really think to split the logic and lazy load as much as possible ).

<!doctype html>
<head>
<!-- head content here -->
<!-- most basic CSS -->
</head>
<body>
<!-- most basic content -->
<!-- "animated gif" or loader spin -->
<script src="bigstuff.minzipped.js">/* code */</script>
<!-- optional BIG CSS -->
</body>
</html>


This page should be as attractive as possible and no interaction that depends on JavaScript should be shown.

Why Scripts Loaders

Because an articulated website may have articulated logic split in different files.
The main page may rely into jQuery, commonLogic, mainPageLogic.
Any sub section in the site may depend on jQuery, commonLogic, subSectionLogic, adHocSectionLogic, etc.
The build approach will fail big time here because every page will contain a different script to download in all its variants.
Moreover, thanks to CDN some library can be cached cross domain, including as example jQuery from that CDN.
In this scenario a script loader is the best solution:

$LAB
.script("http://commoncdn.com/jquery")
.script("commonLogic.js")
.wait()
.script("subSectionLogic.js")
.wait()
.script("adHocSectionLogic.js")
.wait(function () {
// eventually ready to go
// in this section
})
;

Above example is based on LAB.js, a widely adopted library I have actually indirectly contributed as well solving one conflict with jQuery.ready() method.

script() and wait()

LAB.js has been created with performances in mind where every script will be pre downloaded as soon as it's defined in the chained logic.
The wait() method is a sort of "JS interpretation break point" and it's really useful when a script depends on another script.
Let's say commonLogic is just a set of functions while subSectionLogic starts with a jQuery.ready(function () { ... }) call, LAB.js will ensure that latter script won't be executed until jQuery is ready ... got it?
LAB.js size once minzipped is about 2.1Kb and the best way to use it is to include LAB.js as very first script in whatever page.
AFAIK LAB.js is not yet hosted in any major CDN but I do believe that will happen soon.

Preload Compatibility

LAB.js uses different techniques to ensure both pre downloads and wait() behavior. Unfortunately some adopted fallback looks inevitably weak to me.
As example, I am not a big fun of "empty setTimeouts" solutions since these are used as workaround over unpredictable behaviors.
One of these behaviors is the readyState script property that on "complete" state may have or may have not already interpreted the script on "onreadystatechange" notification.
If we have a really slow power machine, as my netbook is, the timeout used to decide that the script has been already parsed may not be enough.
I don't want to bother you with details, I simply would like you to understand why I came out with an alternative loader.
Before I reach that point I wanna show an alternative technique to get rid of wait() calls.

Update
It looks like few setTimeout calls will be removed soon plus apparently the setTimeout I pointed out has nothing to do with wait: my bad.
In any case I don't fancy empty timers plus LAB.js logic is focused on cross browser parallel pre-downloads and for this reason a bit more bigger in size than all I needed for my purpose.

Avoiding wait() Calls

JavaScript let us successfully download and parse scripts like this without problems:

function initApplication() {
jQuery.ready(function () {
// whatever we need to do
});
}

Please note that no error will be thrown even if jQuery has not been loaded yet.
The only way to have an error is to invoke the function initApplication() without jQuery library in the global scope.
In few words, we are not in Java or C# world where the compiler will argue if some namespace is accessed in any defined method and not present/included as dependency before ... we are in JavaScript, much cooler, isn't it? ;)
Accordingly, if the current page initialization is wrapped in a single function we could simply use a single wait call at the end.

$LAB // no direct jQuery calls in the global scope
.script("http://commoncdn.com/jquery")
.script("commonLogic.js")
.script("subSectionLogic.js")
.script("adHocSectionLogic.js")
.wait(function () {
initApplication();
})
;

The potential wait() problem I am worried about is still there but at least in a single exit point rater than distributed through the whole loading process ... still bear with me please.

The Namespace Problem


The generic init function can be part of a namespace as well. If we have namespaces the problem is different 'cause we cannot assign my.namespace.logic.init = function () {} before my.namespace object has been defined.
In this case we either create a global function for each namespace initialization/assignment or we impose a wait() call between every included namespace based file.

yal.js - Yet Another ( JavaScript ) Loader


Update
yal.js now on github


As written in yal.js landing page I have been dealing with JS loaders for a while.
This library created a sort of "little twitter war" between me and @getify where Kyle main arguments were "why another loader?" followed by "LAB.js has better performances".

Why yal.js

It's really a tiny script that took me 1 hour tests included plus 20 minutes of refactoring in order to implement a sort of "forced preload" alternative ( which kinda works but I personally don't like and neither does Kyle ).
yal.js is just an alternative to LAB.js and we all like alternatives, don't we?
The main focus of yal.js is being as small and as cross browser as possible using KISS and YAGNI principles.

No Empty Timers Essential Script Logic

yal.js is based on script "onload" event which behavior is already defined as standard and it's widely compatible.
If not usable in some older browser, the more reliable "loaded" state of readyState property is used instead. This state comes always after the "loading" or "complete" one.
I could not trigger any crash or problem wit this approach and together with next point no need to use unpredictable timers.

Simplified Wait Logic

In the basic version of the script any wait() call will block other scripts. These won't be pre downloaded until the previous call has been completed.
However, if we consider we may not even need wait calls:

yal // no direct jQuery calls in the global scope
.script("http://commoncdn.com/jquery")
.script("commonLogic.js")
.script("subSectionLogic.js")
.script("adHocSectionLogic.js")
.wait(function () {
initApplication();
})
;

yal will perform parallel downloads same way LAB.js does and, being yal just 1.5Kb smaller, performances will be slightly better on yal rather than LAB.js
Also for my bad experience with "complete" state, I feel a bit more secure with the fact that when wait() is invoked in yal.js, everything before has been surely already interpreted and executed ( but please prove me wrong if you want with a concrete example I can test online, thanks )

Just What I Need

For my random and sporadic personal projects yal.js fits all my requirements. I do not use the forced parallel downloads and I don't care. I have asked Kyle to be able to grab a subset of LAB.js getting rid of all extra features surely useful for all possible cases out there but totally unnecessary for mine. Unfortunately that would not have happened any soon so I created the simplest solution for all I personally needed.

As Summary


I am actually sorry Kyle took my little loader as a "non sense waste of time" and if that's what you think as well or if you need much more from a loader, feel free to ignore it and go happily with LAB.js
Also I am not excluding that the day LAB.js will be in any major CDN I will start using it since at that point there won't be any overhead at all and cross domain.

Finally, in this post I have tried to summarize different techniques and approaches to solve a very common problem in this RIA era, hope you appreciated.