Modules
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.
Default vs Named exports
export default modulename
Named exports are useful when you need to export several values. When importing this module, named exports must be referred to by the exact same name (optionally renaming it with as), but the default export can be imported with any name. — MDN export
Douglas Crockfords’ advice in How JavaScript Works is to use export default
with one frozen object per module like so at the end of the module file:
export default Object.freeze({
abs,
abs_lt,
add,
/* long list of more functions */
});
The client then has something like this at the top:
import big_integer from "./big_integer.js";
I recommend having a single export, but you can use as many import statements as you need. Put your imports at the top of the file and your export at the bottom.
I got into a possibly bad habit of using namespace import, as used in the module object example provided by Mozilla, and tried to refactor as suggested by Crockford which broke my code.
Snags I hit:
- My modules contain state objects which get mutated for client modules to read. Under
export default
, only the initial version is seen whether frozen or not.
export function or variable name
The example main.js
looks like this.
import * as Canvas from './modules/canvas.js';
import * as Square from './modules/square.js';
import * as Circle from './modules/circle.js';
import * as Triangle from './modules/triangle.js';
// create the canvas and reporting list
let myCanvas = Canvas.create('myCanvas', document.body, 480, 320);
let reportList = Canvas.createReportList(myCanvas.id);
// draw a square
let square1 = Square.draw(myCanvas.ctx, 50, 50, 100, 'blue');
Square.reportArea(square1.length, reportList);
Square.reportPerimeter(square1.length, reportList);
// draw a circle
let circle1 = Circle.draw(myCanvas.ctx, 75, 200, 100, 'green');
Circle.reportArea(circle1.radius, reportList);
Circle.reportPerimeter(circle1.radius, reportList);
// draw a triangle
let triangle1 = Triangle.draw(myCanvas.ctx, 100, 75, 190, 'yellow');
Triangle.reportArea(triangle1.length, reportList);
Triangle.reportPerimeter(triangle1.length, reportList);
MDN’s illustrates a snag I hit that modules can’t see any outside variables except those prefixed by window.whatever
.
The canvas module has a function, create, which returns {ctx: ctx, id: id};
In the examples, the modules contain functions which require all the required information to be passed to them as arguments, and return values. There are no environment variables.
Modules
Many things confused me about JavaScript’s modules, and there appear to be many quirks, especially regarding scoping.
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().