@hidearea-design/plugin-system
v0.1.0
Published
Plugin system for Hidearea Design System - extensibility framework
Maintainers
Readme
@hidearea-design/plugin-system
拡張可能なプラグインシステムで、Hidearea Design Systemにカスタムコンポーネント、テーマ、機能を追加できます。
特徴
- カスタムコンポーネント: Web Componentsを登録して新しいUI要素を追加
- カスタムテーマ: 独自のデザイントークンとスタイルでテーマを作成
- トークン拡張: 既存のデザイントークンを拡張
- ライフサイクル管理: プラグインのインストール、アクティブ化、非アクティブ化、アンインストール
- 依存関係管理: プラグイン間の依存関係をサポート
- イベントシステム: プラグイン間の通信用イベントバス
- TypeScript完全サポート: 完全な型定義
インストール
pnpm add @hidearea-design/plugin-system基本的な使い方
プラグインの作成
import { createPlugin } from '@hidearea-design/plugin-system';
export const myPlugin = createPlugin({
metadata: {
id: 'my-plugin',
name: 'My Plugin',
version: '1.0.0',
description: 'カスタムプラグインの説明',
author: 'Your Name',
},
install(context) {
context.logger.info('プラグインがインストールされました');
},
onActivate(context) {
context.logger.info('プラグインがアクティブ化されました');
},
onDeactivate(context) {
context.logger.info('プラグインが非アクティブ化されました');
},
});プラグインの登録と使用
import { PluginManager } from '@hidearea-design/plugin-system';
import { myPlugin } from './my-plugin';
const manager = PluginManager.getInstance();
// プラグインを登録してインストール
await manager.register(myPlugin, {
autoActivate: true, // 自動的にアクティブ化
});
// プラグインをアクティブ化
await manager.activate('my-plugin');
// プラグインを非アクティブ化
await manager.deactivate('my-plugin');
// プラグインをアンインストール
await manager.uninstall('my-plugin');カスタムコンポーネントプラグイン
カスタムWeb Componentsを追加するプラグインの例:
import { LitElement, html, css } from 'lit';
import { property } from 'lit/decorators.js';
import { createPlugin } from '@hidearea-design/plugin-system';
class RatingComponent extends LitElement {
@property({ type: Number }) value = 0;
@property({ type: Number }) max = 5;
@property({ type: Boolean }) readonly = false;
static styles = css`
.rating {
display: inline-flex;
gap: 4px;
}
.star {
cursor: pointer;
font-size: 24px;
color: var(--ha-color-text-tertiary);
transition: color 0.2s;
}
.star.filled {
color: var(--ha-color-warning);
}
`;
render() {
return html`
<div class="rating">
${Array.from({ length: this.max }, (_, i) => i + 1).map(
star => html`
<span
class="star ${star <= this.value ? 'filled' : ''}"
@click=${() => !this.readonly && this._handleClick(star)}
>
${star <= this.value ? '★' : '☆'}
</span>
`
)}
</div>
`;
}
private _handleClick(star: number) {
this.value = star;
this.dispatchEvent(new CustomEvent('change', { detail: { value: star } }));
}
}
export const ratingPlugin = createPlugin({
metadata: {
id: 'rating-component',
name: 'Rating Component Plugin',
version: '1.0.0',
description: 'スター評価コンポーネント',
},
install(context) {
context.registerComponent('ha-rating', RatingComponent);
context.logger.info('Rating component registered');
},
});使用方法:
<ha-rating value="3" max="5"></ha-rating>カスタムテーマプラグイン
独自のテーマを追加するプラグインの例:
import { createPlugin, ThemeDefinition } from '@hidearea-design/plugin-system';
const oceanTheme: ThemeDefinition = {
name: 'ocean',
tokens: {
// プライマリカラー - 青系
'--ha-color-primary': '#0077be',
'--ha-color-primary-hover': '#005fa3',
'--ha-color-primary-active': '#004d8a',
// セカンダリカラー - 水色系
'--ha-color-secondary': '#00a8e8',
'--ha-color-secondary-hover': '#0096d1',
'--ha-color-secondary-active': '#0084ba',
// 背景カラー - 涼しげな色調
'--ha-color-background-primary': '#f0f8ff',
'--ha-color-background-secondary': '#e3f2fd',
'--ha-color-background-tertiary': '#d1ecf9',
},
dark: {
tokens: {
'--ha-color-primary': '#29b6f6',
'--ha-color-secondary': '#4fc3f7',
'--ha-color-background-primary': '#0a1929',
'--ha-color-background-secondary': '#132f4c',
'--ha-color-background-tertiary': '#1e4976',
},
},
styles: `
:root[data-theme="ocean"] {
--ha-shadow-sm: 0 1px 2px 0 rgba(0, 119, 190, 0.05);
--ha-shadow-md: 0 4px 6px -1px rgba(0, 119, 190, 0.1);
--ha-shadow-lg: 0 10px 15px -3px rgba(0, 119, 190, 0.15);
}
`,
};
export const oceanThemePlugin = createPlugin({
metadata: {
id: 'ocean-theme',
name: 'Ocean Theme Plugin',
version: '1.0.0',
description: '爽やかな青系のOceanテーマ',
},
install(context) {
context.registerTheme('ocean', oceanTheme);
context.logger.info('Ocean theme registered');
},
onActivate(context) {
document.documentElement.setAttribute('data-theme', 'ocean');
context.logger.info('Ocean theme activated');
},
onDeactivate(context) {
document.documentElement.removeAttribute('data-theme');
context.logger.info('Ocean theme deactivated');
},
});トークン拡張プラグイン
既存のデザイントークンを拡張するプラグイン:
import { createPlugin } from '@hidearea-design/plugin-system';
export const customTokensPlugin = createPlugin({
metadata: {
id: 'custom-tokens',
name: 'Custom Tokens Plugin',
version: '1.0.0',
},
install(context) {
// カスタムトークンを追加
context.extendTokens({
'--custom-gradient-sunset': 'linear-gradient(135deg, #ff6b35 0%, #ff8a80 100%)',
'--custom-gradient-ocean': 'linear-gradient(135deg, #0077be 0%, #00a8e8 100%)',
'--custom-spacing-xl': '3rem',
'--custom-spacing-2xl': '4rem',
});
// カスタムスタイルを追加
context.addGlobalStyles(`
.gradient-sunset {
background: var(--custom-gradient-sunset);
}
.gradient-ocean {
background: var(--custom-gradient-ocean);
}
`);
},
});イベントシステム
プラグイン間で通信するためのイベントシステム:
// プラグインA: イベントを発火
export const pluginA = createPlugin({
metadata: { id: 'plugin-a', name: 'Plugin A', version: '1.0.0' },
install(context) {
// イベントを発火
context.emit('theme-changed', { theme: 'dark' });
},
});
// プラグインB: イベントをリッスン
export const pluginB = createPlugin({
metadata: { id: 'plugin-b', name: 'Plugin B', version: '1.0.0' },
install(context) {
// イベントをリッスン
context.on('theme-changed', (data) => {
console.log('Theme changed to:', data.theme);
});
},
});依存関係管理
他のプラグインに依存するプラグイン:
export const dependentPlugin = createPlugin({
metadata: {
id: 'dependent-plugin',
name: 'Dependent Plugin',
version: '1.0.0',
dependencies: ['ocean-theme'], // ocean-themeプラグインが必要
},
install(context) {
// ocean-themeがインストールされていることが保証される
context.logger.info('Dependent plugin installed');
},
});
// 使用方法
const manager = PluginManager.getInstance();
// 依存関係がない場合はエラー
await manager.register(dependentPlugin); // Error: Plugin depends on "ocean-theme"
// 正しい順序で登録
await manager.register(oceanThemePlugin);
await manager.register(dependentPlugin); // OKAPI リファレンス
PluginManager
プラグインのライフサイクルを管理するシングルトンクラス。
class PluginManager {
static getInstance(): PluginManager;
register(plugin: Plugin, options?: PluginOptions): Promise<void>;
install(pluginId: string): Promise<void>;
uninstall(pluginId: string): Promise<void>;
activate(pluginId: string): Promise<void>;
deactivate(pluginId: string): Promise<void>;
getPlugin(pluginId: string): PluginEntry | undefined;
getAllPlugins(): Map<string, PluginEntry>;
isInstalled(pluginId: string): boolean;
isActive(pluginId: string): boolean;
}PluginContext
プラグイン内で使用できるコンテキストAPI。
interface PluginContext {
metadata: PluginMetadata;
options: PluginOptions;
logger: PluginLogger;
registerComponent(name: string, constructor: CustomElementConstructor): void;
registerTheme(name: string, theme: ThemeDefinition): void;
extendTokens(tokens: Record<string, string>): void;
addGlobalStyles(styles: string): void;
on(event: string, handler: (...args: unknown[]) => void): void;
off(event: string, handler: (...args: unknown[]) => void): void;
emit(event: string, ...args: unknown[]): void;
}Plugin Interface
プラグインが実装する必要があるインターフェース。
interface Plugin {
metadata: PluginMetadata;
install(context: PluginContext): void | Promise<void>;
onInstall?(context: PluginContext): void | Promise<void>;
onUninstall?(context: PluginContext): void | Promise<void>;
onActivate?(context: PluginContext): void | Promise<void>;
onDeactivate?(context: PluginContext): void | Promise<void>;
}PluginMetadata
プラグインのメタデータ。
interface PluginMetadata {
id: string;
name: string;
version: string;
description?: string;
author?: string;
dependencies?: string[];
}ThemeDefinition
カスタムテーマの定義。
interface ThemeDefinition {
name: string;
tokens: Record<string, string>;
dark?: {
tokens: Record<string, string>;
styles?: string;
};
styles?: string;
}ベストプラクティス
1. プラグインIDは一意にする
// ✓ Good - スコープ付きID
metadata: {
id: '@mycompany/custom-theme',
name: 'Custom Theme',
}
// ✗ Bad - 汎用的すぎるID
metadata: {
id: 'theme',
name: 'Theme',
}2. クリーンアップを適切に実装
export const myPlugin = createPlugin({
metadata: { id: 'my-plugin', name: 'My Plugin', version: '1.0.0' },
install(context) {
// イベントリスナーを追加
const handler = () => console.log('Event fired');
context.on('custom-event', handler);
},
onDeactivate(context) {
// クリーンアップ - イベントリスナーを削除
context.off('custom-event', handler);
},
});3. エラーハンドリング
export const myPlugin = createPlugin({
metadata: { id: 'my-plugin', name: 'My Plugin', version: '1.0.0' },
async install(context) {
try {
// 何らかの処理
await someAsyncOperation();
context.logger.info('Successfully installed');
} catch (error) {
context.logger.error('Installation failed:', error);
throw error;
}
},
});4. セマンティックバージョニング
// メジャーバージョン: 破壊的変更
metadata: { version: '2.0.0' }
// マイナーバージョン: 新機能追加
metadata: { version: '1.1.0' }
// パッチバージョン: バグ修正
metadata: { version: '1.0.1' }例
完全な例は examples/ ディレクトリを参照してください:
- custom-component-plugin.ts: 評価コンポーネントの例
- custom-theme-plugin.ts: Sunsetテーマの例
ライセンス
このパッケージはHidearea Design Systemモノレポの一部です。
関連パッケージ
- @hidearea-design/core - コアコンポーネント
- @hidearea-design/tokens - デザイントークン
- @hidearea-design/react - Reactラッパー
