Theme Framework How the LeaseLeads theme frontend works

A page's life

From a WordPress request to a wired-up instance, end to end.

On this page 5

One request, four movements: PHP renders markup, the browser boots in two phases, presence-gated assets load, and each element becomes a running instance. This page is the map; each movement links to the Core Concepts page that covers it in depth.

flowchart TD
    A[WordPress request] --> B[header.php renders the shell<br/>Layouts + open body]
    B --> C[Template / the_content renders Modules<br/>View::module → View::component]
    C --> D[Markup carries data-* options,<br/>data islands, CSS custom properties]
    D --> E{Browser boot}
    E -->|critical path| F[critical.js → Crucial<br/>fetch above-the-fold, mostly no instances]
    E -->|first interaction / 10s fallback| G[public.js → Deferred<br/>the real instantiation]
    F --> H[Processor: selector matches element?]
    G --> H
    H -->|yes| I[import the chunk<br/>JS + colocated SCSS]
    H -->|no| J[nothing ships]
    I --> K[element becomes an Instantiable]
    K --> L[boot → afterBoot: declare & load sub-components]
    L --> M[init: wire behaviour, toggle state classes]

1. PHP renders the markup

A request hits the page shell. header.php opens the document and emits the Layouts that are site chrome — the header bar and takeover — gated only by theme settings:

php
<?php get_template_part('resources/public/views/layouts/header', 'bar', []); ?>
<?php get_template_part('resources/public/views/layouts/header', 'takeover', []); ?>

Then the Template produces the module region. On the default template, Modules are appended to the_content, so plugins that filter content keep working; a bespoke template like OneFold runs its own loop instead. Either way, each Module is rendered through the View helper, which resolves a name to a colocated PHP partial:

php
View::module('intro', $data);      // → resources/public/views/modules/intro.php
View::component('rich-media', $d);  // → resources/public/views/components/rich-media.php

The markup those partials emit is the contract with the frontend. It carries three kinds of backend data across the seam:

  • data-* options — scalar config as a JSON attribute (data-splide-gallery='…'), read on construction into the instance's options.
  • Data islands — a <script type="application/json"> child for collections too big for an attribute (gallery items, map markers).
  • CSS custom properties — inline --vars and utility classes emitted by Field classes, so an editor's colour and spacing choices reach the SCSS.

See The Backend Bridge for all three.

2. The browser boots in two phases

The body ships hidden (display: none) and two scripts drive the boot. This is two-phase loading, owned by the two Globals:

  • critical.js → Crucial runs on the critical path. It mostly fetches above-the-fold module chunks without instantiating them (style-only entries with name: null), applies the eager global CSS, then reveals the body. Withholding instantiation here avoids reflow before the page is visible.
  • public.js → Deferred loads on first user interaction (WP-Rocket-delayed in production; a 10-second timer in critical.js is only a no-interaction fallback) and does the real instantiation of every Module on the page.
js
// critical.js — hand the critical path to Crucial, then arm the fallback
;(async (processor) => processor
    .convert({ name: 'Crucial', selector: 'body',
               loader: () => import('./assets/global/Crucial/Crucial.js') })
    .load()
)(new Processor())

window.LeaseLeads_Theme.pageload_timer = setTimeout(() => import('./public.js'), 10000)

See Two-phase loading for the full performance story.

3. Presence-gated assets load

Both Globals do the same thing: build a list of descriptors — each a name, a selector, and a dynamic import() — and hand it to the Processor. The Processor loads a chunk only if its selector matches an element on the page:

js
processor
  .convert({ name: 'Reviews',  selector: '.reviews',
             loader: () => import('../../modules/Reviews/Reviews.js') })
  .convert({ name: 'Hero',     selector: '.hero',
             loader: () => import('../../modules/Hero/Hero.js') })
await processor.load()

Importing the chunk pulls in the JS and, as a side-effect, its colocated SCSS — so styles for a Module ride along with its behaviour, and neither ships for a Module that isn't present. See The AssetLoader pipeline.

4. Each element becomes an instance

Every behavioural element extends Instantiable. When a matched class loads, its static selector finds the elements, and each becomes an instance that parses its data-* JSON into options and then runs an ordered lifecycle:

  • boot / afterBootdeclare and defer: read the element and queue the assets for any sub-Components it contains, without assuming anything else has loaded.
  • initwire behaviour: idempotent and guarded, it calls autoInitializeComponents() to init discovered sub-Components in order, then binds real interactions and toggles state classes.

A Host (a Module, Layout, Template, or Global) mixes AutoInitializes and is the sole controller of the behavioural Components in its subtree — it discovers them with HasX traits and owns their load and init ordering. Passive sub-Components never self-init, so the Host keeps control.

js
class Intro extends Instantiable {
  static selector = '.intro'

  async init () {
    await this.carousel.init()
    await this.autoInitializeComponents()
    await super.init()
  }
}

See Instantiable & the instance model, The lifecycle, and Sub-components & HasX.