Tuesday, September 29, 2009

RunJS

I just created what I hope to be the next generation JavaScript module loader here: RunJS. The documentation has some history and reasoning behind it.

I would like to see it used for any project that needs JS code loading, particularly since it handles dependencies, can load regular JavaScript files, handles multiple versions of modules, and is a compact 2.2KB (minified and gzipped). It is JavaScript toolkit-agnostic and has no dependencies.

Enjoy!

9 comments:

Kris Zyp said...

How do you return the object/set of functions from the module itself? It would be really cool if this was based on the CommonJS module system.

James Burke said...

Kris Zyp: if you return an object from the function wrapper, that object defines the module.

As I mention in the "History and Influences" section at the bottom of the doc page, CommonJS is not optimized for running in the browser. It works best/assumes a synchronous loader, and our experience on Dojo is the synchronous Dojo loader is harder to use and debug in the browser.

As an alternative to using sync XHR calls for browser loading, I have heard that CommonJS may use a server process to wrap the modules in a function wrapper similar to the one used by RunJS.

I want something that is usable out of the box, that works with static files from a web server.

So, I prefer a solution that works best in and is optimized for the largest JavaScript environment, the browser, and then use that model on the server. RunJS should be easily adapted for server running.

I plan on adding in a stub in RunJS that would make it easy for a server environment to define the synchronous loading function for a JavaScript file.

Unknown said...

I see problems with this for Dojo use. You can't put Dojo in the HEAD element if you plan to append elements to the HEAD during the load.

And there's no way this onreadystatechange/onload stuff is going to work with document.write. Won't be reliable cross-browser either.

I'll also point out that my loader (featureDetection branch) is all of six lines long and easily backward compatible via configuration.

I see some other bugs too. typeof xyz == 'array' will never be true in an ECMAScript implementation and instanceOf Array will never be 100% reliable.

James Burke said...

David,

On the head thing, I just put in a change I had from the xd loader, where html is used in case head is available. That change has been very stable, used in the xd loader for many years now.

I'm not too concerned about doc.write support if it does not work out. appendChild will be fine for me.

Your featureDetection loader seems to have the problem where if someone does a dojo.require("dijit._Widget") then does a dojo.declare("something", dijit._Widget) it will fail unless they wrap the module code in a function wrapper. So that is not a backwards-compatible change. Basically, a function wrapper around the module is required, and in that case, I prefer the runjs format.

If you know of how the array tests can fail, feel free to provide an example and what env where it might fail.

Unknown said...

James,
Most of dojo exists without the function wrapper. It is mainly to corral complex interdependent Dijit modules and transparent to the average Dojo developer.

But none of that matters. This solution is not practical at all. It relies on:

1. Hacks (onreadystatechange/onload on SCRIPT elements)

2. Appending children to elements that are still parsing (leads to Operation Aborted per MS and me and is just poor practice for DOM scripting) and that do not take such children at all per standards (e.g. SCRIPT in an HTML parent).

3. Language features that do not exist. Read up on typeof (and instanceof). Asking me to prove that something does not exist is not going to lead to understanding.

Again, see the branch. It always turns out that the answer is in there (e.g. the undeclared variable "speedup", detecting addEventListener, this, eval, attr, etc.) ;)

James Burke said...

David,

As I said on the mailing list, any widget that anyone has made that extends from dijit._Widget will have to use the function wrapper. That affects a significant amount of users. So the function wrapper is a backwards-incompatible change for Dojo 1.x.

On the numbered parts:

1) This seems like standard script load detection, if you know how it will fail, that would be good to know.

2) The appendChild approach has been used for many years in the XD loader, and we have not gotten bugs about it. The Operation Aborted might be a problem if the IE DOMContentLoaded hack is also used for triggering "page load" scripts, but it is hardly a universal.

It has normally worked in the xd loader case, and the places where it failed, the pages are more complicated. In other words, I believe the Operation Aborted error is a symptom of the DOMContentLoaded hack in IE.

Currently runjs does not use the IE DOMContentLoaded hack, so I expect this not to be an issue.

But I may use the DOMContentLoaded hack in the future. Getting the fast path in IE so JS can initialize before waiting for images and iframes to load seems worth a minority case of Operation Aborted. I would provide a switch for the minority use case where it is an issue so that window onload is used.

And all of that does not preclude document.write from being used (before page load). In that path, if script onload tests do not work, then we write out a script block between each script tag that indicates which module just loaded. But we still need appendChild() support for loading modules after page load.

3) The brief search on instanceof I did seem to point to issues with getting values to test from other frames, for which the Object.prototype.toString tests seems to be the best solution, so I switched to that approach for array and function tests.

Even though I do not expect the loader to be used cross frames (using code across frames is generally hazardous) I think the new Object toString tests are good enough for those cases now. But again, if you know of a specific error case besides that, feel free to share it. I will still learn from it. You and I just have different tradeoffs that we find acceptable.

christophercuizon said...

Hey man i almost loss all my head with this script it doesn't work really i've been having this error on operation aborted ie7 hope you can help me.. thnks for you're awaiting moderate response.it would be my pleasure if you can help me as soon as possible

James Burke said...

christophercuizon: Sure, I am happy to help. When did you get run.js? I have been doing regular updates, as recent as yesterday, so using the latest version would help make it easier to debug.

If you want, you can open a bug ticket here:
http://code.google.com/p/runjs/issues/list

That way you can attach test files or mention a test URL for a failure case.

Also, there is a discussion group for runjs here:
http://groups.google.com/group/runjs

One possible explanation for the error:

Operation Aborted happens in IE if you try to modify the DOM before it has been loaded. You should put any code that wants to modify the DOM in a run.ready() call, not just in a function callback after loading some modules.

The function callback is called as soon as the modules load, but that could be before the page has loaded. run.ready() waits for the page to be loaded before calling its callbacks. Example (probably jumbled due to limited comment formatting):

run("run", "a", "b", function(run, a, b) {
//This function called when a, b and run have loaded.
//run is asked for as a dependency so we can get the right run context for the script.
run.ready(function() {
//This is called after the DOM is ready to accept modifications.
});
});

Numair said...

Always fun to see where things began. I'm relying heavily on Require.JS in a new project I'm building, and wanted to thank you for your 2.5 years of work on this!