This page discusses why modules on the web are useful and the mechanisms that can be used on the web today to enable them. There is a separate page that talks about the design forces for the particular function wrapped format used by RequireJS.
Front-end developers need a solution with:
First thing to sort out is a script loading API. Here are some candidates:
All of them map to loading some/path/some/module.js. Ideally we could choose the CommonJS syntax, since it is likely to get more common over time, and we want to reuse code.
We also want some sort of syntax that will allow loading plain JavaScript files that exist today -- a developer should not have to rewrite all of their JavaScript to get the benefits of script loading.
However, we need something that works well in the browser. The CommonJS require() is a synchronous call, it is expected to return the module immediately. This does not work well in the browser.
This example should illustrate the basic problem for the browser. Suppose we have an Employee object and we want a Manager object to derive from the Employee object. Taking this example, we might code it up like this using our script loading API:
var Employee = require("types/Employee"); function Manager () { this.reports = []; } //Error if require call is async Manager.prototype = new Employee();
As the comment indicates above, if require() is async, this code will not work. However, loading scripts synchronously in the browser kills performance. So, what to do?
It is tempting to use XMLHttpRequest (XHR) to load the scripts. If XHR is used, then we can massage the text above -- we can do a regexp to find require() calls, make sure we load those scripts, then use eval() or script elements that have their body text set to the text of the script loaded via XHR.
Using eval() to evaluate the modules is bad:
Using script tags with body text set to file text is bad:
XHR also has issues with cross-domain requests. Some browsers now have cross-domain XHR support, but it is not universal, and IE decided to create a different API object for cross-domain calls, XDomainRequest. More moving parts and more things to get wrong. In particular, you need to be sure to not send any non-standard HTTP headers or there may be another "preflight" request done to make sure the cross-domain access is allowed.
Dojo has used an XHR-based loader with eval() and, while it works, it has been a source of frustration for developers. Dojo has an xdomain loader but it requires the modules to be modified via a build step to use a function wrapper, so that script src="" tags can be used to load the modules. There are many edge cases and moving parts that create a tax on the developer.
If we are creating a new script loader, we can do better.
Web Workers might be another way to load scripts, but:
document.write() can be used to load scripts -- it can load scripts from other domains and it maps to how browsers normally consume scripts, so it allows for easy debugging.
However, in the Async vs Sync example we cannot just execute that script directly. Ideally we could know the require() dependencies before we execute the script, and make sure those dependencies are loaded first. But we do not have access to the script before it is executed.
Also, document.write() does not work after page load. A great way to get perceived performance for your site is loading code on demand, as the user needs it for their next action.
Finally, scripts loaded via document.write() will block page rendering. When looking at reaching the very best performance for your site, this is undesirable.
We can create scripts on demand and add them to the head:
var head = document.getElementsByTagName('head')[0], script = document.createElement('script'); script.src = url; head.appendChild(script);
There is a bit more involved than just the above snippet, but that is the basic idea. This approach has the advantage over document.write in that it will not block page rendering and it works after page load.
However, it still has the Async vs Sync example problem: ideally we could know the require() dependencies before we execute the script, and make sure those dependencies are loaded first.
So we need to know the dependencies and make sure we load them before executing our script. The best way to do that is construct our module loading API with function wrappers. Like so:
define( //The name of this module "types/Manager", //The array of dependencies ["types/Employee"], //The function to execute when all dependencies have loaded. The //arguments to this function are the array of dependencies mentioned //above. function (Employee) { function Manager () { this.reports = []; } //This will now work Manager.prototype = new Employee(); //return the Manager constructor function so it can be used by //other modules. return Manager; } );
And this is the syntax used by RequireJS. There is also a simplified syntax if you just want to load some plain JavaScript files that do not define modules:
require(["some/script.js"], function() { //This function is called after some/script.js has loaded. });
This type of syntax was chosen because it is terse and allows the loader to use head.appendChild(script) type of loading.
It differs from the normal CommonJS syntax out of necessity to work well in the browser. There have been suggestions that the normal CommonJS syntax could be used with head.appendChild(script) type of loading if a server process transforms the modules to a transport format that has a function wrapper.
I believe it is important to not force the use of a runtime server process to transform code:
More details on the design forces and use cases for this function wrapping format, called Asynchronous Module Definition (AMD), can be found on the Why AMD? page.
© jQuery Foundation and other contributors
Licensed under the MIT License.
http://requirejs.org/docs/why.html