Blog-Archiv

Dienstag, 26. Februar 2019

How to Flatten AMD JavaScript

I have written a lot of JavaScript goodies which I would like to apply sometimes here to make things more fancy. As this Blog doesn't allow me to upload scripts, I need to copy & paste the script code into the HTML.

Now my script goodies are written as AMD modules. AMD is a special way how you can organize dependencies, I would say it is the safest and most advanced, free of magic, dynamical by nature. But AMD-packed JS code is not so easy to use for copy & paste, there have been things done to avoid exactly this, and pasting also the AMD loader into each Blog page is definitely too much. The following shows a quick way how to "flatten" AMD-packed code.

AMD has not yet been fully replaced by ES6 imports. At the time being, all imported ES6 code is loaded initially by the browser, which is NOT dynamical!

Understanding AMD

  1. First AMD requires to load the AMD loader itself into the HTML page. Assumed the loader implementation is in file lib/define.js below the page, this could be done by following HTML code:

      <script src="lib/define.js" data-path="lib"></script>
    

    Mind that you need a closing </script> tag here, it would not work with "/>".

    The src attribute tells the browser where to load the script from. The data-path attribute tells the AMD loader the base path where the AMD libraries are, relatively to the page (the loader will search its own HTML element and read this attribute). Such a base path is needed when you have HTML pages in different directories, but all want to refer to the same JS libraries.

  2. After this you can use the global define() function to load dependencies, e.g. by following code in the HTML page that retrieves an alert-message from dependency "jokes/bouuh.js":

    <script>
        define(
          [
              "jokes/bouuh.js"
          ],
          function(message) {
              alert(message);
          }
        );
    </script>
    

    This define() call receives two parameters that are associated with each other. The first is an array of dependencies, given as paths relative to data-path. The second is a function that accepts as many parameters as the dependency-array has members, each dependency-member will yield an object that gets passed as parameter to that function. That means, function(message) receives whatever the execution of "jokes/bouuh.js" returns. Of course these return-objects are cached by the loader, they will be singletons, and every other "jokes/bouuh.js" dependency will receive the same object.

    Modules that do not have dependencies can leave out the first parameter, the loader will detect that and will just execute the function-parameter.

    This example is a top-level call, so it will be executed when the script element gets loaded. The define() being nested inside some other function, dependencies would get loaded deferred at the time the function was executed - NOT possible with ES6 imports!

  3. Every AMD script file returns exactly one singleton. The singletons are meant to be factories for objects in case instances are needed. Here is an example AMD module file:

    lib/jokes/bouuh.js
    define(
        function() {
            return "Bouuh!";
        }
    );
    

    The dependency-array is missing, define() has just one parameter, this is legal. The function inside define() will be executed by the AMD loader, and its return will be buffered as singleton for the key "jokes/bouuh.js". The return is the string "Bouuh!", thus the alert() call above will show us "Bouuh!".

How to Flatten an AMD Dependency Tree

Nice things, but all in the way when you are forced to abandon dynamic dependency management and do primitive copy & paste work for some Blog article.

Knowing how this works we can simulate what an AMD loader does:

  • load dependencies and pass their return objects as parameters to the function (2nd parameter),
  • cache the function's return as singleton and pass it further to any dependent module.

Assign AMD Module to Variable

Dig down the dependency root and find the AMD modules that do not depend on anything. For each, create a variable and assign the return of the executed module function to it. Here I did this for "jokes/bouuh.js":

var jokesBouuh = function() {
    return "Bouuh!"
}();

Try to name the variable like the module file name. And mind that you need these IIFE parentheses after the closing curly brace! This stands for the function execution through the AMD loader, replacing the define() call.

Pass Variable as Parameter

To any dependent module, pass the variable(s) as parameter(s).

function(message) {
    alert(message);
}(jokesBouuh);

Of course it will not be so simple as shown here, but that is all you need to flatten AMD scripts.

Resume

JavaScript dependency management creates complicated structures. AMD is a recursive concept, and a reliable and efficient mechanism, but it is not so easy to practice and understand. Especially when it comes to using the AMD singletons as object factories, flattening such modules will not be easy. The usage of an AMD module should be documented exhaustively in a comment header, else the chance is high that it won't be understood and be applied wrongly.

Both AMD and functional inheritance use just language means to solve their problem. That makes them outstanding and clear concepts. The world is going different ways, promoting ES6 imports and classes. Although that didn't solve everything cleanly, it will be easier to read - is it?




Keine Kommentare: