With all the front-ending needing so many script libraries, plugins or even frameworks like Angular, the amount of scripts to load and, even more, the order in which they need to be loaded has increased its complexity highly in the last years. To help with this one very useful solution is called RequireJS.
So what does RequireJS do?
RequireJS will take control of the scripts load order and improve page load, configure their dependencies, etc. To do that, we have three methods: require(), define() and config().
Require() will immediately load the files set, while define() will just “configure” a module that can be called (using require()) and loaded. config() will help on setting some values to make all this easier.
Requiring a file
Let’s start by a simple example, this is how we tell requireJS that we want to load a file:
require(["Scripts/myApp/index"]);
And this is how we load multiple files:
require(["Scripts/myApp/index", "Scripts/myApp/script2", "Scripts/myApp/script3"]);
Note there is no .js, require already expects a script file so no need to say .js
Using the config
But having to write the “Scripts/MyApp” path all the time makes it dirty and unallows some stuff we could do with to load many files. So let’s see how the config can help us with that:
This is how we set the paths:
requirejs.config({ paths: { app: "Scripts/myApp/index" script2: "Scripts/myApp/script2" script3: "Scripts/myApp/script3" } ]);
Again, no need to set the .js extension. But config also allows us to set the base url to simplify even more:
requirejs.config({ baseUrl: "Scripts/myApp/", paths: { app: "index" script2: "script2" script3: "script3" } ]);
Warning: If you set more than one “config()” in multiple files, have in mind that requireJS only allows one instance of config(), so while you can add more values to the main config making calls to it on different files, if any of the keys or properties already existed will be overwritten. This is, if you set different baseUrls, for example, it won’t work well as it will overwrite it causing trouble.
That said, we can also set the dependencies between the scripts. Imagine our app.js needs the two other scripts to be loaded in order to work:
requirejs.config({ baseUrl: "Scripts/myApp/", paths: { app: "index" script2: "script2" script3: "script3" }, shim:{ app: { deps: [script2, script3]} } ]);
Also note that on setting the paths we don’t just say where to find the file but also give it an id, with that id we can make calls to that file or point to it like im doing at the shim property of the config, but it also works when using require or define:
requirejs.config({ baseUrl: "Scripts/myApp/", paths: { app: "index" script2: "script2" script3: "script3" }, shim:{ app: { deps: [script2, script3]} } ]); define([script2, script3], function(){ require([app]); });
Defining Modules
Now that we saw how to configure and request the files, let’s see how to make a module that can be called when needed with a request().
define([script2, script3], function(){ return function (){ this.doSometing = function(){ } //... } });
We actually saw the define() earlier but I didn’t explain it properly. As a norm, we normally have only one define method per file, which automatically sets it as the definition of such script. So if we have an app.js file with some functions to use or an object/library/whatever scripted, we can add a define to that script and add the script inside the define.
The define() method has 3 parameters: an id, an array with dependencies and the script to return if any.
The id parameter is not needed and can even cause trouble in case you expect it to have a different id from the config, that’s why if you follow the approach “one file one define” the id of such file will be the one at the config and that’s it, no trouble.
The dependencies are the honey of this method, as you can just set which files need to be loaded in case you can to load this module, and to top it off, you can add an extra config to such file allowing you to just define modules and prepare them to be loaded. Let’s see an example:
Imagine this is moduleIndex.js:
requirejs.config({ baseUrl: "Scripts/myApp/", paths: { app: "index" script2: "script2" script3: "script3" }, shim:{ app: { deps: [script2, script3]} } ]); define([script2, script3, app]);
In this moduleIndex.js I’m not setting any id to the define nor I am adding any script lines, I’m just configuring the files my module will use, the dependencies it has (which files it requires to be loaded) and the order to load those files (dependencies between the files).
I love this approach because this way we can isolate the “file loading” logic from the “real stuff” the scripts do, instead of mixing everything together. To call this moduleIndex.js we would need a require at another file:
require(["moduleIndex"]);
Also, a define() can call other defines, which means that a module can be form of multiple modules, so we could have a chain of defines were the define1 calls define2 and this one the define3. I said a define() will only load its scripts when called by a require() method, but that works on cascade, so if a require() calls a define() that calls another define(), all the define()s in the chain will load their files.
[…] And that would replace the “my-angular” tag by our angular view and make it work (though our current app is doing nothing). note that I set ng-app on the custom tag, not on the html one, this allows us to add apps to our html code with a mere custom tag and a call to a script were the application resides (if you have many scripts, you can use RequireJS). […]