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.
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.
<h2></h2>
<button ref="submitBtn">Submit</button>
<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 })
}
})
</script>
<h2>{{ user.firstName + ' ' + 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.
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 })
}
// 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.
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.
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.