建立外掛
打造自訂的 PikaCSS 引擎外掛,為引擎擴充新功能。
結構
PikaCSS 外掛是一個回傳 EnginePlugin 物件的函式。建議的寫法:
import { defineEnginePlugin } from '@pikacss/core'
export function myPlugin() {
return defineEnginePlugin({
name: 'my-plugin',
configureRawConfig: (config) => {
config.layers ??= {}
config.layers['my-layer'] = 5
},
configureEngine: async (engine) => {
engine.addPreflight('/* my-plugin preflight */')
},
})
}defineEnginePlugin
defineEnginePlugin 輔助函式會為外掛物件提供型別推導。它接受一個物件,包含:
name:識別這個外掛的唯一字串。order:選擇性的執行順序,'pre'、'post',或省略以使用預設值。- Hook 方法:會在引擎生命週期的特定時機執行的函式。
上面的範例直接使用 defineEnginePlugin(),讓 config 與 engine hook 參數不需額外的輔助型別就能保持推導。
order
外掛的執行順序決定一個外掛的 hook 相對於其他外掛何時執行:
| 值 | 行為 |
|---|---|
'pre' | 在預設順序的外掛之前執行 |
| (省略) | 預設順序,依註冊順序執行 |
'post' | 在預設順序的外掛之後執行 |
在同一個順序群組內,外掛會依照它們在 plugins 陣列中出現的順序執行。核心外掛(variables、keyframes、selectors、shortcuts、important)會自動加到最前面並使用預設順序,因此預設順序的使用者外掛一定會在它們之後執行。
生命週期與注意事項
第一次撰寫外掛時容易忽略的運作行為。
Hook 錯誤會被捕捉,而不是拋出
如果某個 hook 拋出錯誤,引擎會記錄該錯誤,並帶著先前的 payload 繼續執行 pipeline(packages/core/src/plugin.ts)。createEngine() 不會失敗,後續的外掛仍會執行。有兩個影響:
- 有問題的外掛會默默失效,開發時請留意 log 輸出(
Plugin "<name>" failed to execute hook "<hook>")。 - 會拋出錯誤的 transform hook 會讓 payload 維持在你的外掛執行前的狀態,因此如果你是就地修改 payload,在拋出前所做的部分變動可能仍然看得到。
order: 'pre' 會在核心服務掛上之前執行
engine.selectors、engine.shortcuts、engine.keyframes 與 engine.variables 是由核心外掛在它們自己的 configureEngine hook 中掛上的。帶有 order: 'pre' 的外掛會在這件事發生之前就執行 configureEngine,因此在那裡存取這些服務會拋出錯誤,而根據前一點,這個錯誤只會變成一行 log 訊息。在建構時就存在的引擎方法(addPreflight、appendAutocomplete、appendCssImport、addConfigDependency)在任何順序群組中都能安全使用。@pikacss/plugin-design-tokens 就是一個遵守這條規則的真實 order: 'pre' 外掛:它只會變動原始設定,並呼叫 addConfigDependency。
用 addConfigDependency 註冊載入的檔案
如果你的外掛會讀取外部檔案(token 檔、圖示集、JSON 主題),請註冊每一個載入的路徑:
defineEnginePlugin({
name: 'my-plugin',
configureEngine: (engine) => {
engine.addConfigDependency('/absolute/path/to/tokens.json')
},
})建置整合會監看這些路徑,並在其中之一變更時重新建立引擎(見 SSR 與正式環境)。少了這個,使用者就必須重新啟動開發伺服器,才能套用你外掛原始檔的變更。@pikacss/plugin-design-tokens 正是這樣重新載入 token 檔的。
測試外掛
外掛的 hook 都是單純的函式,因此大多數外掛行為的測試不需要真正的引擎,可以比照官方的 @pikacss/plugin-reset 測試(packages/plugin-reset/src/index.test.ts):用最精簡的 mock 直接呼叫這些 hook,然後斷言其效果。
import { describe, expect, it, vi } from 'vitest'
import { myPlugin } from './index'
describe('myPlugin', () => {
it('registers its layer and preflight', async () => {
const plugin = myPlugin()
const engine = { addPreflight: vi.fn() }
const config: Record<string, any> = {}
plugin.configureRawConfig?.(config as any)
await plugin.configureEngine?.(engine as any)
expect(config.layers).toEqual({ 'my-layer': 5 })
expect(engine.addPreflight).toHaveBeenCalled()
})
})如果要對產生的 CSS 做端對端斷言,請改為建立一個真正的引擎:const engine = await createEngine({ plugins: [myPlugin()] }),接著 await engine.use({ ... }),再對 await engine.renderAtomicStyles(true) 拍快照。
下一步
- 可用的 Hook:所有你可以實作的生命週期 hook。
- 型別擴增:為你的外掛擴充 PikaCSS 型別。
- Define 輔助函式:
defineEngineConfig與defineEnginePlugin。