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:
<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:
- Computed tokens or slots
- Client-side JavaScript execution
- Access to plugin methods
- DOM element references
<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:
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:
<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:
<!-- Page: user.html -->
<head>
<meta name="name" content="Alice"></meta>
<title>User Profile</title>
</head>
<body>
<user-profile age="25"></user-profile>
</body>
// 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:
// 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:
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:
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.
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 #
- Registration: Plugin helpers are registered with ScriptManager
- Factory Execution: Coralite calls each factory with current context
- Helper Injection: Returned functions are injected into template scripts
- Template Usage: Scripts receive
(context, helpers)with ready-to-use functions
// 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:
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 your template needs computed tokens
- When you need custom slot processing
- When you need client-side JavaScript
- When you need to use plugin methods
When NOT to Use #
- Simple static templates with only token replacement
- Templates that don't need script processing
refs Helper #
The refs helper provides DOM element access at runtime using the factory pattern:
<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 #
- Factory Definition:
refs({ refs })receives context and returns resolver - Factory Execution: ScriptManager calls
refs(context)automatically - Helper Injection: Result is injected as
helpers.refs - Template Usage: Scripts use
helpers.refsdirectly - DOM Access: Queries elements with
data-coralite-refattributes
Plugin Registration #
The refs helper is automatically available when you import the refs plugin:
// 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:
<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>