Core Definitions
Core definitions can be placed at the top level of Modulo code. That is to say,
the definitions listed here (Artifact, Component, Configuration, and Library)
can all be defined at the top level of file loaded by Modulo or
Library, or embedded directly within a Modulo tag in the <head>
of the
document. Typically, documentation on this page is most useful when you want to
dig into configuring Modulo to behave a certain way across multiple pages, or
interacting with other frameworks and versions.
Artifact
The Artifact definition is how you can specify custom build artifacts. When the build command is run, Modulo loops through all defined Artifacts and outputs the files they generate. This is how the JS, CSS, and HTML files get generated whenever you run build by default. For most users, this is enough--this website uses the default Artifacts, for example. However, if you want to create custom build outputs for advanced usage in building a SSG-powered site, then you can use this feature to create as many extra files per HTML file you SSG as you'd like. For example, you could loop through images, generating a "thumbnail" page for each one. It's very versatile, and gives you the full power of the Modulo templating language for JS, CSS, and prerendered / SSR'ed HTML.
It manages to be this versatile by functioning as a mini-scripting system for specifying and configuring builds, allowing for easy addition of minification, split bundles, or other file types. This gives very fine-grained control over output in a declarative way.
Alpha API: Note that the Artifact definition is still in flux. In lieu of fully documenting while it is being changed, examine the default build artifacts in DEVLIB.
Component
Low-level interface - Component developers will typically only need to use the
name="..."
attribute on Component, and sometimesmode="vanish-into-document"
for page-level components. Similarly, none of therenderObj.component
properties are typically useful for component developers. Instead, they are used internally by CParts (notably Template) in order to move along the rendering cycle.
The Component definition is the most central to Modulo. It's how we register components to be mounted on the page, and define the CParts that go inside those Components. It's also what stitches together the "machinery" that makes reconciliation and rendering even possible. In other words, the Component is what handles the "low-level" operations of mounting, reconciling to generate patches, and applying those patches to rerender, and the interface described here allows for low-level manipulation of this process for unusual circumstances when it's needed.
renderObj
The Component reads several properties from the renderObj
, at different
lifecycle phases. Read below on how to interface with it.
component.originalHTML
- Read-only - This contains the initial HTML that the element had when mounted on the page.component.innerHTML
- Write-only - Assign to this to cause the Component to attempt to reconcile the current HTML with the target HTML provided. It functions a little bit like HTML's built-in "element.innerHTML": Assign a string containing HTML code to this property to see the HTML appear on the page. This is what the Template does behind the scenes. Unlike HTML's built-in "element.innerHTML", assigning to this renderObj property will not cause an full update, overwriting everything that exists, but instead will cause the Component to employ the "reconciler" specified to generate a patches list (see next). By default, it will then also apply those patches in theupdate
lifecycle.component.patches
- Between thereconcile
andupdate
stages, the "patches" property is exposed to allow access to or modification of the patches that the Component intends to apply. This is an advanced feature that gives fine-grained control to how rendering works, and thus is only useful in rare situations where that control is needed. For example, you could use this property to "intercept" the patches that are about to be applied to your component while re-rendering, and, for an example, forbid the removal of attributes by deleting all patches with "removeAttribute". This is in the format of an Array. Each item in the Array is itself a 4-Array (Array of length 4), in the following format:[element, method, arg1, arg2]
. For example, to do a "removeAttribute" patch on an "id" attribute for a given "target element" (a real DOM node on the page somewhere), it might be[(ref to "target element"), "removeAttribute", "id", undefined]
If the Array is empty, then no patches will be applied.
attrs
Components use their attributes to set several configuration options. They take the following attributes:
name
name
- REQUIRED - The "name" of the component. Conventionally, it should
be written in camel or dromedary case, like how class names are written in
JavaScript: LikeThis
. Technically, it's case insensitive, so a component
named like LikeThis
, imported with an x-
namespace, could also be used like
x-likethis
or X-LIKETHIS
.
rerender
rerender
- default is rerender="event"
. Specify mode to change the
render mode of this component. A detailed discussion of valid options are
below:
rerender="event"
- The default behavior, where the the component will rerender after ever event it handles (e.g. after every user interaction). This is typically desirable since usually every user interaction will cause some visual change that needs to be reflected.rerender="manual"
- Modulo will not auto-rerender in any situation. This Note that if this is set, you will have to manually rerender, such as in a Script CPart (element.rerender()
), or in a custom CPart (this.element.rerender()
) is invoked. This is useful if there are many events that don't cause changes (e.g. mouse movement), but you have discovered that the extra rerender invocations are impacting performance, even though they aren't generating patches to modify the DOM.
mode
mode
- default is mode="regular"
. Specify mode to change the DOM root
render mode of this component. This changes what is considered to be the root
of the element, and thus where the content of the Template goes during
reconciliation. In "regular" and "vanish", it's the custom component itself
(which will be removed after rendering with "vanish"), in "shadow", it uses an
attached shadow DOM as the root, and with "vanish-into-document" it replaces
the entire page. This allows you to isolate outside CSS from your component
using "shadow", do one-time renders only with "vanish", or make your component
replace the entire document with "vanish-into-document". A detailed discussion
of valid options are below:
mode="regular"
- The default behavior, where the content generated by this the element will be attached to the regular DOM, as the element itself. This means that CSS stylesheets attached the normal way (e.g. with a "link" tag) will affect the contents of this component. Style CParts are, however, automatically scoped to the component, so a selector likep {...}
will get prefixed likex-MyComp {...}
when CSS files are being generated from Style CParts. Keep in mind the normal CSS rules will still apply, meaning that auto-scoped "p" tag will also affect children, grandchildren, etc of sub-components (unless those are "shadow" based).mode="shadow"
- Use the so-called "shadow DOM" to render the content generated by this component. While the shadow DOM may sound like a sci-fi villain, it's a mechanism browsers provide for custom elements to isolate their contents from CSS selectors or other JavaScript libraries. This isolation means means that CSS stylesheets attached the normal way (e.g. with a "link" tag) will not affect the contents of this element. Note that other than the isolation provided, the shadow mode should work exactly the same as regular. That said, Style CParts will still be automatically scoped to the component, but will use the shadowDOM feature to more strictly enforce that scoping. Note that shadowDOM CSS will not get included in the CSS file bundle, but instead will be bundled in the JavaScript source code. For a component by itself, switching from "shadow" to "regular" or vice-versa shouldn't change behavior at all. It's only useful when CSS from one component or a third party CSS library is "getting inside" your other components in an undesirable way, and you want to isolate them.mode="vanish"
- This less-used setting causes the component to "vanish" after rendering, or replace itself with it's children. Setting this will cause the component to remove itself after the first time it renders, effectively using the component as a simple template. All CParts will become useless after the first rendering, so CParts like Script or State are rarely useful in combination with this mode. This "one-time render" feature is most useful for static pages when you don't want your custom components to get in the way of the DOM that is generated, and for creating static-site generators, when you just want to generate plain HTML, with no Modulo JS or behavior in the end.mode="vanish-into-document"
- This setting is useful in one situation: When you want to create a "page" level component that changes tags that belong in the document head, such as<title>
. Like withmode="vanish"
described above, setting this will cause the component to remove itself after the first time it renders. However, with vanish-into-document, it will instead replace the entire page. It will also attempt to correctly insert all tags that belong in the document head (meta, title, link, script to be specific), causing link and script tags alike (e.g.<script src=".."></script>
) to load. Finally, the document will be wiped, and anything else it finds will be put directly into the document's body, for a clean DOM structure that removes itself entirely during this "one-time render".mode="custom-root"
- This is rarely useful, but allows for setting some other DOM element as the new root element for the component to target for rendering it's content. This is done by assigning a value torenderObj.component.root
before reconciliation. If no.root
value is set, an error will occur.
Directives
The Component defines 3 directives:
[component.event]
(shortcut:@
) - Attach event listeners to DOM elements, and remove them when the DOM elements are removed. (For jQuery users, this is used for similar purposes as "live" (delegated) events, but is faster.)[component.dataProp]
(shortcut::
) - Attach data to a DOM element's.dataProp
object, which can be used to directly passrenderObj
values as Props or events[component.slot]
- Reattach the original child elements that this component had when it was first mounted to a new DOM element. (For React users, this is similar to doing{this.props.children}
.) This can be used in addition to or instead of the<slot>
interface. For more on the slot interface, see Mozilla's [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot](The Web Component Slot element). Note that Modulo uses this interface for bothmode="shadow"
(e.g. usingshadowRoot
as the root element for reconciliation) and all the other DOM rendering modes (e.g.mode="regular"
,mode="vanish-into-document"
). For Shadow DOM, it will fall-back on Browser behavior. For everything else, it will detach any nodes it finds and reattach the nodes just before the beginning of the reconciliation process (it's DOM load-directive).
Configuration
Use a Configuration definition to configure Modulo at the global level, or extend Modulo's functionality with JavaScript.
For example, suppose you wanted to configure Modulo to allow Components and
Libraries to be defined "loose" within the head of an HTML document, along with
the Modulo definition. Here is an example of setting the
modulo.config.domloader.topLevelTags
configuration attribute to do so:
Configuration can also be used as a script tag for more fine-grained control. This will only be run once, within an isolated context, so temporary variables are discarded.
Configuration's use as a Script tag becomes useful for integrating third party libraries, as a sort of simple module and adapter system. Examine the following for a complete example of registering the "snarkdown" module from NPM as a templateFilter, allowing it to be used by components as a filter in the HTML Templates:
Integrating analytics
If you have some custom analytics script to integrate, it can be done this way. Examine the following snippet that was taken from the Fathom Lite mini-analytics software:
(Note that ZZ.XXXXXXXX.YYY
would be replaced with a real domain)
Library
Use a Library to share Component "Library files" between components. The
most typical usage is in conjunction with -src
and
namespace
, as below:
attrs
Libraries use their attributes to configure the Components that they load. That means their attributes are the same as the Component class attributes, except they are "applied" across all child components. This means you can configure any configuration option on Components. However, you typically only want to set namespace, and possibly mode.
namespace
-- default:
namespace="x"
- The namespace prefix that goes before the component when registering on
the DOM. This is the most common thing you will set on a Library, since
it allows you to "namespace" or isolate the components that you load and
prevent names from conflicting. For example, you can import two
Libraries, each with a
name="Modal"
component, but by giving them separate namespaces, you can distinguish them with something likelib1-Modal
andlib2-Modal
, instead ofx-Modal
for both, which would get confusing.
- default:
mode
- Override the default mode of loaded components.
- You can use this to force a Library to have vanishing components
(
mode="vanish"
), or have components that isolate their style (mode="shadow"
). - Note that if you are loading somebody else's component library and overriding the component modes with this attribute, it may not work correctly (or at all!), since many components may require they stay on the page after rendering for the JavaScript and other functionality to work. However, some components may work either way, allowing a lighter-weight usage with "vanish", a style-isolated version with "shadow", or an interactive, non-isolated version as default.
Modulo
This is the "launching off point" or starting point of all Modulo projects and Modulo code. The "Modulo" definition is what then contains Components, Libraries, and others.
Modulo.js, while in "dev" mode (e.g. not the "built" version), will by default search for a Modulo definition in a few different places:
child of head - Defined already in the head (NOT nested in any tags) by the time the Script tag appears. It will read it synchronously, meaning synchronous tags will be registered immediately after loading the src="Modulo.js", blocking, inline with page load. This is the default way that embedded Modulo projects work, such as when you start with a blank file and paste in a Modulo snippet. Note that embedded (NOT using
-src=
) core definition scripts (such as Configuration) will be run synchronously and blocking while the page is loading, possibly with the document open for writing.child of body - Defined in the body at the top level (NOT nested in any tags). Modulo.js will read it asynchronously after the DOM has finished parsing (the
DOMContentLoaded
event). This will happen if yoursrc="Modulo.js"
tag is in the body of a normally formed HTML page, or is loaded asynchronously or after the document has finished writing. Component registration will not be synchronous or blocking.
Example
Using a bare "Modulo" tag:
Note that this is only good if it is NOT being embedded in an HTML document, or if you are sure that it will not need any more complexity than this (simply because things like Script tags and Style tags will not function correctly in this situation, as they will get executed when the page loads causing "leakage").
As a "template" tag, for embedding:
Condensed "script" tag:
This example is in a single Script tag that also loads Modulo JS code (in
this case, using the unpkg CDN). It is ideal if you put all of your Modulo code
in another HTML file anyway (in this case, /static/index.html
),
thus making a simple "4-liner" boilerplate for any page that needs your Modulo
component libraries: