Module Optimization by using RequireJS in ASP.NET MVC


Tarun Kumar Chatterjee
.Net – Technology Specialist
Published On :   26 Jun 2017
Visit Count
Today :  1    Total :   184
Plan, Migrate, Secure, Report
SharePoint & Office 365 Tool. Simple & Easy to Use. 15-Day Trial!

SharePoint Office 365 Tool
Simple & Powerful Tool for Migration, Security & Reporting. Free Trial


In my previous article I have explained about some basic idea and implementation on RequireJS. In this article let me explain on Module Optimization using RequireJS and there are ways to use this approach with ASP.NET.

So the obvious answer that most of you already thought of that, let's use ASP.NET Bundling and Minification. We can put all those scripts into a bundle and allow ASP.NET to serve us a single optimize file, like so:

/App_Start/BundleConfig.cs:

 bundles.Add(new ScriptBundle("~/bundles/libs").Include(
                     "~/Scripts/lib/signals.js",
                     "~/Scripts/lib/hasher.js",
                     "~/Scripts/lib/jquery.js",
                     "~/Scripts/lib/jquery.min.js",
                     "~/Scripts/lib/knockout.js"
                 ));
 
 

BundleTable.EnableOptimizations = true;

There is a problem though, we cannot just put a Scripts.Render("~/bundles/libs") into our page, because that would create a separate <Script> tag to load the bundle, RequireJS will show an error because it insists in being sole manager of script dependencies (which makes sense since they are loaded asynchronously). So the solution is to change RequireJS's configuration to ask for these using the 'bundles' option.

Let’s first create an ASP.Net MVC application

Download the scripts from nuget library like mootools, hasher, singles, require etc. Below snapshot depicts Scripts hierarchy. We have kept all the custom scripts under app/home.

clip_image002

Below are the custom script codes:

config.js:

 var require = {
     bundles: {
         '/bundles/libs?v=2015': [
                 "signals",
                 "hasher",
                 "jquery",
                 "jquery.min",
                 "knockout"
         ],
         '/bundles/app?v=2015': [
             'method',
             'jQueryFile'
         ],
          '/bundles/custom?v=2015': [            
             'CusScript'
             ]
     },
     paths: {
         "bootstrap": "/Scripts/bootstrap.min"
     }
     //, shim: {
         //"bootstrap": { deps: ["jquery"] },
         //"angular": { deps: ["jquery"] }
     //}
     //,waitSeconds: 200,
 };
 

jQueryFile.js:

 define('jQueryFile', ['jquery', 'method'], function ($, method) {
     
     $('#clickMe').click(function () {
         
         method.changeHTML('Clicked');
         method.showAlert('Clicked');
         requirejs(['CusScript']);
         requirejs(['bootstrap']);
     })
     console.info("app started");
 }); 
 

method.js:

 define('method', ['jquery', 'knockout'], function ($, knockout) {
     var methods = {};
         
     methods.changeHTML = function(argument)
     {
         $('body').html(argument);
     }
     methods.showAlert = function (argument) {
         alert(argument);
     }
     return methods;
 }); 
 

CusScript.js:

 define('CusScript', ['hasher'], function (hasher) {
 }); 
 

In BundleConfig.cs add the following bundles

 bundles.Add(new ScriptBundle("~/bundles/require").Include(               
                 "~/Scripts/app/config.js",
                  "~/Scripts/lib/require.js"
             ));
 
             bundles.Add(new ScriptBundle("~/bundles/libs").Include(
                     "~/Scripts/lib/signals.js",
                     "~/Scripts/lib/hasher.js",
                     "~/Scripts/lib/jquery.js",
                     "~/Scripts/lib/jquery.min.js",
                     "~/Scripts/lib/knockout.js"
                 ));
 
             bundles.Add(new ScriptBundle("~/bundles/app").Include(                
                 "~/Scripts/app/home/method.js",
                  "~/Scripts/app/home/jQueryFile.js"
             ));
 
             bundles.Add(new ScriptBundle("~/bundles/custom").Include(
                "~/Scripts/app/home/CusScript.js"
            ));
 BundleTable.EnableOptimizations = true;
 

Added the below piece of code in bottom part of Shared/_Layout.chtml code:

 @Scripts.Render("~/bundles/require");
 Home/Index.chtml code: 
 @{
     ViewBag.Title = "Home Page";
 }
 
 <div class="row">
     <div class="col-md-4">
         <h2>Getting started</h2>
         <p>
             
             <button id="clickMe">Asynchronous Module Defination</button>
             
             @section scripts {
             <script type="text/javascript">
                 alert('before required keyword');
                 requirejs(['jQueryFile']);
             </script>
             }
             
         </p>
       </div>
 </div>
 
 

Now build the solution and run

The order here is important, we load up the configuration settings first then the actual require.js library, which will automatically detect those settings and apply them. All the libraries that were being requested manually, will now be managed by RequireJS as modules.

clip_image004

Click on Ok, it will go to load 'jQueryFile' from Index.chtml. Within the 'jQueryFile' we have the reference of method.js and jquery.js. jquery.js is the part of '/bundles/libs?v=2015' and method.js is the part of '/bundles/app?v=2015'. So, both bundles will be loaded.

clip_image006

Click on “Asynchronous Module Definition” button, it will show the alert which we defined within 'jQueryFile' module: method.showAlert('Clicked') .

clip_image008

Click on OK. It will go to load ‘CusScript’ and 'bootstrap'. Again, 'CusScript' is the part of '/bundles/custom?v=2015' and 'bootstrap' has defined standalone within the config paths. So, in this call '/bundles/custom?v=2015' and 'bootstrap.min.js' both will be loaded successfully.

clip_image010

This is how some of the libraries were defining their modules (from their original sources):

// knockout.js

if (typeof define === 'function' && define['amd']) {

// [2] AMD anonymous module

define(['exports', 'require'], factory);

}

// hasher.js

if (typeof define === 'function' && define.amd) {

define(['signals'], factory);

}

This was the problem, those modules were unnamed, which when bundled together prevented RequireJS from finding them among the rest. This is not an issue when the modules are loaded individually, since you usually 'map' the module to the file but if we put all those scripts into a single bundled file, then RequireJS will not be able to find any anonymous (unnamed) modules.

The quick solution, go into each source library and verify and fix by providing a default name like this:

// knockout.js

if (typeof define === 'function' && define['amd']) {

define("knockout", ['exports', 'require'], factory);

}

// hasher.js

if (typeof define === 'function' && define.amd) {

define("hasher", ['signals'], factory);

}

Happy Coding

Tarun Kumar Chatterjee

SharePoint Usage Reports
Usage reports, collaboration and audit for SharePoint.
Categories

Migratiin Tools for SharePoint