Skip to main content

Coralite Plugin System

Coralite plugins are extensible modules that integrate into the Coralite framework's lifecycle, enabling developers to customize and enhance its behavior through hooks, data manipulation, and dynamic content generation.

Component Types #

Coralite supports two types of components, each with different requirements:

Static Components #

Static components are simple HTML files that support basic token replacement but don't require any script processing:

HTML
Code copied!
<template id="simple-component">
  <div>
    <h1 class="display">{{ title }}</h1>
    <p>{{ description }}</p>
  </div>
</template>

No script tag needed - Coralite will process token replacements automatically.

Dynamic Components #

Dynamic components require a script tag and are used when you need:

HTML
Code copied!
<template id="dynamic-component">
  <div>
    <h1 class="display">{{ greeting }}</h1>
    <button type="button" ref="actionBtn">Click me</button>
  </div>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    tokens: {
      greeting: ({ name }) => `Hello, ${name}!`
    },
    script: (context, helpers) => {
      const btn = helpers.refs('actionBtn')
      btn.addEventListener('click', () => {
        console.log('Button clicked!')
      })
    }
  })
</script>

Requires defineComponent as the default export.

Creating Plugins #

Use the definePlugin function to define a new plugin with configuration options:

JavaScript
Code copied!
  import { definePlugin } from 'coralite'
  
  const myPlugin = definePlugin({
  name: 'my-plugin',
  method: (options, context) => {
  // Server-side logic
  return { ...context.values, custom: 'data' }
  },
  components: ['src/components/custom.html'],
  onPageSet: async (data) => {
  console.log('Page created:', data.path.pathname)
  },
  client: {
  helpers: {
  formatDate: (context) => {
  return (date) => new Date(date).toLocaleDateString()
  }
  }
  }
  })

definePlugin Parameters #

Parameter Type Required Description
name string Yes Unique identifier for the plugin
method function No Server-side function available in components
components string[] No Array of component file paths to include
client object No Client plugin configuration with helpers
onPageSet function No Hook for when a page is created
onPageUpdate function No Hook for when a page is updated
onPageDelete function No Hook for when a page is deleted
onComponentSet function No Hook for when a component is created
onComponentUpdate function No Hook for when a component is updated
onComponentDelete function No Hook for when a component is deleted
onBeforePageRender function No Hook called before rendering a page to a string
onAfterPageRender function No Hook called after rendering a page, before saving
onBeforeBuild function No Hook called at the start of the build process
onAfterBuild function No Hook called at the end of the build process

Component-Level Plugins #

Component-level plugins provide methods that can be called inside defineComponent tokens and slots. These run during the build process and have access to the component's context.

Plugin Method Context #

Plugin methods receive two parameters: options and context. The context contains:

Property Type Description
values Object Page metadata and element attributes
document CoraliteComponent The HTML file being processed
element CoraliteElement The specific element calling the method
path Object File path information
transform function Exposed transform method for serialization control and rendering logic

Using Plugin Methods in Components #

Import plugin methods from coralite/plugins and use them inside defineComponent tokens:

HTML
Code copied!
<template id="user-profile">
  <div>
    <h1 class="display">{{ name }}</h1>
    <p>{{ bio }}</p>
    <span>Joined: {{ joinDate }}</span>
  </div>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  import { formatDate } from 'coralite/plugins'
  
  export default defineComponent({
    tokens: {
      // Plugin method called with options
      joinDate: (values) => formatDate({
        format: 'long'
      }, values.date)
    }
  })
</script>

Context Values Difference #

It's important to understand the difference between values passed to component scripts vs plugin methods:

HTML
Code copied!
<!-- Page: user.html -->

<head>
  <meta name="name" content="Alice"></meta>
  <title>User Profile</title>
</head>

<body>
  <user-profile age="25"></user-profile>
</body>
JavaScript
Code copied!
  // In defineComponent:
  export default defineComponent({
  tokens: {
  // Plugin method receives:
  // values = { $name: "Alice", $title: "User Profile", age: "25" }
  userName: (values) => values.$name
  },
  script: (context, helpers) => {
  // Component script receives:
  // values = { userName: "Alice", age: 25 } (processed tokens)
  }
  })

Key Difference: Plugin methods get raw metadata/attributes, component scripts get processed token values.

Page-Level Plugins #

Page-level plugins use lifecycle hooks to modify the final HTML output. They work across all pages and components.

How Page-Level Plugins Work #

These plugins are registered in your coralite.config.js and automatically process pages during the build:

JavaScript
Code copied!
  // coralite.config.js
  import inlineCSSPlugin from './plugins/inline-css.js'
  
  export default {
  components: './components',
  pages: './pages',
  plugins: [
  inlineCSSPlugin({
  path: './styles',
  minify: true
  })
  ]
  }

Example: Inline CSS Plugin #

Here's how a page-level plugin transforms <link> tags to inline <style> tags:

JavaScript
Code copied!
  import { definePlugin } from 'coralite'
  import { readFile } from 'node:fs/promises'
  import { join, resolve } from 'node:path'
  
  export default ({ path, minify } = {}) => {
  return definePlugin({
  name: 'inline-css',
  async onPageSet(context) {
  // Walk through all elements in the page
  let stack = [context.elements.root]
  
  while (stack.length > 0) {
  const node = stack.pop()
  
  if (node.type === 'tag'
  && node.name === 'link'
  && node.attribs.rel === 'stylesheet'
  && node.attribs['inline-css'] != null
  ) {
  // Read and inline CSS file
  const cssPath = resolve(join(path || '', node.attribs['inline-css']))
  const css = await readFile(cssPath, 'utf8')
  
  // Replace link with style tag
  node.name = 'style'
  node.attribs = {}
  node.children = [{
  type: 'text',
  data: css,
  parent: node
  }]
  }
  
  if (node.children) {
  stack.push(...node.children)
  }
  }
  }
  })
  }

Component-Level vs Page-Level #

Feature Component-Level Page-Level
Registration Imported in component script Registered in coralite.config.js
Execution Build time (per component) Build time (per page)
Access Inside defineComponent tokens Global lifecycle hooks
Use Case Data processing, token computation HTML transformation, optimization
Context Component context + element attributes Full page context + DOM tree

Lifecycle Hooks #

Hooks allow plugins to respond to specific events in the Coralite lifecycle:

JavaScript
Code copied!
  const analyticsPlugin = definePlugin({
  name: 'analytics',
  onPageSet: async (data) => {
  // Called when a new page is created
  console.log('New page:', data.path.pathname)
  await trackPageView(data.path.pathname)
  },
  onPageUpdate: async ({ elements, newValue, oldValue }) => {
  // Called when a page is updated
  console.log('Page updated:', newValue.path.pathname)
  },
  onPageDelete: async (value) => {
  // Called when a page is deleted
  console.log('Page deleted:', value.path.pathname)
  },
  onComponentSet: async (component) => {
  // Called when a component is created
  console.log('New component:', component.id)
  },
  onComponentUpdate: async (component) => {
  // Called when a component is updated
  console.log('Component updated:', component.id)
  },
  onComponentDelete: async (component) => {
  // Called when a component is deleted
  console.log('Component deleted:', component.id)
  }
  })

Client Plugins & Helpers #

Client plugins provide client-side helpers that are available in component scripts. To safely bridge global plugin configuration with local Web Component instances, Coralite requires client.helpers to be authored using a strict Two-Phase Currying System.

The Two-Phase Currying System #

  1. Phase 1 (Global Context): The outermost function receives the globalContext (containing imports and global config).
  2. Phase 2 (Local Instance Context): It must return a second function that receives the local Web Component instance context ({ values, root, signal }). Here, root is the component's HTMLElement (or Document), and signal is an AbortSignal.
  3. Phase 3 (Execution): It finally returns the actual callable utility function used by the user's script in the browser.

Note on signal: The signal is tied natively to the component's mount lifecycle. It triggers .abort() automatically when an imperative component is unmounted from the DOM, making it perfect for cleaning up event listeners (e.g. element.addEventListener('click', fn, { signal })). For declarative components, signal is null.

JavaScript
Code copied!
  import { definePlugin } from 'coralite/plugins'
  
  export const myCustomPlugin = definePlugin({
    name: 'my-custom-plugin',
    client: {
      helpers: {
        // Phase 1: Receives Global Context
        myHelper(globalContext) {
          
          // Phase 2: Receives Local Instance Context
          return ({ values, root, signal }) => {
            
            // Phase 3: The actual function accessible via context.helpers.myHelper
            return function (selector) {
               // The plugin queries elements from the local component root
               const element = root.querySelector(selector);
               
               // Example using the signal to prevent memory leaks
               if (signal) {
                 element.addEventListener('click', () => console.log('clicked'), { signal });
               }
               
               return element;
            }
          }
        }
      }
    }
  })

Usage in Components #

Once injected, the user interacts strictly with the Phase 3 Execution function.

JavaScript
Code copied!
  // In component script:
  export default defineComponent({
    script: (context) => {
      const { helpers } = context;
      
      // helpers.myHelper is already curried with Global and Local Context
      const btn = helpers.myHelper('.btn-class');
    }
  })

Built-in Plugins #

Coralite includes three built-in plugins that provide core functionality:

defineComponent #

The defineComponent plugin is required for all dynamic components. It provides the wrapper for tokens, slots, and script execution:

JavaScript
Code copied!
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
  tokens: {
  // Computed tokens that run at build time
  fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`,
  formattedDate: ({ date }) => new Date(date).toLocaleDateString()
  },
  slots: {
  // Custom slot processing
  content: (slotNodes, values) => {
  // Transform slot content based on values
  return slotNodes
  }
  },
  script: ({ values, refs }) => {
  // This function runs in the client-side
  // It has access to computed tokens and can use refs
  const btn = refs('actionBtn');
  btn.addEventListener('click', () => {
  console.log('Hello!');
  });
  }
  })

When to Use #

When NOT to Use #

refs Helper #

The refs helper provides DOM element access at runtime using the factory pattern:

HTML
Code copied!
<template id="my-component">
  <div>
    <span ref="author">Author Name</span>
    <button type="button" ref="actionBtn">Click me</button>
    <input type="text" ref="userInput"></input>
  </div>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    script: (context, helpers) => {
      // helpers.refs is the resolver function (factory was executed)
      const refs = helpers.refs
  
      // Get DOM elements by their ref name
      const button = refs('actionBtn')
      const input = refs('userInput')
      const author = refs('author')
  
      // Use the elements at runtime
      button.addEventListener('click', () => {
        author.textContent = input.value || 'Anonymous'
      })
    }
  })
</script>

How refs Works #

Plugin Registration #

The refs helper is automatically available when you import the refs plugin:

JavaScript
Code copied!
  // In your coralite.config.js
  import { refsPlugin } from 'coralite/plugins'
  
  export default {
  plugins: [
  refsPlugin
  // Other plugins...
  ]
  }

Metadata Plugin #

The metadata plugin automatically processes page metadata for use in components:

See the Metadata Plugin Reference for full details.

Static Assets Plugin #

The staticAssetPlugin handles the orchestration of copying raw files (like .wasm, images, or pre-compiled scripts) directly into the final build directory.

Configuration #

It acts as a higher-order function that accepts an assets array and returns a configured Coralite plugin. Every object in the array must contain a dest (destination) property.

Lifecycle Hook #

It triggers on the onBeforeBuild hook, ensuring all required binary/static dependencies are physically present in the output directory before Coralite attempts to render pages or start the dev server.

Resolution Strategies #

Complete Example #

Here's a complete example showing all built-in plugins working together:

HTML
Code copied!
<template id="counter">
  <div class="counter">
    <h2>Count: {{ count }}</h2>
    <button type="button" ref="increment">+</button>
    <button type="button" ref="decrement">-</button>
  </div>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    tokens: {
      // Computed token
      count: ({ initial }) => parseInt(initial) || 0
    },
    script: (context, helpers) => {
      // Use refs helper
      const refs = helpers.refs
      const increment = refs('increment')
      const decrement = refs('decrement')
  
      let count = context.values.count
  
      increment.addEventListener('click', () => {
        count++
        document.querySelector('h2').textContent = `Count: ${count}`
      })
  
      decrement.addEventListener('click', () => {
        count--
        document.querySelector('h2').textContent = `Count: ${count}`
      })
    }
  })
</script>

Start Building with Coralite!

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

Copied commandline!