Plugins
This guide covers creating and using plugins in EreoJS.
Using Plugins
Installation
ts
// ereo.config.ts
import { defineConfig } from '@ereo/core'
import tailwind from '@ereo/plugin-tailwind'
import { createAuthPlugin, github, google } from '@ereo/auth'
export default defineConfig({
plugins: [
tailwind(),
createAuthPlugin({
session: { secret: process.env.AUTH_SECRET! },
providers: [
github({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! }),
google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! }),
],
})
]
})Plugin Order
Plugins execute in order. Place foundational plugins first:
ts
plugins: [
// Infrastructure plugins first
loggingPlugin(),
securityPlugin(),
// Feature plugins (e.g., auth, analytics)
createAuthPlugin({ /* ... */ }),
analyticsPlugin(),
// Styling and build plugins last
tailwind()
]Creating Plugins
Basic Plugin
ts
// plugins/my-plugin.ts
import { definePlugin } from '@ereo/core'
export function myPlugin(options = {}) {
return definePlugin({
name: 'my-plugin',
setup(context) {
console.log('Plugin initialized with:', options)
console.log('App root:', context.root)
console.log('Mode:', context.mode)
}
})
}Plugin with Middleware
ts
export function requestIdPlugin() {
return definePlugin({
name: 'request-id',
runtimeMiddleware: [
async (request, context, next) => {
const requestId = crypto.randomUUID()
context.set('requestId', requestId)
const response = await next()
response.headers.set('X-Request-Id', requestId)
return response
}
]
})
}Transform Plugin
Transform code during build:
ts
export function envReplacePlugin() {
return definePlugin({
name: 'env-replace',
transform(code, id) {
// Only transform JS/TS files
if (!id.match(/\.[jt]sx?$/)) return code
// Replace process.env.NODE_ENV
return code.replace(
/process\.env\.NODE_ENV/g,
JSON.stringify(process.env.NODE_ENV)
)
}
})
}Virtual Module Plugin
Create virtual modules:
ts
export function virtualConfigPlugin(config: Record<string, any>) {
const virtualModuleId = 'virtual:app-config'
const resolvedId = '\0' + virtualModuleId
return definePlugin({
name: 'virtual-config',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedId
}
return null
},
load(id) {
if (id === resolvedId) {
return `export default ${JSON.stringify(config)}`
}
return null
}
})
}
// Usage
import config from 'virtual:app-config'
console.log(config.siteName)Build Hooks Plugin
ts
export function buildStatsPlugin() {
let startTime: number
return definePlugin({
name: 'build-stats',
buildStart() {
startTime = Date.now()
console.log('Build started...')
},
buildEnd() {
const duration = Date.now() - startTime
console.log(`Build completed in ${duration}ms`)
}
})
}Dev Server Plugin
ts
export function devToolsPlugin() {
return definePlugin({
name: 'dev-tools',
configureServer(server) {
// Add custom middleware to dev server
server.middlewares.use('/__dev/routes', (req, res) => {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(server.routes))
})
// Add WebSocket handler
server.ws.on('connection', (socket) => {
socket.on('message', (msg) => {
console.log('Dev tools message:', msg)
})
})
}
})
}Plugin Context
The PluginContext provides access to framework internals:
ts
interface PluginContext {
// Framework configuration
config: FrameworkConfig
// Project root directory (process.cwd())
root: string
// Environment mode
mode: 'development' | 'production'
}Using Context
ts
export function configLoggerPlugin() {
return definePlugin({
name: 'config-logger',
setup(context) {
console.log('Running in', context.mode, 'mode')
console.log('Project root:', context.root)
console.log('Server port:', context.config.server?.port)
console.log('Build target:', context.config.build?.target)
}
})
}Composing Plugins
Combine multiple plugins:
ts
import { composePlugins } from '@ereo/core'
const securityPlugin = composePlugins('security', [
csrfPlugin(),
corsPlugin({ origin: '*' }),
securityHeadersPlugin()
])
export default defineConfig({
plugins: [securityPlugin]
})Real-World Examples
Analytics Plugin
ts
export function analyticsPlugin(options: { trackingId: string }) {
return definePlugin({
name: 'analytics',
runtimeMiddleware: [
async (request, context, next) => {
const start = Date.now()
const response = await next()
const duration = Date.now() - start
// Track page view
trackEvent('pageview', {
url: request.url,
duration,
status: response.status
})
return response
}
],
// Inject tracking script
transform(code, id) {
if (id.endsWith('_layout.tsx')) {
return code.replace(
'</head>',
`<script src="https://analytics.com/track.js" data-id="${options.trackingId}"></script></head>`
)
}
return code
}
})
}Database Plugin
ts
export function databasePlugin(options: { url: string }) {
let db: Database
return definePlugin({
name: 'database',
async setup(context) {
db = await createConnection(options.url)
context.logger.info('Database connected')
},
runtimeMiddleware: [
async (request, context, next) => {
context.set('db', db)
return next()
}
],
async buildEnd() {
await db?.close()
}
})
}Feature Flags Plugin
ts
export function featureFlagsPlugin(flags: Record<string, boolean>) {
return definePlugin({
name: 'feature-flags',
runtimeMiddleware: [
async (request, context, next) => {
context.set('features', flags)
return next()
}
],
// Replace at build time for tree-shaking
transform(code, id) {
if (!id.match(/\.[jt]sx?$/)) return code
for (const [flag, enabled] of Object.entries(flags)) {
code = code.replace(
new RegExp(`features\\.${flag}`, 'g'),
String(enabled)
)
}
return code
}
})
}Publishing Plugins
Package Structure
my-ereo-plugin/
├── src/
│ └── index.ts
├── dist/
│ └── index.js
├── package.json
└── README.mdpackage.json
json
{
"name": "ereo-plugin-example",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"@ereo/core": "^1.0.0"
}
}Best Practices
- Name plugins descriptively - Use
namefor debugging - Accept options - Make plugins configurable
- Use context.logger - For consistent logging
- Handle errors gracefully - Don't crash the app
- Document your plugin - Explain options and usage
- Test thoroughly - Test all hooks and scenarios
- Keep plugins focused - One responsibility per plugin