So far in this chapter, we have talked about what module augmentation is in general and have also covered the loose augmentation technique. It is time to explore a different technique used in augmenting modules, known as tight augmentation. You might wonder if tight augmentation is the opposite of loose augmentation, and you are correct to think that, but with a couple of considerations which we will talk about a little later.
Tight augmentation is used for adding properties (functionality) to our modules when we do want to enforce a set order of file loading and code execution, and therefore it is less flexible. This type of augmentation is usually used when we need to make sure that a certain property from the original module is available for our augmenting code.
Tight augmentation of ImagesInc_GlobalData
Consider our augmenting code for ImagesInc_GlobalData module from the
previous section, when loose augmentation was being used. As mentioned previously, since we are passing a reference to ImagesInc_GlobalData or an
empty anonymous object into our IIFE, we can load our original module and our augmenting code in any order that we like.
This was shown as follows:
var ImagesInc_GlobalData = (function(coreModule){
coreModule.someText = "this is a test for loose module augmentation";
coreModule.getExtendedModuleMsg = function(){
ImagesInc_LoggingHandler.logInfo(coreModule.someText); };
return coreModule;
})(ImagesInc_GlobalData || {});
However, this also means that if we wanted to override one of the original module's properties, depending on what code gets loaded and executed first (the original module or the augmenting code), our "override" might be overridden by another piece of code, unintentionally and unexpectedly.
To understand this better, let's create another file, Modules_3.js, and add the
following code to it:
var ImagesInc_GlobalData = (function(coreModule){
coreModule.someText = "this is a test for overriding module properties with loose augmentation";
coreModule.getExtendedModuleMsg = function(){
ImagesInc_LoggingHandler.logInfo(coreModule.someText); };
return coreModule;
})(ImagesInc_GlobalData || {});
Also, let's load this file in our application as follows: <script type="text/javascript" async src="js/ Modules_3.js" ></script>
<script type="text/javascript" src="js/Modules_2.js" ></script> <script type="text/javascript" src="js/Modules.js" ></script> Here we are loading two files (Modules_3.js and Modules_2.js), which augment
our original module. Modules_2.js is being loaded before Modules.js, but
Modules_3.js can be loaded in any order since we are using the async property on
the <script> tag for this file. This property tells the browser to load the file in any
order that it can.
Both of these augmenting codes add the same property, coreModule.someText, to
the original module. However, depending on which code is loaded and executed first, only one of two pieces of text will be printed in the console.
We can test this by executing the following code: ImagesInc_GlobalData.getExtendedModuleMsg(); The console will display one of the following:
• this is a test for module augmentation (from Modules_2.js)
• this is a test for overriding module properties with loose augmentation (from Modules_3.js)
Keep in mind that in this scenario, we have no control over which one of the strings will be the value of the coreModule.someText property after all the code execution
is completed. This is because we don't know which augmenter code will be loaded and executed last. This also means that by using loose augmentation technique and asynchronous loading, augmenter code precedence is determined dynamically at runtime and not necessarily in the order that we think or desire.
On such basis, if our intent was that the value of coreModule.someText should
be overridden by the code in Modules_3.js, then we could not be sure of such an
override taking place.
Simulating asynchronous script loading
To simulate the asynchronous loading of our augmenters, you can reload the index.html page a few times in a row (from the application code accompanying this chapter). You'll see that the message displayed in the console may change from time to time. The message displayed depends on which file, Modules_2.js or Modules_3.js, is loaded first by the browser.
Tight augmentation, on the other hand, guarantees the order of code execution and therefore how our modules get augmented. By using this technique, we can be sure that when a module property is overridden, it will be in the order that we intended and the result will be as expected.
This guarantee is provided by the fact that we don't have any choice but to load our module and its augmenting code in the correct order, otherwise a code execution error will be generated.
Let's examine this by modifying the code in our Moduels_3.js as follows:
var ImagesInc_GlobalData = (function(coreModule){
if(!coreModule){
ImagesInc_LoggingHandler.logError('coreModule was not found to be augmented!');
alert('coreModule was not found to be augmented!'); return false;
}
coreModule.someText = "this is a test for overriding module properties with TIGHT augmentation";
coreModule.getExtendedModuleMsg = function(){
ImagesInc_LoggingHandler.logInfo(coreModule.someText); };
return coreModule; })(ImagesInc_GlobalData);
In this version of the augmenting code, we are no longer passing in a reference to an empty anonymous object to our IIFE. Therefore, if ImagesInc_GlobalData module
has not been already loaded, we cannot augment it with any new properties. Note that at the start of the preceding code we are checking to see whether
coreModule exists, and if not we are using our ImagesInc_LoggingHandler module
to log an error to the console. We are also using an alert box in the browser to make sure that the situation really catches the user's attention (try not to use alert boxes in production code as it looks unprofessional; I'm just using it here for ease of demonstration).
Loading ImagesInc_GlobalData augmenting
code
To examine how tight augmentation enforces a set order of script loading and code execution, we can change our index.html as follows:
<script type="text/javascript" src="js/Modules_3.js" ></script> <script type="text/javascript" src="js/Modules_2.js" ></script> <script type="text/javascript" src="js/Modules.js" ></script> As you can see, we are no longer loading the Modules_3.js file asynchronously
and it will be the first module-related file that gets loaded. Considering that we have modified the augmenting code in this file to only augment the module when the module (ImagesInc_GlobalData) is already present in the global scope, an error
message will be logged and an alert box will be displayed in the browser when we load the page.
Because we are using the tight augmentation technique now, we need to load this augmenting code after either Modules.js or Modules_2.js. This is necessary so we
can be sure that the ImagesInc_GlobalData module (object) is already present in the
global scope.
Also, since our intention is to override the value of someText by using the code in Modules_3.js, and this property was added to the module by the augmenting code
in Modules_2.js, we need to load both Modules.js and Modules_2.js first. This is
the only way we can guarantee that an override of the value of someText is taking
place as intended.Therefore, to achieve the proper override, we need to modify the loading order of our scripts as follows:
<script type="text/javascript" src="js/Modules.js" ></script> <script type="text/javascript" src="js/Modules_2.js" ></script> <script type="text/javascript" src="js/Modules_3.js" ></script>
This rearrangement of the order of scripts in our index.html file ensures that
the override of the value of the someText property for the module will produce
the expected result. This is of course, because our original module is loaded first, then it is augmented with the someText property, using the augmenting code in Modules_2.js. At the end, the value of this property is overridden by the tight
augmenting code in Module_3.js.