Modularization#
A module is an object. The private parts stay inside. The public functions are what the markup calls. The module name is the prefix for its attributes.
A module comes together from three parts:
- The markup: elements tagged with the module's namespaced attributes.
- The JavaScript: the object.
- The contract: the markup names the attributes and calls the public functions. The JavaScript reads and writes the DOM through those same attributes.
Object#
The object is built by an IIFE. The IIFE keeps the private parts inside and returns the public API.
const Accordion = (() => {
const item = (ctx) => { /* ... */ };
const Show = (ctx) => { /* ... */ };
const Init = (ctx) => { /* ... */ };
return { Init, Show };
})();
<div accordion-space>
<div accordion-item> ... </div>
</div>
Accordion is the object. accordion-* is the markup. The object and the markup share one name.
Instances#
The same module can be placed many times on one page. Each instance keeps its own state. Two accordion-space blocks do not share it. One is open while the other stays closed. There is no instance list and no id to track. A module is written once and placed as often as the page needs.
Naming#
A module is one global name. Accordion is global, and so is every toolkit. Each stands on its own: take one file, drop it into a page, and it works. There is no App to attach it to first.
Many modules mean many globals. When that is a concern, take one: App. A module's name is then a path under it, with dots: App.Catalog.Product. The last part is the module. The parts before it say where it lives, like folders. App.Catalog.Product is one module. App.Catalog is a place, not a module that owns it.
The markup uses the same name as its prefix, without App: catalog-product-space, catalog-product-item, onclick="App.Catalog.Product.Add(this)".
Which names exist under App is the app's own decision. By area: App.Catalog, App.Ordering, App.Billing. By team: each team takes one prefix and everything under it. That is Conway's law: the system takes the shape of the organization that builds it. By layer: App.Ui for generic widgets, the domain names next to it. Which split fits depends on the app. The pattern asks for one thing: one module, one name.
A new module takes a free name. There is no registry to update and no import to add. Deleting the script removes the module.
Signature#
A module signature is the contract between the JavaScript and the HTML, without a type system. It lists the namespaced attributes, the values they hold, and the functions that run on them. It also shows nesting, where one part is inside another. That matters when a function depends on the hierarchy.
< todo-space>
< todo-item todo-item-status="done|active">
< onclick="Todo.Toggle(this)">
Todo.Save()
Styling#
A module keeps state in attributes. CSS can target those same attributes directly. The JavaScript reads and writes the attribute. The CSS styles from it. A restyle needs no change to the JavaScript. A class name that the JavaScript also sets would couple the two to the same name instead.
[todo-item][todo-item-status="done"] { text-decoration: line-through; }
Why#
No module system to set up. A module is a plain object returned by an IIFE. There is no import graph and no bundler deciding what ships. A module is defined and it is there. A module is added by including a script. Deleting the script removes it.
Globals, on purpose. Modules are on the global scope. There is no IoC container and no event bus. The prefix keeps names apart, the same way BEM keeps CSS class names apart. It works by agreement, not by tooling.