3. Using ES6 Modules

Normal modules are library code files that explicitly export those (variable, function and class) names that other modules can use (as implicitly frozen like const declarations). A module that is to use items from another module needs to explicitly import them from that other module using import statements. It is recommended that all JS module files use the file extension "mjs" for indicating that they are different from classical script files.

Web pages can load module files, possibly along with classical script files, with the help of a special type of script element.

The meaning of ES6 modules is based on the following principles:

  1. A JS library file can be turned into a module by using "export" for all library items. Other modules can "import" its items.

  2. Any ordinary script file that is to use one or more items from a module has itself to be turned into a module ("only modules can use modules"). Since it doesn't export anything, such a module could also be called an "import module".

  3. All modules, no matter if they export anything or are just "import modules", are separated from the global scope in the following sense: they have read access to items from the global scope such as DOM objects (like document) or other global objects (like Array), but they cannot create any names (including objects and functions) in the global scope. This implies, for instance, that a JS function defined in a module cannot be assigned to an onclick event handler attribute in an HTML file..

Using modules implies that we can no longer use the global scope for the names of functions/classes, which is a restriction that is considered a good practice in software engineering.

An example of a normal (library) module file is util.mjs with the following code:

function isNonEmptyString(x) {
  return typeof(x) === "string" && x.trim() !== "";
}
...
export { isNonEmptyString, ... };

An example of a module that imports certain items from other modules and then uses them in its own code, and also exports some of its own items is the model class file Book.mjs with the following import/export statements:

import { isNonEmptyString, ... } from "../../lib/util.mjs";
import { NoConstraintViolation, MandatoryValueConstraintViolation, ... }
    from "../../lib/errorTypes.mjs";
export default function Book( slots) {...}

Since this module only exports one class (Book), a default export us used, allowing simplified imports.

An example of a module that does not export anything, but only imports certain items, is the view code file createBook.mjs with the following import statements:

import Book from "../../src/m/Book.mjs";
import { fillSelectWithOptions } from "../../lib/util.mjs";
...

An HTML page (here: createBook.html) can load such a module with a special type of script element:

<script src="src/v/createBook.mjs" type="module"></script>

Notice that this script element's type attribute is set to "module".

Alternatively, the code of such a module can be embedded in the HTML page like so:

<script type="module">
 import Book from "./src/m/Book.mjs";
 const clearButton = document.getElementById("clearData");
 // Set event handler for the button "clearData"
 clearButton.addEventListener("click", function () {Book.clearData();});
</script>.