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.

Template Types #

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

Static Templates #

Static templates 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>{{ title }}</h1>
    <p>{{ description }}</p>
  </div>
</template>

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

Dynamic Templates #

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

HTML
Code copied!
<template id="dynamic-component">
  <div>
    <h1>{{ 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 createPlugin function to define a new plugin with configuration options:

JavaScript
Code copied!

import { createPlugin } from 'coralite'

const myPlugin = createPlugin({
  name: 'my-plugin',
  method: (options, context) => {
    // Server-side logic
    return { ...context.values, custom: 'data' }
  },
  templates: ['src/components/custom.html'],
  onPageSet: async (data) => {
    console.log('Page created:', data.path.pathname)
  },
  script: {
    helpers: {
      formatDate: (context) => {
        return (date) => new Date(date).toLocaleDateString()
      }
    }
  }
})
      

createPlugin Parameters #

Parameter Type Required Description
name string Yes Unique identifier for the plugin
method function No Server-side function available in templates
templates string[] No Array of template file paths to include
script object No Script 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
onTemplateSet function No Hook for when a template is created
onTemplateUpdate function No Hook for when a template is updated
onTemplateDelete function No Hook for when a template is deleted

Template-Level Plugins #

Template-level plugins provide methods that can be called inside defineComponent tokens and slots. These run during the build process and have access to the template'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 CoraliteDocument The HTML file being processed
element CoraliteElement The specific element calling the method
path Object File path information

Using Plugin Methods in Templates #

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

HTML
Code copied!
<template id="user-profile">
  <div>
    <h1>{{ user.name }}</h1>
    <p>{{ user.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 template 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) => {
    // Template script receives:
    // values = { userName: "Alice", age: 25 } (processed tokens)
  }
})
      

Key Difference: Plugin methods get raw metadata/attributes, template 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 templates.

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 {
  templates: './templates',
  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 { createPlugin } from 'coralite'
import { readFile } from 'node:fs/promises'
import { join, resolve } from 'node:path'

export default ({ path, minify } = {}) => {
  return createPlugin({
    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)
        }
      }
    }
  })
}
      

Template-Level vs Page-Level #

Feature Template-Level Page-Level
Registration Imported in template script Registered in coralite.config.js
Execution Build time (per template) Build time (per page)
Access Inside defineComponent tokens Global lifecycle hooks
Use Case Data processing, token computation HTML transformation, optimization
Context Template 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 = createPlugin({
  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)
  },
  onTemplateSet: async (template) => {
    // Called when a template is created
    console.log('New template:', template.id)
  },
  onTemplateUpdate: async (template) => {
    // Called when a template is updated
    console.log('Template updated:', template.id)
  },
  onTemplateDelete: async (template) => {
    // Called when a template is deleted
    console.log('Template deleted:', template.id)
  }
})
      

Script Plugins & Helpers #

Script plugins provide client-side helpers that are available in template scripts. These use a factory pattern where Coralite automatically executes the factory with context.

JavaScript
Code copied!

const myScriptPlugin = createPlugin({
  name: 'script-enhancer',
  script: {
    // Called when plugin is registered
    setup: (plugin) => {
      console.log('Plugin registered:', plugin.name)
    },
    
    // Helpers are factory functions that receive context
    helpers: {
      formatDate: (context) => {
        // Factory receives context, returns actual helper
        return (date) => new Date(date).toLocaleDateString()
      },
      
      uppercase: (context) => {
        return (str) => str.toUpperCase()
      }
    }
  }
})
      

How Helpers Work #

  1. Registration: Plugin helpers are registered with ScriptManager
  2. Factory Execution: Coralite calls each factory with current context
  3. Helper Injection: Returned functions are injected into template scripts
  4. Template Usage: Scripts receive (context, helpers) with ready-to-use functions
JavaScript
Code copied!

// In template script:
export default defineComponent({
  script: (context, helpers) => {
    // helpers.formatDate is already the result function
    const formatted = helpers.formatDate('2024-01-01')
    
    // helpers.uppercase is ready to use
    const upper = helpers.uppercase('hello')
  }
})
      

Built-in Plugins #

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

defineComponent #

The defineComponent plugin is required for all dynamic templates. 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 gets serialized and runs client-side
    // It has access to computed tokens and can use refs
    return `
      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...
  ]
}
      

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!