Concepts
Lifecycle
Tiny Engine creates capsules from DOM markers, refreshes refs/directives, syncs option attributes, and destroys removed instances with both automatic and explicit lifecycle controls.
Creation and cleanup
A capsule is created by UI.init(), the observer, or UI.getOrCreate(). In functional capsules, listeners registered through api.on() are tracked and cleaned up automatically. Add destroy() in the returned hooks object for extra teardown work.
import { UI } from "tiny-engine-core";
UI.register("modal", (el, api) => {
const onRootClick = (event) => {
if (event.target.matches("[data-close]")) close();
};
const close = () => {
el.classList.remove("is-open");
};
api.on(el, "click", onRootClick);
return {
close,
// Called when UI.destroy(root?) or observer teardown removes this instance.
destroy() {
document.body.classList.remove("has-modal");
},
};
});Init, observe, scan, and manual creation
UI.init() scans a root for registered markers like ui-modal. UI.scan(root?) re-scans only a specific subtree. UI.observe() watches the document for inserted nodes, removed nodes, and attribute changes. UI.getOrCreate() creates or syncs one instance.
// Hydrate current markup.
UI.init();
UI.config({ hydrate: true });
// Keep the document in sync for added/removed nodes and attribute changes.
UI.observe();
// Manually initialize a specific subtree.
UI.scan(document.querySelector("#sidebar"));
// Mount or sync one host manually.
const instance = UI.getOrCreate(el, "modal", { open: true });
// Explicitly destroy one subtree, or everything with UI.destroy().
UI.destroy(document.querySelector("#sidebar"));Scoped scan and teardown
Use UI.scan(root) when you inject or reveal a specific DOM region and want to initialize capsules only there. Use UI.destroy(root) before removing or replacing that region to run capsule cleanup for just that subtree. In hydration flows, set UI.config({ hydrate: true }) before UI.init() so no-op option sync work is skipped for pre-rendered markup.
import { UI } from "tiny-engine-core";
const sidebar = document.querySelector("#sidebar");
if (sidebar) {
// Initialize/sync capsules only inside this subtree.
UI.scan(sidebar);
// Later, teardown only this subtree's capsule instances.
UI.destroy(sidebar);
}Hydration and attribute sync
When observed option attributes change, the instance receives syncOptions(), prop listeners run, and refs/directives are refreshed. With UI.config({ hydrate: true }), no-op option sync work is skipped during hydration flows.
import { UI } from "tiny-engine-core";
// 1) Enable hydration mode before mounting.
// This skips no-op option sync work for SSR/pre-rendered HTML.
UI.config({ hydrate: true });
// 2) Hydrate existing DOM markers (ui-*/app-*).
UI.init();
// 3) Keep future DOM inserts/removals/attr updates in sync.
UI.observe();<div ui-modal ui-modal-open="false"></div>
<!-- Later, while UI.observe() is active: -->
<script>
modal.setAttribute("ui-modal-open", "true");
</script>