Skip to content

建立插件

本指南帶你從零開始建立 PikaCSS 插件。插件可以擴充引擎,加入自訂變數、捷徑、選擇器、關鍵影格、前置樣式和自動補齊項目——全部具備完整的 TypeScript 型別安全。

最簡單的插件

插件是一個包含 name 和可選鉤子的純物件,使用 defineEnginePlugin() 包裝以確保型別安全:

ts
import type { EnginePlugin } from '@pikacss/core'
import { defineEnginePlugin } from '@pikacss/core'

export function myPlugin(): EnginePlugin {
	return defineEnginePlugin({
		name: 'my-plugin',
	})
}

慣例是匯出一個回傳插件物件的工廠函式。這讓使用者可以傳入選項:

ts
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 */`,
			)
		},
	})
}

插件結構

每個插件都有:

屬性類型必要說明
namestring插件的唯一識別符
order'pre' | 'post'控制相對於其他插件的執行順序
鉤子函式在各個生命週期階段呼叫的函式

執行順序

插件在鉤子執行前依 order 排序。執行順序為:pre → (預設) → post

ts
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)

在解析原始設定前修改它。回傳修改後的設定以傳遞給下一個插件。

ts
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)

在套用預設值和插件解析後,修改已解析的設定。

ts
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)

最常用的鉤子。在引擎完全建構後呼叫。用於新增變數、捷徑、選擇器、關鍵影格、前置樣式和自動補齊項目。

ts
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)

這些鉤子在執行期間樣式提取時呼叫:

ts
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)

這些鉤子通知插件狀態變更——它們無法修改有效載荷:

ts
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 的方式。

ts
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})` },
				])
			}
		},
	})
}

使用者在設定引擎時即可獲得完整的自動補齊:

ts
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 字串直接注入:

ts
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 屬性為值的結構化物件:

ts
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 的函式。適用於需要讀取引擎狀態的動態前置樣式:

ts
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 實例上使用:

ts
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.atomicStylesID → AtomicStyle 物件的對應表

真實範例

Reset 插件(簡化版)

基於 @pikacss/plugin-reset——使用 order: 'pre'configureRawConfig、模組擴增和 engine.addPreflight()

ts
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——使用變數、捷徑和模組擴增:

ts
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 包含一個互動式腳手架腳本:

bash
# 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.jsonkeywords 中加入 pikacsspikacss-plugin

下一步