Compiler design / 18. HTML assembly and output

The HTML builder assembles records into a document and owned output set, ending at the greeting fragment.

Building and writing the final page

Every earlier stage produced records. The HTML builder's job is to turn the right records into one working document and a safe output set without rediscovering source structure or letting each backend spray files into the tree. This is harvest time, not another season of growth from seed.

This is the last primary stop on the chronological route. The fragment at the end is still:

<p>Hello, Priya!</p>

Assembly inputs

The builder consumes explicit inputs such as:

If a fact is missing, an upstream owner failed. The builder should not open greeting.moth to peek at source again.

Diagram placeholder: Compiler and backend records converge on a document and output plan

Caption: accepted ownership map simplified. Question: what does the builder consume?

Document shell and fragments

Assembly plans route and document metadata, then builds a shell.

Compile-time fragments insert at recorded indexes first. They are already strings. The greeting's static paragraph wants this path when everything folded.

Runtime path: invoke start once for the activated root and hydrate runtime fragments in source order. Dynamic pieces fill reserved slots without scrambling author order.

The result is a document anatomy: HTML shell, paragraph text, script tags, runtime references and whatever the entry needs to boot.

Diagram placeholder: Folded pieces enter first while runtime fragments hydrate in source order

Caption: conceptual sequence. Question: when does the paragraph appear?

Diagram placeholder: HTML shell contains paragraph, scripts and runtime references

Caption: article-specific shell details remain illustrative. Question: what makes the page runnable?

Reactive and external work

Reactive mount work is a target-specific extension of the page model, not a second compiler. Where the entry needs it, the builder wires backend-owned update strategy to the compiled source and sink metadata.

External JavaScript assets arrive demand-driven. Import maps and glue appear because link facts and bindings required them, not because a global script tag fashion show looked nice.

Resources and output records

The compiler hands over semantic facts and no physical decisions. A resource use is a stable origin plus the location that wanted it, and the strings carrying it still hold an unresolved anchor rather than a URL. Placement, conflicts, copying and link text are all builder work, and this is the stage that does it.

Liveness is exact. Entry planning unions the resource uses reachable from the selected start, from reachable source and generated functions, from runtime and compile-time fragments, from entry settings and from provider requirements. Every physical-target-bearing file path was already a build input and a watch interest long before this — that happened before AST and never depended on reachability. A .moth file value and a site-root @/ have no physical target. Emission is the narrower question. A resource that nothing reachable uses is never read, never hashed and never written, though the watcher still knows the physical targets that were authored.

Then placement. Project-local resources keep their path relative to entry_root. Package resources sit under one injective package prefix. Provider-managed output uses its declared stable path. Conflicts settle here: one origin at one path deduplicates, different origins at the same path fail with both authored locations, and resource output collides with HTML, JavaScript, Wasm and manifest output on equal terms. All of that completes before a single resource byte is read.

Only then, link text. Each use gets a URL context, meaning the artefact whose URL rules actually observe the string: the page document for inline CSS, the stylesheet for standalone CSS. The builder renders a lexical relative path from that artefact's parent, with / separators, percent-encoded segments, ./ on same-or-descendant paths and ../ retained. A builder that cannot assign a context to a reachable use rejects it rather than guessing one.

The site root is the other structural piece and follows a different rule. It names no file, has no origin and joins no union, and it renders from the project-origin policy of the selected artefact. A builder with no such policy rejects a reachable site-root use during target contract validation; it never quietly settles for /, and that origin is never prepended to an ordinary resource URL.

Nothing here scans rendered HTML, CSS or Markdown to rediscover files. The records were complete when they arrived.

Manifests remember what this builder produced. Incremental builds compare records. Humans and tools can audit the set.

Writing and ownership

The build system owns output validation, writing, manifests and stale cleanup.

Practical rules that keep disks sane:

Backends emit data into central records. They do not each open a private output directory and hope the next clean step guesses right.

Diagram placeholder: Current records define writes and safe removal of stale owned files

Caption: exact output principle simplified. Question: who may remove a file?

Diagram placeholder: HTML, JavaScript, Wasm, runtime assets and manifest sit under one owned output root

Caption: conceptual output tree. Question: which files belong together?

Final checkpoint

A conceptual output tree for the walkthrough might contain:

output/
├── index.html          -- or route-shaped HTML
├── *.js                -- page scripts and helpers
├── *.wasm              -- when the partition needs it
├── runtime/            -- shared helpers as designed
└── manifest            -- owned output inventory

Open the HTML. Find the fragment:

<p>Hello, Priya!</p>

That string travelled a long way: characters, tokens, shells, bindings, types, folded constants, template handoff, diagnostics that stayed quiet, HIR, memory rules, artefacts, entry plans, target assignment, lowering and assembly. Each owner removed one ambiguity and refused to rebuild the previous owner's work from bare source.

There is no later primary tutorial page in this series. Deep dives live in language, memory and progress authorities when you need the fine print.

What can fail here

A missing or unreadable file is not an assembly failure. File paths resolve once, before AST, so that diagnostic arrived many stages ago.

Backend generation failures belong to the backend article. Assembly failures belong here.

Why Moth does it this way

Alternative: let each backend write files, or let builders rescan source.

Choice: central output records, validation, manifests and stale cleanup.

Benefit: output ownership stays auditable and repeatable.

Cost: builders must declare every produced file and ownership transition.

Roadmap and current status

Exit state

Generated HTML, runtime assets and an owned output manifest sit on disk. The final fragment is <p>Hello, Priya!</p>. The chronological educational route ends here.

Compiler design / Previous