建立插件
本指南帶你從零開始建立 PikaCSS 插件。插件可以擴充引擎,加入自訂變數、捷徑、選擇器、關鍵影格、前置樣式和自動補齊項目——全部具備完整的 TypeScript 型別安全。
最簡單的插件
插件是一個包含 name 和可選鉤子的純物件,使用 defineEnginePlugin() 包裝以確保型別安全:
import type { EnginePlugin } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'
export function myPlugin(): EnginePlugin {
return defineEnginePlugin({
name: 'my-plugin',
})
}慣例是匯出一個回傳插件物件的工廠函式。這讓使用者可以傳入選項:
import type { EnginePlugin } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'
export interface GreetingPluginOptions {
/** The greeting prefix. @default 'Hello' */
prefix?: string
}
export function greetingPlugin(
options: GreetingPluginOptions = {},
): EnginePlugin {
const { prefix = 'Hello' } = options
return defineEnginePlugin({
name: 'greeting',
configureEngine: async (engine) => {
// Use options to customize plugin behavior
engine.addPreflight(
`/* ${prefix} from greeting plugin */`,
)
},
})
}插件結構
每個插件都有:
| 屬性 | 類型 | 必要 | 說明 |
|---|---|---|---|
name | string | 是 | 插件的唯一識別符 |
order | 'pre' | 'post' | 否 | 控制相對於其他插件的執行順序 |
| 鉤子函式 | — | 否 | 在各個生命週期階段呼叫的函式 |
執行順序
插件在鉤子執行前依 order 排序。執行順序為:pre → (預設) → post。
import { defineEnginePlugin } from '@pikacss/core'
// Runs before default-order plugins
export const earlyPlugin = defineEnginePlugin({
name: 'early',
order: 'pre',
})
// Runs in default order (no `order` specified)
export const normalPlugin = defineEnginePlugin({
name: 'normal',
})
// Runs after default-order plugins
export const latePlugin = defineEnginePlugin({
name: 'late',
order: 'post',
})生命週期鉤子
在 createEngine(config) 期間,鉤子按以下順序觸發:
1. configureRawConfig (async)
在解析原始設定前修改它。回傳修改後的設定以傳遞給下一個插件。
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
// Async — modify raw config before resolution
configureRawConfig(config) {
// Add default preflights
config.preflights ??= []
config.preflights.push('/* injected by example plugin */')
return config
},
// Sync — read finalized raw config (cannot modify)
rawConfigConfigured(config) {
console.log('Final prefix:', config.prefix)
},
})2. rawConfigConfigured (sync)
在所有 configureRawConfig 鉤子執行後呼叫。用於讀取最終確定的原始設定。回傳值會被忽略。
3. configureResolvedConfig (async)
在套用預設值和插件解析後,修改已解析的設定。
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
// Async — modify resolved config before engine creation
configureResolvedConfig(resolvedConfig) {
// Override the prefix in resolved config
resolvedConfig.prefix = 'x-'
return resolvedConfig
},
})4. configureEngine (async)
最常用的鉤子。在引擎完全建構後呼叫。用於新增變數、捷徑、選擇器、關鍵影格、前置樣式和自動補齊項目。
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
// Async — called after engine is created
configureEngine: async (engine) => {
// Add CSS variables
engine.variables.add({
'--brand-color': '#0ea5e9',
})
// Add shortcuts
engine.shortcuts.add([
'flex-center',
{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
])
// Add custom selectors
engine.selectors.add(['hover', '$:hover'])
// Add keyframe animations
engine.keyframes.add([
'fade-in',
{ from: { opacity: '0' }, to: { opacity: '1' } },
['fade-in 0.3s ease'],
])
// Add preflight CSS
engine.addPreflight('*, *::before, *::after { box-sizing: border-box; }')
},
})Transform Hooks (runtime)
這些鉤子在執行期間樣式提取時呼叫:
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
// Transform selectors before they are resolved
async transformSelectors(selectors) {
return selectors.map(s =>
s === 'dark' ? '[data-theme="dark"]' : s,
)
},
// Transform style items before extraction
async transformStyleItems(styleItems) {
// Insert additional style items or modify existing ones
return styleItems
},
// Transform style definitions before extraction
async transformStyleDefinitions(styleDefinitions) {
return styleDefinitions
},
})Notification Hooks (sync)
這些鉤子通知插件狀態變更——它們無法修改有效載荷:
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
// Sync — called when a new atomic style is generated
atomicStyleAdded(atomicStyle) {
console.log(`New atomic style: ${atomicStyle.id}`)
},
// Sync — called when preflights are updated
preflightUpdated() {
console.log('Preflights updated')
},
// Sync — called when autocomplete config changes
autocompleteConfigUpdated() {
console.log('Autocomplete config updated')
},
})鉤子執行模型
- 鉤子會依排序順序逐插件執行。
- 若非同步鉤子回傳非 null 值,該值將取代下一個插件的有效載荷。
- 鉤子錯誤會被捕捉並記錄;執行將繼續至下一個插件。
模組擴增
使用 TypeScript 模組擴增來新增自訂設定選項,為終端使用者提供型別安全。這是官方插件(icons、reset、typography)將其設定欄位新增至 EngineConfig 的方式。
import type { EnginePlugin } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'
// Step 1: Define your config type
export interface SpacingConfig {
/** Base spacing unit in px. @default 4 */
base?: number
}
// Step 2: Augment EngineConfig so users get autocomplete
declare module '@pikacss/core' {
interface EngineConfig {
spacing?: SpacingConfig
}
}
// Step 3: Read the config in your plugin
export function spacingPlugin(): EnginePlugin {
let spacingConfig: SpacingConfig = {}
return defineEnginePlugin({
name: 'spacing',
configureRawConfig(config) {
if (config.spacing)
spacingConfig = config.spacing
},
configureEngine: async (engine) => {
const base = spacingConfig.base ?? 4
// Generate spacing variables
for (let i = 0; i <= 12; i++) {
engine.variables.add({
[`--spacing-${i}`]: `${i * base}px`,
})
}
// Add spacing shortcuts
for (let i = 0; i <= 12; i++) {
engine.shortcuts.add([
`p-${i}`,
{ padding: `var(--spacing-${i})` },
])
engine.shortcuts.add([
`m-${i}`,
{ margin: `var(--spacing-${i})` },
])
}
},
})
}使用者在設定引擎時即可獲得完整的自動補齊:
import { defineEngineConfig } from '@pikacss/core'
import { icons } from '@pikacss/plugin-icons'
import { reset } from '@pikacss/plugin-reset'
import { typography } from '@pikacss/plugin-typography'
export default defineEngineConfig({
plugins: [
reset(),
icons(),
typography(),
],
// Plugin config options are type-safe via module augmentation
reset: 'modern-normalize',
icons: { prefix: 'i-', scale: 1.2 },
typography: {},
})新增前置樣式
前置樣式是在原子化樣式之前注入的全域 CSS 樣式。engine.addPreflight() 方法接受三種形式:
字串前置樣式
原始 CSS 字串直接注入:
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
configureEngine: async (engine) => {
// String preflight — raw CSS injected as-is
engine.addPreflight(`
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
line-height: 1.5;
}
`)
},
})PreflightDefinition 物件
以選擇器為鍵、CSS 屬性為值的結構化物件:
import type { PreflightDefinition } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
configureEngine: async (engine) => {
// PreflightDefinition — structured object with CSS properties
const preflight: PreflightDefinition = {
':root': {
fontSize: '16px',
lineHeight: '1.5',
},
'body': {
margin: '0',
fontFamily: 'system-ui, sans-serif',
},
}
engine.addPreflight(preflight)
},
})PreflightFn 函式
接收引擎實例並回傳字串或 PreflightDefinition 的函式。適用於需要讀取引擎狀態的動態前置樣式:
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
configureEngine: async (engine) => {
// PreflightFn — dynamic preflight that reads engine state
engine.addPreflight((engine, isFormatted) => {
const vars = Array.from(engine.variables.store.entries())
const css = vars
.map(([name, list]) =>
list.map(v => `${name}: ${v.value};`)
.join(
isFormatted ? '\n ' : '',
),
)
.join(isFormatted ? '\n ' : '')
return isFormatted
? `:root {\n ${css}\n}`
: `:root{${css}}`
})
},
})自動補齊 API
插件可以透過新增自訂項目來豐富 TypeScript 自動補齊體驗。這些 API 可在 configureEngine 中的 engine 實例上使用:
import { defineEnginePlugin } from '@pikacss/core'
export const plugin = defineEnginePlugin({
name: 'example',
configureEngine: async (engine) => {
// Add custom selectors to autocomplete
engine.appendAutocompleteSelectors('hover', 'focus', 'dark')
// Add custom style item strings to autocomplete
engine.appendAutocompleteStyleItemStrings(
'flex-center',
'btn-primary',
)
// Add extra TypeScript properties for autocomplete
engine.appendAutocompleteExtraProperties('__shortcut')
// Add extra CSS properties for autocomplete
engine.appendAutocompleteExtraCssProperties(
'--my-color',
'--my-size',
)
// Add TypeScript type unions for a property value
engine.appendAutocompletePropertyValues(
'__shortcut',
'(string & {})',
)
// Add concrete CSS values for a property
engine.appendAutocompleteCssPropertyValues(
'display',
'flex',
'grid',
'block',
)
},
})| 方法 | 用途 |
|---|---|
appendAutocompleteSelectors(...selectors) | 新增選擇器字串至自動補齊 |
appendAutocompleteStyleItemStrings(...strings) | 新增樣式項目字串(捷徑名稱等) |
appendAutocompleteExtraProperties(...properties) | 新增額外 TypeScript 屬性 |
appendAutocompleteExtraCssProperties(...properties) | 新增額外 CSS 屬性(例如自訂 CSS 變數) |
appendAutocompletePropertyValues(property, ...tsTypes) | 為屬性值新增 TypeScript 型別聯合 |
appendAutocompleteCssPropertyValues(property, ...values) | 為 CSS 屬性新增具體 CSS 值 |
內建引擎 API
在 configureEngine 中,引擎公開了來自內建核心插件的 API:
| API | 說明 |
|---|---|
engine.variables.add(definition) | 新增具有自動補齊支援的 CSS 變數 |
engine.shortcuts.add(...shortcuts) | 新增靜態或動態捷徑 |
engine.selectors.add(...selectors) | 新增靜態或動態選擇器對應 |
engine.keyframes.add(...keyframes) | 新增 @keyframes 動畫 |
engine.addPreflight(preflight) | 新增全域前置樣式 CSS |
engine.config | 存取已解析的引擎設定 |
engine.store.atomicStyleIds | 內容雜湊 → 原子化樣式 ID 的對應表 |
engine.store.atomicStyles | ID → AtomicStyle 物件的對應表 |
真實範例
Reset 插件(簡化版)
基於 @pikacss/plugin-reset——使用 order: 'pre'、configureRawConfig、模組擴增和 engine.addPreflight():
import type { EnginePlugin } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'
export type ResetStyle = 'modern-normalize' | 'eric-meyer'
// Module augmentation for config type safety
declare module '@pikacss/core' {
interface EngineConfig {
reset?: ResetStyle
}
}
// Simplified version of @pikacss/plugin-reset
export function reset(): EnginePlugin {
let style: ResetStyle = 'modern-normalize'
return defineEnginePlugin({
name: 'reset',
order: 'pre', // Run before other plugins
configureRawConfig: (config) => {
if (config.reset)
style = config.reset
},
configureEngine: async (engine) => {
// Load and inject the selected reset stylesheet
const resetCss = await loadResetCss(style)
engine.addPreflight(resetCss)
},
})
}
async function loadResetCss(style: ResetStyle): Promise<string> {
// In real code, this loads from bundled CSS files
return `/* ${style} reset styles */`
}Typography 插件(簡化版)
基於 @pikacss/plugin-typography——使用變數、捷徑和模組擴增:
import type { EnginePlugin } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'
export interface TypographyPluginOptions {
/** Custom variable overrides */
variables?: Record<string, string>
}
// Module augmentation
declare module '@pikacss/core' {
interface EngineConfig {
typography?: TypographyPluginOptions
}
}
// Simplified version of @pikacss/plugin-typography
export function typography(): EnginePlugin {
let typographyConfig: TypographyPluginOptions = {}
return defineEnginePlugin({
name: 'typography',
configureRawConfig: (config) => {
if (config.typography)
typographyConfig = config.typography
},
configureEngine: async (engine) => {
// 1. Add CSS variables
engine.variables.add({
'--prose-font-size': '1rem',
'--prose-line-height': '1.75',
...typographyConfig.variables,
})
// 2. Add base prose shortcut
engine.shortcuts.add([
'prose',
{
fontSize: 'var(--prose-font-size)',
lineHeight: 'var(--prose-line-height)',
maxWidth: '65ch',
},
])
// 3. Add size variant shortcuts
const sizes = {
sm: { fontSize: '0.875rem', lineHeight: '1.71' },
lg: { fontSize: '1.125rem', lineHeight: '1.77' },
}
for (const [size, overrides] of Object.entries(sizes)) {
engine.shortcuts.add([
`prose-${size}`,
['prose', overrides],
])
}
},
})
}腳手架插件套件
Monorepo 包含一個互動式腳手架腳本:
# Create a new plugin package interactively
pnpm newplugin
# Or pass the plugin name directly
pnpm newplugin my-feature腳手架會產生:
packages/plugin-<name>/資料夾- 包含
defineEnginePlugin樣板的src/index.ts - 以
@pikacss/core為同級相依的package.json - TypeScript 和 Vitest 設定檔
- 基本測試檔案
產生的工廠函式遵循 create<PascalName>Plugin(options) 的模式,插件 name 設定為 slug。
發布慣例
| 類型 | 套件名稱模式 |
|---|---|
| 官方 | @pikacss/plugin-xxx |
| 社群 | pikacss-plugin-xxx |
- 匯出回傳
EnginePlugin的工廠函式 - 使用模組擴增來擴充設定型別
- 在
package.json的keywords中加入pikacss和pikacss-plugin