Frontier Software

Modules

Modules

Many things confused me about JavaScript’s modules, and there appear to be many quirks, especially regarding scoping.

Declaration

The modern way is to load a main.js file in the head of an HTML document with the attribute type="module":

<head>
...
  <script type="module" src="/js/main.js"></script>
</head>

This replaces the old way of loading the file before the closing </body> to ensure the DOM objects used by the JavaScript code were known to it. Something that tripped me up is the type="module" attribute means main.js is a module client, aka importer, not an exporter, ie what’s generally thought of as a module.

Namespace import

aka Module namespace object

Of the various ways of importing modules, I’ve opted for import * as myModule from "/my-module.js"; which as explained in creating a module object allows the importer to access the functions in the module as myModule.function1(arg1, arg2).

Originally I created a namespace (ie object) in the module file (with the same name excluding the suffixe) and then used export default myModule, but this seems to imply the module houses a class which the importer needs to initialise with new rather than a namespace. The import * as myModule from "/my-module.js"; seems to be the official style for what I want.

Sharing data between modules

That modules don’t share scope in some ways encourages low coupling which is considered good design.

The best form of coupling is by passing only data. One way to do this in bulk is to pass a global object as a parameter. According to jslint, modules should freeze their objects to avoid mutation.

JavaScript modules can be both importers and exporters, remembering imported values can only be modified by the exporter.

But I think having modules import other modules encourages bad (ie high) coupling. Furthermore with browsers there’s the snag filenames need to be changed once edited to so old, cached versions aren’t used. Having to update multiple files is bound to lead to problems.

Here are some hacks to share data when required between modules:

Global window namespace

window.canvas = document.querySelector("canvas");
window.ctx = window.canvas.getContext("2d");

Making such global variables is frowned upon. Please try to avoid them. — javascript.info

My view is DOM elements such as canvas, ctx etc are pinned to the browser’s glogal window object anyhow, so that’s the easiest way to share them between modules.

Web Storage API

localStorage.myKey = "myValue";

or

localStorage["myKey"] = "myValue";

Apparently it’s better to use localStorage.setItem("myKey", "myValue"); and localStorage.getItem("myKey");. getItem has the advantage of being testable:

if (!localStorage.getItem("bgcolor")) {
  populateStorage();
} else {
  setStyles();
}

Other that when a test is needed, simply treating localStorage like any other namespace object seems better to me, with one important caveat:

Storage only supports storing and retrieving strings. If you want to save other data types, you have to convert them to strings. For plain objects and arrays, you can use JSON.stringify().