How a page reaches visitors

v0.0.1

Mishka CMS keeps a page as a row in the database and turns it into running code. This page follows a page from the moment you publish it to the moment a visitor reads it: the background jobs, the compiled modules, the stylesheet and script bundles, and what a tab that is already open does when something changes.

What happens when you publish a page

Publishing writes the page record, and so does saving the page from the Pages list. A draft that the editor autosaves queues nothing. Once the write has committed, the CMS queues one background job for that record on the modules queue. Saving again while the job waits replaces the data in the waiting job, so only the newest version is built.

The job reads the site from memory, never from the database, and then does one of three things:

  • If the page was deleted or made inactive, it unloads the page's module and removes the page's address from the routing table. The address then answers with the site's not-found page.
  • If the page is active, it records the address in the routing table and writes a copy of the page's markup for the stylesheet build to scan. It builds the page's module if the module is already loaded or the page has Precompile on. Otherwise the module waits for the first visit.
  • If the page belongs to an inactive site, it builds nothing. Switching the site on again rebuilds every part of it from the database.

When the job ends, the CMS queues the stylesheet build and the sitemap build of the site.

Compiled modules

The CMS compiles each page, layout, component and error page into its own module, so a visitor's request runs code and does no interpretation. A module is built at one of three moments:

MomentWhen it applies
At bootThe record is active and has Precompile on.
When the record is savedThe record is created with Precompile on, or it is updated and its module is already loaded or Precompile is on.
On the first visitEvery other case. The first request that needs the module builds it.

Requests that need the same module at the same time share one build, and a gate limits how many builds run at once. A build that fails is remembered, so the next request does not try the same broken module again. Saving the record builds it again.

Stylesheet and script bundles

Each site has one stylesheet and one script, built on the assets queue.

  • The stylesheet is Tailwind CSS. It starts from the styles of the CMS itself and adds the classes of everything the site can reach: its pages and error pages, the layouts those pages use, and the components they name, followed through components that name other components. Where the CMS cannot tell what a template reaches, it scans everything rather than leave a component unstyled.
  • The script bundles the app code with the global JS hooks and the site's own JS hooks.

The CMS keeps each bundle in memory in three forms: plain, Brotli and gzip. The address of a bundle carries its content hash, such as /assets/css/<site>/<hash>.css. A bundle is served only at its current hash, so browsers may keep it for a year.

A build job waits 3 seconds before it runs, and another change to the same site moves it later. A burst of saves makes one build. A change to a page, layout, error page, component or stylesheet rebuilds the stylesheet, and a change to a JS hook rebuilds the script.

Components a page reaches

A component that is not precompiled would make the first visitor wait for its build, and a page meets its components one after another. To avoid that, a page or layout builds the components it reaches, several at once, before it draws them. It uses a share of the compile slots, so other requests keep the rest. A page whose components are all built does no extra work. A component that the CMS cannot trace is built by the first render that names it.

What an open page does

A page that is already open is told when a bundle changes:

  • When the stylesheet gets a new hash, the page loads the new stylesheet beside the old one and removes the old one when the new one has loaded. The page does not reload.
  • When the script gets a new hash, the page shows a bar that reads This page was updated. with a Refresh button.

When someone changes the permissions of a page, the readers on it are checked again. See How permissions work.

What a request does

  1. The CMS finds the site from the host of the request.
  2. It looks the address up in the routing table of the site. An address with no entry answers with the site's not-found page.
  3. It loads the page's module, or builds it if nothing has built it yet.
  4. It checks the permissions of the page.
  5. It reads the rows the page shows, once for this address. The plugin on runtime:page:source reads them as the visitor. See Page source.
  6. It resolves the page's frame: the languages the page is written in and the bar under the header. The plugin on runtime:page:frame answers for the pages it owns. See Page frame.
  7. It resolves the page's head from the SEO fields of the page and the plugin on runtime:page:seo, which is handed the rows. The hreflang links of the head come from the languages of the frame.
  8. It checks the permissions of the record the page shows, if any. The plugin's answer for the head names them.
  9. The page renders inside its layout, and it links the current stylesheet and script of the site.

The rows, the frame and the head are resolved in handle_params/3, once for each address a visitor opens, a live navigation included. They run after the page's language, the path's captures and the query are known.

What happens at start

When the CMS starts, it loads the active sites into memory and compiles the components, error pages and layouts. It prepares the JS hooks, registers the address of every active page and builds the module of each page that has Precompile on. It then queues the stylesheet and script builds of every site, and builds the sitemaps. A page whose build fails at boot is skipped and logged, and saving it again builds it.

Example: publish a pricing page

  1. You click Publish on the page /:lang/pricing, which has Precompile off.
  2. A job on the modules queue records the address of the active page. The module is not loaded, so nothing is built.
  3. The stylesheet job runs about 3 seconds later. If the page brought classes the stylesheet did not have, the hash changes and the open tabs of the site swap their stylesheet.
  4. The first visitor to /en/pricing builds the module, and the page builds the components it reaches before it draws them. Every later visitor runs them.

With Precompile on, the job builds the module in step 2, and the first visitor waits for no page build. A component of the page that is not precompiled is still built when the page first draws.