TypeScript Module Augmentation
When creating plugins that add new configuration options or engine properties, you can use TypeScript module augmentation to provide full type safety.
Why Module Augmentation?
Module augmentation allows your plugin to:
- Add new configuration options that appear in
defineEngineConfig - Add new properties to the engine instance
- Provide full autocomplete support in IDEs
Basic Pattern
typescript
import { defineEnginePlugin } from '@pikacss/core'
// Step 1: Declare the module augmentation
declare module '@pikacss/core' {
interface EngineConfig {
myPlugin?: {
option1?: string
option2?: boolean
}
}
}
// Step 2: Create the plugin
export function myPlugin() {
return defineEnginePlugin({
name: 'my-plugin',
configureEngine(engine) {
// Access typed config
const options = engine.config.myPlugin
if (options?.option1) {
console.log(options.option1)
}
}
})
}Extending EngineConfig
Add new configuration options:
typescript
declare module '@pikacss/core' {
interface EngineConfig {
/**
* Configuration for my custom plugin
*/
myPlugin?: {
/**
* Prefix for generated classes
* @default 'my-'
*/
prefix?: string
/**
* Enable debug mode
* @default false
*/
debug?: boolean
/**
* Custom theme colors
*/
colors?: Record<string, string>
}
}
}Now users can configure your plugin:
typescript
// pika.config.ts
export default defineEngineConfig({
plugins: [myPlugin()],
myPlugin: {
prefix: 'custom-',
debug: true,
colors: {
primary: '#007bff',
secondary: '#6c757d'
}
}
})Extending Engine (Extra Properties)
Add new properties and methods to the engine instance:
typescript
declare module '@pikacss/core' {
interface Engine {
myPlugin: {
store: Map<string, CustomData>
add: (...items: CustomItem[]) => void
get: (key: string) => CustomData | undefined
}
}
}
interface CustomData {
name: string
value: string
}
interface CustomItem {
id: string
data: CustomData
}
export function myPlugin() {
return defineEnginePlugin({
name: 'my-plugin',
async configureEngine(engine) {
// Initialize the property
engine.myPlugin = {
store: new Map(),
add(...items) {
items.forEach((item) => {
engine.myPlugin.store.set(item.id, item.data)
})
},
get(key) {
return engine.myPlugin.store.get(key)
}
}
}
})
}Complete Example
Here's a complete plugin with full type augmentation:
typescript
import type { EnginePlugin } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'
// Type declarations
declare module '@pikacss/core' {
interface EngineConfig {
/**
* Theme plugin configuration
*/
theme?: ThemePluginConfig
}
interface Engine {
theme: ThemePluginAPI
}
}
// Configuration types
interface ThemePluginConfig {
/**
* Default theme name
* @default 'light'
*/
defaultTheme?: 'light' | 'dark'
/**
* Custom color palette
*/
colors?: {
light?: Record<string, string>
dark?: Record<string, string>
}
}
// API types
interface ThemePluginAPI {
colors: Map<string, string>
setColor: (name: string, value: string) => void
getColor: (name: string) => string | undefined
}
// Plugin implementation
export function themePlugin(): EnginePlugin {
return defineEnginePlugin({
name: 'theme-plugin',
async configureEngine(engine) {
const config = engine.config.theme || {}
const defaultTheme = config.defaultTheme || 'light'
const themeColors = config.colors?.[defaultTheme] || {}
// Initialize API
const colors = new Map<string, string>()
// Load initial colors
Object.entries(themeColors)
.forEach(([name, value]) => {
colors.set(name, value)
})
// Set up property
engine.theme = {
colors,
setColor(name, value) {
colors.set(name, value)
// Update CSS variable
engine.addPreflight({
':root': { [`--color-${name}`]: value }
})
engine.notifyPreflightUpdated()
},
getColor(name) {
return colors.get(name)
}
}
// Add selectors for theme switching
engine.selectors.add(
['@light', 'html:not(.dark) $'],
['@dark', 'html.dark $']
)
// Register for autocomplete
engine.appendAutocompleteSelectors('@light', '@dark')
}
})
}Usage:
typescript
// pika.config.ts
import { defineEngineConfig } from '@pikacss/unplugin-pikacss'
import { themePlugin } from './theme-plugin'
export default defineEngineConfig({
plugins: [themePlugin()],
theme: {
defaultTheme: 'light',
colors: {
light: {
primary: '#007bff',
background: '#ffffff'
},
dark: {
primary: '#0d6efd',
background: '#1a1a1a'
}
}
}
})Best Practices
Use descriptive JSDoc comments: Document all options with JSDoc for IDE tooltips
Provide sensible defaults: Don't require all options to be specified
Make config optional: Use
?to make the plugin config optionalExport types users might need: Export interfaces for external use
Follow existing patterns: Look at core plugins for consistency
typescript
// Export types for external use
export type { ThemePluginAPI, ThemePluginConfig }Type Declaration Location
Place your module augmentation:
- In the plugin's main file - For self-contained plugins
- In a separate
types.tsfile - For complex plugins - Ensure it's included in
tsconfig.json- Viaincludeortypes
json
{
"compilerOptions": {
"types": ["./node_modules/@my-org/pikacss-plugin-theme/types"]
}
}