definePlugin API
The definePlugin function is the entry point for extending Coralite's functionality. It allows you
to
inject server-side logic, register custom templates, and most importantly, bundle and configure client-side
scripts that can be used by your components.
The definePlugin function accepts a configuration object with the following options:
import { definePlugin } from 'coralite'
const myPlugin = definePlugin({
name: 'my-plugin', // Required: Unique name for the plugin
// Optional: Server-side logic
server: {
// Two-Phase Curried functions for component data() blocks
exports: {
log: (context) => (message) => {
console.log(`[${context.id}] ${message}`, context.state)
}
},
// Register custom components
components: ['./path/to/component-name.html'],
// Server-side lifecycle hooks
onPageSet: async (data) => { /* ... */ },
onBeforeComponentRender: ({ state, session }) => { /* ... */ }
},
// Optional: Client-side logic
client: {
config: { /* static configuration */ },
context: {
// Two-Phase Curried utilities for script context
myHelper: (globalContext) => (localContext) => () => {
return 'hello'
}
}
}
})
The server.exports property is an object of functions that processes content using plugin logic during the server-side build process. These functions are available to component data() blocks.
These functions use a Two-Phase Currying pattern to receive the globalContext and config explicitly.
export default definePlugin({
name: 'logger-plugin',
server: {
config: { prefix: 'LOG' },
exports: {
log: (globalContext, config) => (instanceContext) => (message) => {
// Phase 1: Receive globalContext and config
// Phase 2: Receive instanceContext (app, session, state, page, etc.)
// Phase 3: Receive arguments from component data() block
console.log(`[${config.prefix}] [${instanceContext.id}] ${message}`, instanceContext.state);
// Returning a state patch to be merged into component state
return { logged: true };
}
}
}
})
Plugins can hook into Coralite's build lifecycle within the server block. All hooks support asynchronous operations. All hooks receive the shared globalContext (as properties) and the plugin's config in their context object.
onBeforeBuild({ path, options, app, config, ...globalContext }): Called before the build process starts.onPageSet({ elements, state, page, data, app, config, ...globalContext }): Called when a page is initially created.onPageUpdate({ elements, page, newValue, oldValue, app, config, ...globalContext }): Called when a page is updated.onPageDelete({ data, app, config, ...globalContext }): Called when a page file is deleted.onComponentSet({ component, app, config, ...globalContext }): Called when a component is registered.onComponentUpdate({ component, app, config, ...globalContext }): Called when a component is updated.onComponentDelete({ component, app, config, ...globalContext }): Called when a component is deleted.onBeforePageRender({ component, state, page, session, app, config, ...globalContext }): Called before a specific page begins rendering.onAfterPageRender({ result, session, app, config, ...globalContext }): Called after a page has been rendered. Plugins can return additionalCoraliteResultobjects.onBeforeComponentRender({ state, componentId, instanceId, refs, session, app, config, ...globalContext }): Called before a specific component instance is rendered.onAfterComponentRender({ result, state, componentId, instanceId, session, app, config, ...globalContext }): Called after a component instance is rendered.onAfterBuild({ results, error, duration, app, config, ...globalContext }): Called when the entire build process finishes.
The client property is powerful: it allows you to inject code that runs in the browser. This code
is bundled with your application.
Define utilities that will be flattened directly onto the context object in defineComponent scripts in the browser.
definePlugin({
name: 'utils',
client: {
context: {
formatDate: (globalContext, config) => (instanceContext) => (date) => {
return new Date(date).toLocaleDateString()
}
}
}
})
Note: Context utilities must use the Two-Phase Currying System.
The client.config object allows you to pass static configuration data from the server (where the
plugin is defined) to the client. This is useful for API keys, theme settings, or feature flags.
definePlugin({
name: 'analytics',
client: {
config: {
trackingId: 'UA-123456-7',
debug: true
},
// ...
}
})
The client.config is automatically passed as the second argument to your context factories. Additionally, you can mutate globalContext in Phase 1 to expose services to downstream plugins.
// Inside client.context
context: {
trackEvent: (globalContext, config) => {
const { trackingId } = config;
// ✨ EXPOSE TO DOWNSTREAM PLUGINS
globalContext.analyticsReady = true;
return (localContext) => async (eventName) => {
const { default: analyticsLib } = await import('analytics-lib')
analyticsLib.send(trackingId, eventName)
}
}
}
Let's build a plugin that triggers a confetti explosion using a remote library and allows configuration of particle count.
import { definePlugin } from 'coralite'
export default definePlugin({
name: 'confetti-plugin',
client: {
// Define configuration
config: {
particleCount: 100,
spread: 70
},
// Create the context utility
context: {
explode: (globalContext, config) => (localContext) => async () => {
// Dynamically import the library (auto-bundled by AST)
const { default: confetti } = await import('https://esm.sh/canvas-confetti@1.6.0')
// Use the library with the config
confetti({
particleCount: config.particleCount,
spread: config.spread,
origin: { y: 0.6 }
})
}
}
}
})
Usage in a component:
<template id="celebration-button">
<button ref="btn">Celebrate!</button>
</template>
<script type="module">
import { defineComponent } from 'coralite'
export default defineComponent({
script: ({ state, signal, refs }) => {
const btn = refs('btn')
btn.addEventListener('click', () => {
explode()
})
}
})
</script>