Authoring Pattern

Coralite enforces a single, predictable authoring pattern across every layer of the application. Components and pages are authored with flat named exports, while plugins use the definePlugin default export imported from coralite.

1. The Unified Pattern #

Components and pages share the same flat named-export vocabulary; plugins use definePlugin. All three layers share the same blocks and static validation model. defineComponent and definePage wrappers remain supported for legacy code.

javascript
Code copied!
  import { definePlugin } from 'coralite'
  
  // Component or page: flat named exports
  export function server (context) { /* ... */ }
  export const getters = { /* ... */ }
  export function client (context) { /* ... */ }
  
  // Plugin: default export
  export default definePlugin({ /* plugin blocks */ })

2. Block Vocabulary #

Each block serves a clear, specific role during build-time rendering, runtime reactivity, or lifecycle execution.

Block Component Page Plugin Purpose
attributes ✓ — — Input schema for host attributes
meta — ✓ — Declared page information and default state; keys become page.meta fallbacks and seed page state
paths — ✓ — Enumerate dynamic route params at build time
server ✓ ✓ ✓ (as hooks) Build-time data loading and server lifecycle hooks
getters ✓ ✓ — Derived reactive values and computed template data
style ✓ ✓ — Synchronous CSS custom property injection on <html> or component root
slots ✓ — — Transform and inspect projected Light DOM content
provide / consume ✓ ✓ (provide only) — Tree dependency injection across component hierarchy
client ✓ ✓ ✓ Browser-side interactive runtime logic and event listeners
config — — ✓ Plugin options schema and configuration validation
formAssociated ✓ — — Opt into Custom Elements ElementInternals form participation
onError ✓ ✓ — SSR build-time error boundary fallback state

The component attributes block describes inputs supplied by a host element. Pages have no host element, so their equivalent surface is meta: arbitrary key/value page information using the same schema syntax (type, default, values, validate). meta defaults seed page state, and missing keys fall back into page.meta after literal <title>/<meta> tags are extracted. required is rejected on pages because there is no external input.

Meta keys are used verbatim in state and page.meta; only identifier-safe keys are directly usable in templates, so use a getter for names such as og:title. page and route defaults remain available in page.meta but do not override the page object or active route. server() return values update state only; mutate page.meta directly to change document metadata.

3. Context Keys #

Block functions receive a single destructured context object containing framework utilities, state references, and route metadata.

Key Meaning Available in
state Reactive state object backing template interpolations All blocks
route Active route details (path, pattern, params, dynamic) server, getters, client, onError
refs Named template element accessor function (refs('name')) getters, client, slots
signal AbortSignal aborted when the component or page disconnects All blocks
root Host DOM element reference getters, client, slots
slots Projected slot node accessor function getters, client
store Global app store (read-only snapshot in server, reactive in client and getters) getters, client, server
emit Custom DOM Event dispatcher helper function client only
observe State key change subscriber function client only
updateComplete Promise resolving when DOM updates settle client only
isServer Boolean flag indicating SSR execution vs browser runtime All blocks

4. Template Rules #

Coralite templates use flat interpolations exclusively. To keep templates fast, deterministic, and statically indexable, templates forbid expressions, function calls, inline event handlers (such as onclick="..."), conditionals, and loops. All derivation logic must live inside getters.

javascript
Code copied!
  <h2></h2>
  <button ref="submitBtn">Submit</button>
  
  &lt;script type="module">
    import { defineComponent } from 'coralite'
  
    export default defineComponent({
      getters: {
        fullName: ({ state }) => `${state.firstName} ${state.lastName}`
      },
      client ({ refs, signal, state }) {
        refs('submitBtn').addEventListener('click', () => {
          state.submitted = true
        }, { signal })
      }
    })
  &lt;/script&gt;
html
Code copied!
  <h2>{{ user.firstName + &apos; &apos; + user.lastName }}</h2>
  <button onclick="handleSubmit()">Submit</button>

5. Import Rule #

Top-level imports are reserved for definePlugin, page configuration modules, and ESM modules used by isomorphic blocks (such as getters, style, or slots). Node-only dependencies used strictly in server() or paths(), and browser-only dependencies used in client(), must be imported dynamically using await import(...) inside the respective block.

javascript
Code copied!
  export async function server () {
    // Correct: Node module loaded inside server()
    const fs = await import('node:fs/promises')
    const data = await fs.readFile('data.json', 'utf8')
    return { content: JSON.parse(data) }
  }
  
  export async function client () {
    // Correct: Browser module loaded inside client()
    const { animate } = await import('https://esm.sh/motion')
    animate('.card', { opacity: 1 })
  }
javascript
Code copied!
  // Bad: Importing Node/Browser modules at top-level pollutes bundle scope
  import fs from 'node:fs/promises'
  import { animate } from 'motion'

6. Serialization Boundary #

Coralite draws a strict boundary between build-time server execution and browser client runtime execution. The server() function runs in Node.js during build/SSG and its return value is serialized to JSON. The client() function runs in the browser and cannot close over outer module scope variables populated during server() execution.

javascript
Code copied!
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    // 1. Runs at build-time in Node.js
    server () {
      return {
        serverTime: new Date().toISOString()
      }
    },
    // 2. Runs in the browser runtime; receives serialized state
    client ({ state }) {
      console.log('Server rendered at:', state.serverTime)
    }
  })

7. No Defensive Guards #

Coralite guarantees that all context parameters destructured in block function signatures exist. Authors must not write defensive guards (such as if (!lists) return or if (state == null) return) against destructured context keys or guaranteed framework properties.

javascript
Code copied!
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    // Correct: Destructure directly without defensive checks
    client ({ state, refs, signal }) {
      const btn = refs('button')
      btn.addEventListener('click', () => {
        state.count++
      }, { signal })
    }
  })

Next: Explore Getting Started to build your first Coralite component and page.

Start Building with Coralite!

Use the scaffolding script to get jump started into your next project with Coralite

Copied commandline!