Client-Side JavaScript

Coralite's component system allows you to bundle client-side logic directly with your HTML templates using the defineComponent helper. This guide explains how to create interactive components, manage data flow, and access DOM elements using the "Flat Options" API.

Philosophy: Components as Logic Containers

In Coralite, standard .html pages should remain as declarative consumers. All imperative logic, DOM manipulation, and state management should be reserved for the client block within a component. Avoid using <script> tags in pages to interact with components.

The defineComponent Helper #

The defineComponent function is the core of Coralite's component logic. It separates server-side data fetching and attribute coercion from client-side interactivity using four strictly isolated blocks.

javascript
Code copied!
  import { defineComponent } from 'coralite'
  import db from 'node:database' // Stripped from client bundle
  
  export default defineComponent({
    // Inputs: Coerced from HTML attributes
    attributes: {
        initialCount: { type: Number, default: 0 }
    },
  
    // Server-Side: Asynchronous data fetching (runs at build-time)
    async server(context) {
        const user = await db.users.findFirst();
        return { userName: user.name }
    },
  
    // Derived State: Pure functions (Read-Only)
    getters: {
        greeting: (state) => `Hello, ${state.userName}!`
    },
  
    // Client-Side: Controller (Read/Write)
    client({ state, signal, refs, observe, ...pluginContext }) {
        console.log('Component mounted for user:', state.userName)
  
        // Register an observer to react to counts changes
        observe('initialCount', (newVal, oldVal) => {
            console.log(`Count changed from ${oldVal} to ${newVal}`)
        })
  
        const btn = refs('btn')
        btn.addEventListener('click', () => {
            state.initialCount++; // Reactive mutation
        }, { signal });
    }
  })

The Client Context #

The client block is executed in the browser when the component mounts. It receives a flattened context object via destructuring:

State Observation & Side-Effects (The observe Pattern) #

While Coralite components update their templates automatically when state changes, you often need to perform imperative side-effects (such as updating external DOM libraries, sending analytics, or making network requests) in response to state updates. The observe function provides a clean, declarative pattern for handling these state changes.

Registering Observers #

Inside the client function, you can call observe(key, callback). The callback is invoked synchronously whenever the state property matching the key is modified and its new value differs from its old value.

javascript
Code copied!
  export default defineComponent({
    client({ state, observe }) {
      // Observe pre-defined properties (e.g. from attributes or server)
      observe('score', (newVal, oldVal) => {
        console.log(`Score changed from ${oldVal} to ${newVal}`)
      })
  
      // Observe dynamic properties set at runtime
      observe('status', (newVal) => {
        console.log(`Status is now: ${newVal}`)
      })
    }
  })

Best Practices & Safeguards #

Native Reactivity & Performance #

Coralite uses a Lazy Deep Proxy system. Unlike frameworks that recursively make your entire data structure reactive upfront, Coralite only pays the performance cost for nested objects the exact moment they are accessed.

Additionally, Coralite follows the Smart State, Dumb Template philosophy. Instead of a Virtual DOM, Coralite performs direct token replacement ({{ key }}) in the template, making updates extremely fast and memory-efficient.

Using External Libraries #

Because Coralite leverages standard ESM, you can use dynamic import() statements directly inside your client function to load client-side dependencies. These are automatically detected by the build tool and bundled for the browser.

javascript
Code copied!
  export default defineComponent({
    async client({ state, signal, refs, ...pluginContext }) {
        const { default: confetti } = await import('https://esm.sh/canvas-confetti@1.6.0')
  
        const btn = refs('celebrateBtn')
        btn.addEventListener('click', () => {
            confetti();
        }, { signal })
    }
  })

Component Communication #

Coralite encourages using standard Web APIs for component communication. Use CustomEvent to emit signals from a child to a parent. You can dispatch events from any element retrieved via refs.

javascript
Code copied!
  // Inside a child component's client block
  client({ state, signal, refs, ...pluginContext }) {
      const el = refs('main');
      el.dispatchEvent(new CustomEvent('user-updated', {
          bubbles: true,
          detail: { name: 'Alice' }
      }));
  }

Start Building with Coralite!

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

Copied commandline!