
The SAPUI5 framework has built-in support for modularizing comprehensive JavaScript applications. That means, instead of defining and loading one large bundle of JavaScript code, an application can be splitted into smaller parts which then can be loaded at runtime at the time when they are needed. These smaller individual files are called modules.
To load a module, the jQuery.sap.require function must be used. Assume the following page:
#!js
<!-- UI5 bootstrap tag -->
<script id="sap-ui-bootstrap" src="./resources/sap-ui-core.js" data-sap-ui-libraries="sap.ui.commons"></script>
<script>
jQuery.sap.require("sap.ui.commons.MessageBox");
function onPressButton() {
sap.ui.commons.MessageBox.alert("Hello World!");
}
</script>
At first, the SAPUI5 framework initializes and then loads the sap.ui.commons.MessageBox module. Internally, the framework analyzes the module name and derives the module URL from it:
./resources/sap/ui/commons/MessageBox.js
The module script is then loaded from that URL and executed.
A module simply is a JavaScript file that can be loaded and executed in a browser. There are no rules or definitions what code belongs to a module, and what code does not. It is up to the developer what content to bundle into a single module. But typically, the content of a module has some topic in common. Either it forms a JavaScript class or namespace, or the contained functions address a specific topic, for example client to server communication, mathematical functions, and so on.
There is also no special syntax or structure defined for modules. However, there are some features that module developers should be aware of and that they can use:
Name: A module is loaded by calling jQuery.sap.require with the name of the module. So, all modules are identified by a name. Human readers often associate a module with the main JavaScript object declared in it. Therefore, the module names by convention are a hierarchical sequence of dot-separated identifiers (like sap.ui.core.Core). It is best practice to use all but the last identifier to group modules in a logical and/or organizational way (much like packages in Java) and to use the last identifier to give the module a meaningful, semantical name, for example, the 'topic' common to the code in the module.
Declaration: Modules can declare themselves by calling the static jQuery.sap.declare function with their name. This helps SAPUI5 to check at runtime whether a loaded module really contains the expected content (compare the required name against the declared name). As a side effect, jQuery.sap.declare will ensure that the parent namespace of the module name exists in the current global namespace (window). More details can be found in the API documentation of declare.
If a module does not contain a declaration, the framework assumes that the module has the expected content and automatically declares it with the name used for loading it. In some rare cases - which are explained below - a module declaration is mandatory.
Description: Furthermore, modules can contain a description which helps others to decide whether a module is useful for them, or not. By convention, any JavaScript comment preceeding a module's declaration (jQuery.sap.declare statement) is interpreted as its description. The configuration UI displays such descriptions next to the module name.
Dependencies: Last but not least, modules can use the jQuery.sap.require method to load other modules that they depend on. While jQuery.sap.require internally has the effect of a "loadModule" call, it can also be regarded as a dependency declaration (therefore its name 'require'). These dependency declarations can be evaluated at runtime (as explained above), but they can also be analyzed at built time or at runtime on the server.
Example:
A typical module that uses all of the above features might look like this (the module name is my.useful.SampleModule);
#!js
/*
* A short documentation of the module. This documentation is not evaluated at runtime, only during build time
*/
jQuery.sap.declare("my.useful.SampleModule"); // declaration of the module. Will ensure that the containing namespace 'my.useful' exists.
// list of dependencies of this module
jQuery.sap.require("sap.ui.core.Core");
jQuery.sap.require("some.other.Module");
jQuery.sap.require("you.can.Also", "list.multiple.Modules", "if.you.Want");
...
// create the 'main' object of the module
my.useful.SampleModule = {};
As mentioned already, modules are loaded by calling function jQuery.sap.require with the name of a required module. The framework then checks whether the named module is loaded already. If so, the function simply returns. Otherwise it tries to load and execute the module synchronously. If any of these two steps fails, an exception is thrown and execution of the calling module thereby is disrupted.
To summarize it: A call to jQuery.sap.require ensures that the required module is loaded and has been executed before execution of the caller continues (*). (*) this is only true as long as no cyclic dependencies occur.
When loading a module, its dot-separated name must be transformed to an URL. This is done by replacing all dots ('.') with slashes ('/') and appending the standard suffix '.js' to it. This transformed name is then appended to the UI5 resource root URL (a prefix of the URL where UI5 has been loaded from, see explanation of bootstrap). The resulting URL is then used to load the module as a text. If loading succeeds, the module is first declared with the original module name and then executed in a global context (window). If either loading the module or executing it fails, the module name is internally marked as "failed" and an exception is thrown indicating the cause for the failure. Any further tries to load the same module will fail immediately, reproducing the same error message.
It is a common use case for web applications that different modules are located in different locations (servers, web apps). Imagine for example that your web application is deployed as an individual web app and that it contains some very important modules to be loaded at runtime. But for administrative reasons, SAPUI5 and its provided modules have to be loaded from a content delivery network (CDN) or from a centrally deployed web app. As explained above, SAPUI5 by default will try to load any required modules from its resource root URL, namely from the centrally deployed web application. This would fail for the modules contained in your web application.
Such mixed location scenarios are supported with the jQuery.sap.registerModulePath function:
jQuery.sap.registerModulePath = function(sModuleNamePrefix, sURL);
It associates a module name prefix with an URL prefix. Any module whose name starts with the module name prefix will be loaded from the registered URL instead of the standard resource root URL. In the scenario prethought above, this can be used to redirect the request for the application-specific modules to the corresponding web application:
#!js
<!-- bootstrap tag which implicitly defines the resource root as 'http://www.sap.com/sapui5/1.0/resources/' -->
<script src="http://www.sap.com/sapui5/1.0/resources/sap-ui-core.js" ></script>
<script>
// request will be mapped to http://www.sap.com/sapui5/1.0/resources/sap/ui/core/Core.js
jQuery.sap.require('sap.ui.core.Core');
// redirect the 'my.webapp' package to the local web app
jQuery.sap.registerModulePath('my.webapp', '/my-webapp/resources/my/webapp/');
// loads /my-webapp/resources/my/webapp/MyModule01.js
jQuery.sap.require('my.webapp.MyModule01');
</script>
The registered URL above contains the transformed module name prefix 'my/webapp/'. While this seems to be an unnecessary redundancy in the registration, it allows a more flexible packaging of the modules. For example, a company might decide to deploy all its specific modules named 'my.company.*' to the central URL 'http://my.company/shared/' without packaging them into a two level hierarchy of subfolders:
jQuery.sap.registerModulePath('my.company', 'http://my.company/shared/');
However, when the standard build tools of the SAPUI5 framework are used, the full package name will be part of the runtime file hierarchy and the registration must contain the transformed package hierarchy as above.
The previous section contained some explanations how dependencies between modules are resolved on the client at runtime. During development, this is the typical use case. Modules can be modified in the development environment and can be deployed as individual entities to some runtime. When the client then is refreshed - and if caching is configured properly - it will reload only the modified modules.
In productive systems however, it might be desirable to bundle again several modules into one single file. This helps reducing the number of necessary roundtrips and can thereby help to reduce the impact of network latency. However, one doesn't want to loose the flexibility and transparency of the dependency management.
The SAPUI5 framework supports this with a dependency resolution tool. It analyzes a module file and all its dependencies and creates a new file containing the original module content, as well as any required modules. It automatically avoids double inclusion of modules. The tool can be used in two ways: Either via an Ant task at build time to create a merged super module which then can be referenced in any HTML page instead of the original file; or at runtime, then using a servlet on server side.
When the runtime dependency resolution is used, the runtime maintains a list of the loaded modules. Before a new module is loaded and executed, the list is searched for it and if found, the module is not loaded again. But when the server or build-time tool is used, it creates a bigger file potentially containing multiple modules. The runtime then can only check in advance whether that bigger module has been loaded already. It does not know about the contained modules and therefore can not avoid double-loading of them. To compensate this, the dependency resolution tool wraps any embedded module with a few lines of additional coding. These additional checks will be evaluated during execution of the merged module and will have the same effect as the original runtime checks in an unmerged scenario:
...
// code of enclosing module
...
// location of a jQuery.sap.require('xyz');
if ( !jQuery.sap.isDeclared('xyz') ) { // check whether module 'xyz' has been loaded already
jQuery.sap.declare('xyz'); // will only be added if the module 'xyz' doesn't contain a declaration
// original text of module 'xyz'
...
}
...
// further code of enclosing module
...
One might wonder why multiple modules can not simply be concatenated into a bigger module. Why have the modules to be parsed and to be nested according to the original jQuery.sap.require calls? The answer simply is that this makes the runtime behavior of the merged file more predictable. As soon as you look at concrete modules with complex (or even cyclic) dependencies, order of module execution becomes significant. The main promise of jQuery.sap.require that the caller continues only after the required module has been successfully loaded and executed can be hold only if the required module is embedded exactly at the place where the jQuery.sap.require call was located.
In cases where a use of the dependency resolution tool is not possible at all, one might indeed simply concatenate modules. But then the following two criteria must be ensured 'manually' by the developer:
The order of the files must obey the dependencies. A module must not 'require' another module that is only merged later on.
All merged modules must do a declaration with jQuery.sap.declare, otherwise the framework will not know that the modules have been loaded and potentially load them again.
As mentioned already, one way of executing the dependency resolution tool is to call it via a servlet. Such a servlet has been included in the phoenix-cdn application that is part of our drop. By default, the servlet is configured to react on the URL http:// host:port /sapui5/download/configurator. It accepts several parameters
| URL Parameter | Default | Description |
|---|---|---|
| modules | None | Mandatory: A comma separated, ordered list of module names that should be included in the resulting module. If the parameter occurs multiple times, the values will be concatened |
| out | "sap-ui-custom.js" | Name of the resulting module. The resulting module will contain a declaration with that name. When the servlet is used from the configurator Web UI, then the name will also be suggested in the download dialog. |
| minimize | False | Activates the JavaScript minimization for the output (experimental feature) |
The Configurator WebUI, which is part of phoenix-cdn as well, uses the servlet to create a downloadable JavaScript file containg all selected modules.
But it is also possible to use the servlet directly from within an application page and to load UI5 and the required controls etc. in one step. The following HTML fragment shows an example (line breaks added for better readability):
<script id="sap-ui-bootstrap" data-sap-ui-theme="sap_platinum" type="text/javascript" data-sap-ui-libs="sap.ui.commons" src="/sapui5/download/configurator?modules=jquery-1.4.2,jquery.sap.global,sap.ui.core.Core, sap.ui.commons.Button,sap.ui.commons.ButtonRenderer, sap.ui.commons.layout.MatrixLayout,sap.ui.commons.layout.MatrixLayoutRow,sap.ui.commons.layout.MatrixLayoutCell, sap.ui.commons.layout.MatrixLayoutRenderer" > </script>
How to Load jquery.sap.require?
Obviously, modules can only be loaded as soon as the jQuery.sap.require function is available. The implementation of this function is located in module 'jquery.sap.global' which in turn requires jQuery itself (located in module 'jquery-1.7.1'). At runtime, these two modules can not be loaded with 'jquery.sap.require' but must be loaded by some other mean. The SAPUI5 framework includes both modules in its bootstrap files sap-ui-core.js and sap-ui-core-lean.js. The first one also embeds the SAPUI5 core functionality and needs no further modules. The second one only contains the two bootstrap modules and a require statement for the core. It is better suited for the development scenario described above (loading the modules separately).
If you create a new bootstrap file with the configuration UI and decide to include the jquery.sap.global or jquery modules, they always will be the first modules in the created file, and they will always be embedded. This ensures the availability of jQuery.sap.require.
Cyclic Dependencies
Sometimes, the functionality in two modules A and B might be interdependent. That means module A requires module B to execute and module B requires module A. We stated above that jQuery.sap.require ensures that the execution of a calling module doesn't continue until the required module has been loaded and executed. Taking this serious, cyclic dependencies could not be resolved but would lead to an endless series of requests (A->B->A->B->...).
This situation can be avoided by a workaround: As soon as a module A has been loaded and is about to execute, it is regarded as declared. So, when this module A embeds another module B which has not been loaded, module B will be loaded and executed. If B now again requires A, then the dependency resolution runtime will find that A has been declared already (despite the fact that its execution has not been finished yet) and simply returns. This workaround helps to break the endless loop, but it doesn't re-ensure the original promise of jQuery.sap.require.
Cyclic modules have to deal with that gap on their own. There are several ways/best practices how to do this:
Variant 1: Merge A and B
If the conflicts can not be resolved easily, or if the modules are so highly related that they will be used together most of the time, then merging them into one module is the most simple solution.
Variant 2: Interlaced Execution of A and B
This variant makes use of the fact that the module loading takes place exactly at the source location where the jQuery.sap.require function is executed. Let's assume that the content of modules A and B can be structured as follows:
// Module A, Part A.1
// Module Section A.1, does not depend on Module B and provides all code that module B depends on.
jQuery.sap.require("B");
// Module Section A.2, might depend on code in Module Section B.1
// Module B, Part B.1
// Module Section B.1, does not depend on Module A and provides all code that module A depends on.
jQuery.sap.require("A");
// Module Section B.2, might depend on code in Module Section A.1
Further assume (WLOG) that module A is loaded first. Then section A.1 will be executed and will be available to the outside world before the require('B') is executed. During the require, the framework will detect that B is not available yet, will load and execute it. The execution starts with section B.1 which succeeds as it does not depend on A. When the execution of B reaches the require('A') statement, the framework detects that A has been loaded already and continues without loading A again. But remember, that the code from section A.2 is not available yet. The execution of B however continues and succeeds as - by assumption - B.2 does not depend on A.2. Now, the first require('B') succeeds and returns and section A.2 will be executed. And it might use the code from section B.1.
Procedure and result are similar in the case that B is loaded first.