A “real” loading indication for Aurelia and RequireJS


Aurelia is a nice and modern JavaScript framework which simplifies much the development of single page applications. One of its most criticized weakness is the  size of its libraries, which are usually loaded using RequireJS. Its tutorial comes with an explanation on how to add a loading indication to your application using nprogress. Nevertheless this loading will only kick in after Aurelia itself is fully loaded. This will exclude the most expensive part which is the download and process of at least 400kb of g-zipped JavaScript libraries. On a not so modern mobile device using a not so modern mobile network this can take some time.

The below solution aims to display the progress while loading the RequireJS modules themselves. It is theoretically applicable to any application using RequireJS.

 

Measuring the progress

RequireJS will load asynchronously each individual module and its dependencies one-by-one. This is good as the browser won’t freeze while loading and also gives us a chance to measure the loading progress. For the sake of simplification, we can state that each individual module has the same weight meaning that if my application loaded 10 out of 20 modules, it has done 50% of its job. This simplification might not render a perfect linear time progress but I’ve also never seen any.

RequireJS saves the list of loaded modules in the global variable:

window.require.s.contexts._.defined

The property defined is not an array but an object where every module is a property. Therefore simply using defined.length won’t work. To get the actual number of modules one have to count the number of properties using Object.keys method which returns an array with all the object’s enumerable properties.

var loadedModules = Object.keys(window.require.s.contexts._.defined).length;

Now that we know how many modules were loaded so far we have to find out how many modules are expected to be loaded. The easiest way is to:

  1. Load your application in a browser
  2. Open the Developer Tools window
  3. Pause the execution
  4. Evaluate at the watch window the variable Object.keys(window.require.s.contexts._.defined).length.

Finally the current progress in percentage can be calculated as:

var loadedModules = Object.keys(window.require.s.contexts._.defined).length;
var currentProgress = Math.round(loadedModules / expectedModules * 100);

 

Implementing the code

We want our progress code to be as low level as possible to avoid any additional overhead and to get immediate results. The lowest we can get is to implement it as a plain JavaScript code (without any external dependency) and add it to the index.html page itself. The same thing for the progress indication: you should avoid loading external images and just implement it with plain inline css. The website loading.io can generate some nice loading effects using css only. Also Google will love your site for having a fully self-contained page.

Whatever we do in the body of our index.html will be erased by Aurelia once the application is ready. So if you want to reuse the javascript or any of the css classes in your application, you should put it on the head section.

The base code for the progress should be:

  1. Evaluate progress
  2. If not done sleep x milliseconds and evaluate again

As JavaScript is single-threaded and cannot sleep, the best way to achive it is to call the same function again and again via setTimeout.

Finally the code should look something like the one below. You can copy/paste it to your index.html and extend it if needed. It assumes that there is an HTML element in your page with id="loadingProgress" where the actual progress is displayed. To implement a progress bar one could have two DIVs with different colors and manipulate their widths in the outputProgress function.

You can find a working example here: https://www.kopf.com.br/puzzle

 

        /* 
        Add this code as a <script> inside of your index.html
        Set expectedModules to the number of your RequireJS modules
        */
        var expectedModules = 177;
        function outputProgress() {
            var loadedModules = 0;
            // Make sure that requirejs is loaded and welll defined to avoid any exception here
            if (window && window.require && window.require.s &&
                window.require.s.contexts && window.require.s.contexts._ &&
                window.require.s.contexts._.defined)
                loadedModules = Object.keys(window.require.s.contexts._.defined).length;
            
            // Calculate the progress
            var progress = Math.round(loadedModules / expectedModules * 100);

            // Get the HTML element with id="loadingProgress" 
            var elem = document.getElementById("loadingProgress");
            if (elem) {
                if (progress > 100) {
                    // Oops, something went wrong. We underestimate the number of modules
                    progress = 100;
                }
                // Set the progress text
                elem.innerHTML = progress + "%";
                if (progress < 100) {
                    // Evaluare again in 100 milliseconds
                    setTimeout(outputProgress, 100);
                }
            }
        }
        // Initial call done in the global scope
        outputProgress();

 

 

 

 

 

Leave a comment

Your email address will not be published. Required fields are marked *