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.
Configuration Options #
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: Top-level ubiquitous context (available across server, getters, client, slots, style)
// Mutually exclusive with server.context and client.context (CORALITE-P211)
context: ({ app, config }) => (instanceContext) => {
return {
format: (val) => String(val).trim()
}
},
// Optional: Server-side logic
server: {
// 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 */ }
}
})
Ubiquitous Context (`context`) #
The top-level context property defines synchronous methods and data injected ubiquitously across all component and page execution blocks: server(), getters, client(), slots, and style. It uses the curried Two-Phase Resolver pattern: ({ app, config }) => (instanceContext) => Record<string, any>.
Because top-level context is available directly in getters, zero-JS static components can resolve localized strings, formatters, and pure derived values during SSR and client hydration without defining a server() block.
Note: Top-level context is mutually exclusive with server.context and client.context (CORALITE-P211).
Context Binding (`server.context`) #
The server.context property defines methods and data injected into component server() blocks and page server handlers (e.g. context['logger-plugin'] or server ({ 'logger-plugin': logger })). It follows the Two-Phase Resolver pattern.
- Phase 1 (Global): Receives
pluginContext. Used for global initialization and sharing data with other plugins via mutation. - Phase 2 (Instance): Receives
instanceContext(containspage,route,app). Returns the object exposed to the component or page. UseinstanceContextto access other plugins.
Testing: To mock server.context or client.context, see Plugin Mocking. The real Phase 1 and Phase 2 functions still run; use a stub plugin for full isolation. For focused unit tests, see Mocking Plugin Context in Unit Tests.
export default definePlugin({
name: 'logger-plugin',
server: {
config: { prefix: 'LOG' },
context: (pluginContext) => {
// Phase 1: Global setup
pluginContext.sharedLogBuffer = [];
return (instanceContext) => {
// Phase 2: Per-instance setup
return {
log: (message) => {
console.log(`[${pluginContext.config.prefix}] ${message}`);
}
}
}
}
}
})
Server-Side Hooks #
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.
Client-Side Scripting #
The client property is powerful: it allows you to inject code that runs in the browser. This code
is bundled with your application.
client.context #
Define utilities that will be available via the plugin's namespace on the context object in component client() blocks and page client scripts.
definePlugin({
name: 'utils',
client: {
context: (pluginContext) => (instanceContext) => {
return {
formatDate: (date) => {
return new Date(date).toLocaleDateString()
}
}
}
}
})
Note: Context utilities use the Two-Phase Resolver pattern.
Client Configuration #
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
},
// ...
}
})
Context Injection #
The client.config is automatically passed as a property of the single argument to your context factories. Additionally, you can mutate the context in Phase 1 to expose services to downstream plugins.
// Inside client.context
context: (pluginContext) => {
const { trackingId } = pluginContext.config;
// ✨ EXPOSE TO DOWNSTREAM PLUGINS
pluginContext.analyticsReady = true;
return (instanceContext) => {
return {
trackEvent: async (eventName) => {
const { default: analyticsLib } = await import('analytics-lib')
analyticsLib.send(trackingId, eventName)
}
}
}
}
Getter Isomorphism Invariant #
Isomorphic getters (export const getters = { ... } in components or getters in pages) strictly receive { state, route, root, refs, slots, signal, isServer, store }. Getters never receive plugin context.
Getters evaluate synchronously during both server-side generation (Node.js) and reactive browser state mutations (Client). Injecting build-time or server-only plugins into getters would destroy isomorphism when state changes in the browser.
To use plugin data within getters: call plugin methods inside server(), assign the result to state, and have pure getters read from state.
Complete Example: Confetti Plugin #
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: (pluginContext) => (instanceContext) => {
return {
explode: 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: pluginContext.config.particleCount,
spread: pluginContext.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({
client: (context) => {
const btn = context.refs('btn')
const { explode } = context['confetti-plugin']
btn.addEventListener('click', () => {
explode()
})
}
})
</script>