Friday, November 11, 2016

Oracle JET: RequireJS How it works

In this blog I would like to show how requireJS. As I am exploring Oracle JET technology so I will keep my blog in context of Oracle JET

What is requireJS and its purpose: This library solves three problems
1. Download and execute a javascript library only when needed.
2. Reduce global variables
3. Provide modular structure to your javascript libraries.

To understand RequireJS, its better to see what generally happens when we don't have any such library. In that case we add all required javascript libraries in our html file using <script> tag. Browser will download all of them and execute them even though I don't need some of them while loading page initially. I may need to them later when user performs some action on page.
Also These libraries may by creating some global variables as well.
Also we need to be double sure about sequence of libraries in html. For example I can't use jquery selectors (like $("#lastname")) in my code unless I have jquery library mentioned. I definitely need to put jquery library script tag in html before using its feature in any subsequent library.

To overcome above limitations I need something which can
1. download and execute library only when needed.
2. reduce global variables
3. manage dependencies

That something is what requireJS is

Let see how it works. I have Oracle JET quickstart basic template project running. If you don't know Oracle JET, its a javascript based framework with rich set of UI components. Its open source and uses various third party libraries like knockout, jquery, promise, and requireJS etc.

I see in my index.html there is only one script tag

<script data-main="js/main" src="js/libs/require/require.js"></script>

The moment browser see this line in html, it download require.js and loads (execute) it.
require.js library validates data-main property of script tag and found that its js/main, which means its will look for a library main.js in js folder. It downloads it and run it. So atleast two libraries are going to get downloaded at page load. Lets use browser developer tools and verify it. I have put a debug point at first line of my main.js file. This line is requirejs.config call.

I found that
a. First require.js is downloaded, which is because we have <script> tag for that. It gets loaded immediately
b. After few milliseconds main.js is also getting download. Its not there in html script tag but its getting downloaded because require.js loads it asynchronously.
c. I also see two global variable require and requirejs added by require.js library. (NOTE we no other library has got executed till now)



main.js: First thing it does is calls requirejs.config function. This function is used to configure your all js library locations with require. All these library locations are given a name. For example knockout library is at libs/knockout/knockout-3.4.0 location. Its registered with name 'knockout'.
NOTE: we are not registering each and every js file. For example we have lots of oj libraries inside libs/oj/v2.1.0/debug but we are not mentioning them individually. We just mention parent directory here and associate it with ojs name. Which means if we want to refer any individual js from that directly we can simple say ojs/<that-library>



Next big call is require function call. It takes two inputs
a. Array of String
b. callback function

 require(['ojs/ojcore', 'knockout', 'appController', 'ojs/ojknockout', 'ojs/ojrouter',
   'ojs/ojmodule', 'ojs/ojdialog', 'ojs/ojnavigationlist', 'ojs/ojtoolbar',
   'ojs/ojbutton', 'ojs/ojmenu'],
   function (oj, ko, app) { // this callback gets executed when all required modules are loaded

       //Some code eliminated from here.
     
   }
 );

each entry in array of string can actually points to a js file if we join it with configuration done previously. For example ojs/ojcore, points to libs/oj/v2.1.0/debug/ojcore.js file. Each entry in this list is nothing but a dependency for the function. Each entry in terms of require is called a module.

job of require function is to call callback function once all the dependencies are loaded. So require function is going to load each js file mentioned as dependency here and once all are loaded its going to run callback function. By above require call I can say its going to load
libs/oj/v2.1.0/debug/ojcore.js, 
libs/knockout/knockout-3.4.0.js, 
appController.js, 
libs/oj/v2.1.0/debug/ojknockout.js, 
libs/oj/v2.1.0/debug/ojknockout.js, 
libs/oj/v2.1.0/debug/ojrouter.js, 
libs/oj/v2.1.0/debug/ojmodule.js, 
libs/oj/v2.1.0/debug/ojdialog.js
libs/oj/v2.1.0/debug/ojnavigationlist.js
libs/oj/v2.1.0/debug/ojs/ojtoolbar.js
libs/oj/v2.1.0/debug/ojbutton.js
libs/oj/v2.1.0/debug/ojmenu.js


NOTE: appContorller.js is not mentioned in config call as its present in same directory as main.js. So if all your libraries are present where you have main.js then you need not to mention anything in configure. But it would be a bad design


Lets put a debug point at very first line of our callback function
But as I know require is going to load dependencies first lets add few more debug points
First line of ojcore.js,
First line of ojmenu.js
First line of ojdialog.js etc

What I observe is it definitely downloads all js before calling callback function.


Now I am stopped at first line of ojcore.js
First line of ojcore.js is
define(['require', 'ojL10n!ojtranslations/nls/ojtranslations', 'promise'], function(require, ojt) {
   //Some code here
})

Its a call to define function.
Here define function is taking two arguments
1. Array of String
2. Callback function

Array of String is again nothing but dependcies and define function is also going to download all dependencies and then call callback function. It means ojcore is also dependent on some other js file or better to say module.
One question arise why not to use require when define is also doing something similar.

Require vs Define: As name suggest require is to download required libraries and then call callback function but Define is something to define. Define is actually defining a module. If you see the callback function of define must return a value. It could be anything but it must return a value. That value is associated with module and module name is nothing but what is mentioned in require call.

Effectively define is not only downloading dependencies and calling callback but also defining a module using moduleName and value.

What is the purpose of this module? This is what is passed to callback function as an input. If you see require function is taking oj, ko and app as three input. They are nothing but module value of ojs/ojcore, knockout and appcontroller first three dependencies mentioned in require call.



Now if I stop my bebugger at first line of require callback I see lots of other files are also downloaded


Can I assume that by the time require's callback is running all modules of JET are loaded. Definitely NOT. If we have to load all modules then why not to load using script tag itself. Only few required modules are loaded. Rest you will be loading when you create a viewModule.

When we create a viewModule, its js file again start of define call. Using that viewModule will load its dependencies separately. That is the advantage a module is loaded when its needed.

Few things you should notice
1. RequireJS tries to divide our code in modules. With that we should also change of coding style. We should write one module (function) in one js file. These functions should clearly know their dependencies.

2. require has so many dependencies but callback only takes three oj, ko, app as input. Why we have other dependencies. If you see first is ojs/ojcore. It creates oj global variable (namespace). All other dependencies adds its methods to oj namespace. For example ojs/ojrouter at Router related functions to oj namespace.

That's all I have here.

No comments:

Post a Comment