34.3. JavaScript Modules II

34.3.1. The import Statement

There are some syntactical rules that we shall briefly look at. Given that the previous chapter's examples work, they conformed to those rules.

Example 34.1. Valid Syntax
import {waldo} from 'https://dkexit.eu/libs/fred.mjs';
import {waldo} from '/libs/fred.mjs';
import {waldo} from './fred.mjs';
import {waldo} from '../fred.mjs';

Rules are that there must be a path prefix in the url referencing the module file. Non observance will result in some error message on the log. The first example above may reference any url on the internet. The latter three examples are all referencing the filesystem of the server providing the page.


34.3.2. The Rationale Behind Modules

Using modules allows JavaScript files to work with code defined in other JavaScript files as long as these other files share something. In turn this takes the burdon of knowing the internal structure of the JavaScript from the shoulders of the HTML5 writer.

The modular JavaScript may grow out of a concrete project, but if it is deemed useful enough to be used in other projects as well it is best placed in a more general place on the developer platform.

34.3.3. Have You Noticed Something Capricious? Modules are Deferred

In all our examples so far in the web programming section of our program, we have been working with an event listener deferring all JavaScript action to the point in time when the DOM is fully created ie the document has finished loading. Where has that gone? How come our code above works anyway? These two questions will be answered now.

Example 34.2. Normal Order of Execution, index3.html

We have seen the following or equivalent code

<!doctype html>
<html>
    <head>
        <title>NMLs JS Class</title>
        <meta charset='utf-8'/>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <script>
            let output = function(txt) {
              console.log(txt);
            }
        </script>
        <script src='js0.js'></script>
        <script src='js1.js'></script>
        <script src='js2.js'></script>
        <script src='js3.js'></script>
    </head>
    <body>
        <h1>The Page</h1>
        <script>output('Call from the body');</script>
        <p>Check the log, please.</p>
    </body>
</html>

(Click here!) many times. An HTML5 page references four JavaScript files. The Browser handles each JavaScript file in turn, as it meets a script element. Nothing new, and nothing strange in that. It has a side effect though, the building of the page is blocked while the browser handles the script content. The log shows:

greetings from js0 index0.html:9:23
greetings from js1 index0.html:9:23
greetings from js2 index0.html:9:23
greetings from js3 index0.html:9:23
Call from the body index0.html:9:23

Example 34.3. Modules are Deferred, Alternative Order, index4.html

In order to let the DOM be built before the JavaScript is active, we have either issued a load event listener on the window, or we have placed the JavaScript code at the end of the body element. With regular scripts we may use defer to prevent blocking. Interestingly, modules are deferred by default. For at closer look at defer and async please refer to https://flaviocopes.com/javascript-async-defer/. Take a close look at this:

<!doctype html>
<html>
    <head>
        <title>NMLs JS Class</title>
        <meta charset='utf-8'/>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <script>
            let output = function(txt) {
              console.log(txt);
            }
        </script>
        <script type='module' src='js0.js'></script>    <!-- module -->
        <script src='js1.js'></script>                  <!-- normal script -->
        <script defer src='js2.js'></script>            <!-- normal but deferred -->
        <script src='js3.js'></script>                  <!-- normal script -->
    </head>
    <body>
        <h1>The Page</h1>
        <script>output('Call from the body');</script>  <!-- normal script -->
        <p>Check the log, please.</p>
    </body>
</html>

Click here and open the log. What went on here? You will find that normal script, normal as in not modules, are handled the old fashioned way when the browser reads the script element. Modules, and scripts that are explicitly deferred are not blocking the building of the page, but instead they await completion of page building, and then they are handled in the order they are presented to the browser. The log content confirms:

greetings from js1 index0.html:9:23
greetings from js3 index0.html:9:23
Call from the body index0.html:9:23
greetings from js0 index0.html:9:23
greetings from js2 index0.html:9:23

Example 34.4. Inline Scripts, index5.html

<!doctype html>
<html>
    <head>
        <title>NMLs JS Class</title>
        <meta charset='utf-8'/>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <script>
            let output = function(txt) {
              console.log(txt);
            }
        </script>
        <script type='module'>output('inline module says hi')</script>
        <script src='js1.js'></script>
        <script defer>output('inline script says hi')</script>
        <script defer src='js3.js'></script>
    </head>
    <body>
        <h1>The Page</h1>
        <script>output('Call from the body');</script>
        <p>Check the log, please.</p>
    </body>
</html>

Click here and open the log. Inline scripts that are not modules ignore defer. Inline modules behave as all modules. They are deferred. Verify here:

greetings from js1 index5.html:9:23
inline script says hi index5.html:9:23
Call from the body index5.html:9:23
inline module says hi index5.html:9:23
greetings from js3 index5.html:9:23

34.3.4. Multiple Imports of Same Module

It is quite common that several modules depend on some utility module of your own. This means that these modules all have an import of that module. We shall check what happens in suach a situation.

Example 34.5. Several Imports of Same Module
<!doctype html>
<html>
    <head>
        <title>NMLs JS Class</title>
        <meta charset='utf-8'/>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <script>
            let output = function(txt) {
              console.log(txt);
            }
        </script>
        <script type="module" src="js0.js"></script>        <!-- modules one import -->
        <script type="module" src="js0.js"></script>        <!-- modules one import -->
        <script type="module">import "./js0.js";</script>   <!-- modules one import -->
        <script type="module">import "./js0.js";</script>   <!-- modules one import -->

        <script src="js1.js"></script>                      <!-- normal repeat -->
        <script src="js1.js"></script>                      <!-- normal repeat -->
    </head>
    <body>
        <h1>The Page</h1>
        <script>output('Call from the body');</script>
        <p>Check the log, please.</p>
    </body>
</html>

Click here and open the log, resulting in:

greetings from js1 index6.html:9:23
greetings from js1 index6.html:9:23
Call from the body index6.html:9:23
greetings from js0 index6.html:9:23

The module is actually only imported once although there are many references to it. It is of course deferred. The normal script is executed as often as it is sourced.