Theme Framework How the LeaseLeads theme frontend works

The Globals registry

The runtime component registry and how scripts outside the theme hook into it.

On this page 5

Globals is a static registry mapping every component's moniker to its class, exposed on the window at window.LeaseLeads_Theme.components. Internally it lets the loader answer "is this component already registered?"; externally it is the theme's supported API for reaching a live instance from a script that isn't part of the theme build. You interact with it through one call — look a component up by moniker:

js
const Intro = window.LeaseLeads_Theme.components.get('Intro')  // the registered class

Registration itself is automatic: the loader writes the moniker onto each class and adds it here as its chunk resolves. You never call the registry's setter — you only ever read from it.

Note

Why it works this way. Client sites regularly need per-instance tweaks injected from outside the theme — in practice a <script> dropped onto one page via the HFCM (Header Footer Code Manager) plugin. Every component registers here precisely so that script can do components.get('Intro').get(el).open(). The registry is a public API, not internal bookkeeping. (ADR 0002.)

Name vs moniker

Two identifiers travel with every component, and the difference matters the moment you register or reach for one.

  • name — the resolution key in the loader descriptor (convert({ name: 'Intro' })). AssetLoader resolves the class via exports[name], so the export { Final as Intro } alias must equal it — a mismatch throws at load.
  • moniker — the stable runtime identity Globals.set writes onto the class (Class.moniker). It keys this registry and prefixes every lifecycle and emit event.

Vite munges class names when it minifies the build, so SomeClass.name is unreliable at runtime. The moniker is human-authored and never munged — which is why it, not the class name, is the external contract.

Important

A moniker is a breaking change if you rename it. External HFCM scripts and every <moniker>.initialized event listener depend on the exact string. Treat it like a published API name.

Hooking in from outside the theme

This is the reason the registry exists. An external script — typically injected via HFCM, and it must go in the footer — wants to reach a component instance and drive it. There are two routes.

The catch: instances don't exist at script-eval time

Components boot asynchronously. Deferred doesn't even start loading modules until the first user interaction (see Two-phase loading), and each component then loads its chunk, boots, and inits over several turns of the event loop. So at the moment your injected <script> runs, the instance you want almost certainly does not exist yet. Reading the registry synchronously will hand you nothing.

The reliable route is therefore to listen for the lifecycle event and read the instance off event.detail:

html
<!-- HFCM: footer -->
<script>
  // moniker-scoped: fires only for Intro modules
  document.addEventListener('intro.initialized', (event) => {
    const intro = event.detail.instance
    intro.doSomething()
  })

  // or generic: fires for every module as it initializes
  document.addEventListener('module.initialized', (event) => {
    const instance = event.detail.instance
  })
</script>

Because the events bubble to document, one listener catches every matching instance — including ones added after your script ran.

The direct pull (only once you know it's booted)

If you already know a component has initialized — say, inside an initialized handler, or behind your own user-interaction trigger — you can pull the instance straight from the registry by moniker and element:

js
const el = document.querySelector('.intro')
const intro = LeaseLeads_Theme.components.get('Intro').get(el)
intro.open() // or whatever the component exposes

.get('Intro') returns the class; .get(el) returns that element's live instance from its per-element registry. If instantiation hasn't happened yet, .get(el) is undefined — which is exactly why the event route is the one to reach for by default.