A flexible and extensible TypeScript/JavaScript plugin system supporting dependency management, lifecycle control, service registration, and event communication.
- 🔌 Plugin Lifecycle Management - Complete loading, activation, deactivation, and unloading workflow
- 📦 Dependency Management - Automatic handling of plugin dependencies and loading order
- 🎯 Service Registration - Plugins can register and use shared services
- 📡 Event System - EventEmitter-based inter-plugin communication
- ⚙️ Configuration Management - Dynamic configuration updates and hot reloading
- 🏷️ Tag Classification - Organize and find plugins by tags
- 🔍 Plugin Discovery - Automatic plugin discovery and loading
- 📊 Status Monitoring - Real-time plugin status and statistics monitoring
- 🌐 Browser Support - Complete browser environment support with DOM and storage plugins
- 📱 Cross-Platform - Supports both Node.js and browser environments
The plugin system is designed with a clean separation between core functionality and platform-specific implementations:
src/
├── core/ # Platform-agnostic core functionality
│ ├── types.ts # Core interfaces and types
│ ├── base-plugin.ts # Abstract base plugin class
│ ├── plugin-manager.ts # Core plugin manager
│ ├── plugin-registry.ts # Plugin registration and discovery
│ └── plugin-loader.ts # Plugin loading utilities
├── platforms/
│ ├── node/ # Node.js-specific implementations
│ │ ├── index.ts # Node.js platform entry point
│ │ └── plugins/ # Node.js-specific plugins
│ │ ├── logger-plugin.ts
│ │ └── cache-plugin.ts
│ └── browser/ # Browser-specific implementations
│ ├── index.ts # Browser platform entry point
│ ├── event-emitter.ts # Browser-compatible EventEmitter
│ ├── plugin-manager.ts # Browser plugin manager
│ ├── base-plugin.ts # Browser base plugin class
│ └── plugins/ # Browser-specific plugins
│ ├── dom-plugin.ts
│ └── storage-plugin.ts
└── index.ts # Main entry point (Node.js)
npm install plugin-system
import { PluginManager, Logger, BasePlugin } from "plugin-system"; // Create a simple logger class ConsoleLogger implements Logger { debug(message: string, ...args: any[]): void { console.debug(`[DEBUG] ${message}`, ...args); } info(message: string, ...args: any[]): void { console.info(`[INFO] ${message}`, ...args); } warn(message: string, ...args: any[]): void { console.warn(`[WARN] ${message}`, ...args); } error(message: string, ...args: any[]): void { console.error(`[ERROR] ${message}`, ...args); } } // Create a simple plugin class MyPlugin extends BasePlugin { constructor() { super({ name: "my-plugin", version: "1.0.0", description: "My custom plugin", author: "Your Name", tags: ["custom", "example"], priority: 50, }); } protected async onInitialize(): Promise<void> { // Plugin initialization logic this.context.logger.info("Plugin initialized"); // Register service this.context.services.register("myService", { doSomething: () => console.log("Doing something"), }); } protected async onActivate(): Promise<void> { // Plugin activation logic this.context.logger.info("Plugin activated"); } protected async onDeactivate(): Promise<void> { // Plugin deactivation logic this.context.logger.info("Plugin deactivated"); } protected async onDispose(): Promise<void> { // Plugin cleanup logic this.context.services.unregister("myService"); this.context.logger.info("Plugin disposed"); } } // Use the plugin system async function main() { const logger = new ConsoleLogger(); const pluginManager = new PluginManager(logger); const myPlugin = new MyPlugin(); await pluginManager.registerPlugin(myPlugin, { customOption: "value", }); await pluginManager.activatePlugin("my-plugin"); // Use plugin service const plugin = pluginManager.getPlugin("my-plugin") as any; const myService = plugin?.context?.services.get("myService"); myService?.doSomething(); } main().catch(console.error);
<!DOCTYPE html> <html> <head> <title>Plugin System Browser Example</title> </head> <body> <script src="dist/browser/plugin-system.umd.js"></script> <script> async function init() { // Create browser plugin system const pluginSystem = PluginSystem.createBrowserPluginSystem(); // Initialize with default plugins (DOM and Storage) await pluginSystem.initializeWithDefaults(); await pluginSystem.pluginManager.activateAll(); // Use DOM plugin const domService = pluginSystem.pluginManager.getService("dom"); const element = domService.createElement( "div", { style: "color: blue; padding: 10px;", }, "Hello Plugin System!" ); document.body.appendChild(element); } init().catch(console.error); </script> </body> </html>
Provides advanced logging management with level control and file output.
import { LoggerPlugin } from "plugin-system"; const loggerPlugin = new LoggerPlugin(); await pluginManager.registerPlugin(loggerPlugin, { logLevel: "debug", // Log levels: debug, info, warn, error logFile: "./app.log", // Log file path (optional) });
Provides in-memory caching service with TTL, LRU eviction, and memory management.
import { CachePlugin } from "plugin-system"; const cachePlugin = new CachePlugin(); await pluginManager.registerPlugin(cachePlugin, { maxSize: 1000, // Maximum cache entries ttl: 300000, // Default TTL (milliseconds) }); // Use cache service const cacheService = pluginManager .getPlugin("cache") ?.context?.services.get("cache"); cacheService.set("key", "value"); const value = cacheService.get("key");
Plugins can declare dependencies, and the system will automatically load them in the correct order:
export class AdvancedPlugin extends BasePlugin { constructor() { super({ name: "advanced-plugin", version: "1.0.0", dependencies: ["logger", "cache"], // Depends on other plugins // ... }); } protected async onDependencyStatusChange( dependency: string, status: PluginStatus ): Promise<void> { // Handle dependency status changes this.context.logger.info(`Dependency ${dependency} status changed to: ${status}`); } }
Plugins can communicate through events:
// Send events this.emit("custom-event", { data: "some data" }); // Listen for events this.on("system:log", (data) => { console.log("Received log event:", data); }); // Listen for other plugin events pluginManager.on("plugin:other-plugin:some-event", (data) => { console.log("Received other plugin event:", data); });
Support for runtime plugin configuration updates:
// Update configuration await pluginManager.updatePluginConfig('my-plugin', { newOption: 'new value' }); // Handle configuration updates in plugin protected async onConfigUpdate(config: PluginConfig): Promise<void> { // Handle configuration changes this.someOption = config.newOption; }
// Batch load all plugins (in dependency order) await pluginManager.loadAll(); // Batch activate all plugins await pluginManager.activateAll(); // Batch deactivate all plugins await pluginManager.deactivateAll(); // Get active plugin list const activePlugins = pluginManager.getActivePlugins();
Use plugin registry for advanced querying and management:
import { PluginRegistry } from "plugin-system"; const registry = new PluginRegistry(); // Find plugins by tag const logPlugins = registry.getByTag("logging"); // Sort by priority const sortedPlugins = registry.getByPriority(); // Search plugins const results = registry.search({ name: "cache", tags: ["performance"], author: "System", }); // Get statistics const stats = registry.getStats();
Automatic plugin discovery and loading:
import { DefaultPluginLoader, DefaultPluginDiscovery, } from "plugin-system"; const loader = new DefaultPluginLoader(); const discovery = new DefaultPluginDiscovery(); // Load plugin from file const plugin = await loader.loadFromFile("./plugins/my-plugin.js"); // Batch load from directory const plugins = await loader.loadFromDirectory("./plugins"); // Discover plugins const descriptors = await discovery.discoverPlugins([ "./plugins", "./custom-plugins", ]);
Main plugin management class responsible for plugin lifecycle management.
registerPlugin(plugin, config?)- Register pluginloadPlugin(name)- Load pluginactivatePlugin(name)- Activate plugindeactivatePlugin(name)- Deactivate pluginunloadPlugin(name)- Unload pluginloadAll()- Batch load all pluginsactivateAll()- Batch activate all pluginsdeactivateAll()- Batch deactivate all pluginsgetPlugin(name)- Get plugin instancegetActivePlugins()- Get active plugin listupdatePluginConfig(name, config)- Update plugin configuration
Base plugin class providing fundamental plugin development functionality.
onInitialize()- Plugin initialization (must implement)onActivate()- Plugin activation (must implement)onDeactivate()- Plugin deactivation (must implement)onDispose()- Plugin cleanup (must implement)onConfigUpdate(config)- Configuration update (optional)onDependencyStatusChange(dependency, status)- Dependency status change (optional)
getConfig(key?)- Get configurationgetService(name)- Get servicegetUtility(name)- Get utilityemit(event, data?)- Send eventon(event, listener)- Listen for eventoff(event, listener)- Remove event listener
Plugin status enumeration:
UNLOADED- Not loadedLOADING- LoadingLOADED- LoadedACTIVE- ActiveERROR- ErrorDISABLED- Disabled
<!DOCTYPE html> <html> <head> <title>Plugin System Browser Example</title> </head> <body> <!-- Load the built plugin system --> <script src="dist/browser/plugin-system.umd.js"></script> <script> async function init() { // Create browser plugin system const pluginSystem = PluginSystem.createBrowserPluginSystem(); // Initialize default plugins (DOM and Storage) await pluginSystem.initializeWithDefaults(); // Activate all plugins await pluginSystem.pluginManager.activateAll(); // Use DOM plugin const domService = pluginSystem.pluginManager.getService("dom"); const element = domService.createElement( "div", { style: "color: blue; padding: 10px;", }, "Hello Plugin System!" ); document.body.appendChild(element); // Use storage plugin const storageService = pluginSystem.pluginManager.getService("storage"); storageService.local.set("greeting", "Hello World!"); console.log(storageService.local.get("greeting")); } init().catch(console.error); </script> </body> </html>
Provides complete DOM manipulation and event management functionality:
// Get DOM service const domService = pluginManager.getService("dom"); // Create elements const button = domService.createElement( "button", { class: "my-button", id: "test-btn", }, "Click Me" ); // Add event listeners domService.addEventListener(button, "click", () => { alert("Button clicked!"); }); // Create complex components const component = domService.createComponent({ tag: "div", className: "card", children: [ { tag: "h3", textContent: "Title" }, { tag: "p", textContent: "Content" }, ], styles: { border: "1px solid #ccc", padding: "10px", borderRadius: "4px", }, }); // Observe DOM changes domService.observeChanges(document.body, (mutations) => { console.log("DOM changed:", mutations); });
Supports localStorage, sessionStorage, and IndexedDB:
// Get storage service const storageService = pluginManager.getService("storage"); // localStorage operations storageService.local.set("user", { name: "John", age: 25 }); const user = storageService.local.get("user"); // sessionStorage operations storageService.session.set("temp-data", "temporary value"); // IndexedDB operations (async) await storageService.indexed.set("large-data", { /* large data */ }); const largeData = await storageService.indexed.get("large-data"); // Get storage statistics const info = storageService.getInfo(); console.log("Storage usage:", info);
# Build browser version npm run build:browser # Build production version (minified) npm run build:browser:dev
Built files are located in the dist/browser/ directory:
plugin-system.umd.js- UMD formatplugin-system.es.js- ES modules format
# Node.js version npm run build # Compile project npm run example # Run basic example npm run example:advanced # Run advanced example # Browser version npm run build:browser # Build browser version npm run build:browser:dev # Build development version # Development and testing npm run dev:watch # Development mode (watch file changes) npm run typecheck # Type checking npm run lint # Code linting npm run lint:fix # Fix code style npm run test # Run tests npm run test:watch # Run tests in watch mode npm run test:coverage # Run tests with coverage npm run test:ui # Run tests with UI npm run clean # Clean build files
Check the examples/ directory for complete usage examples, including:
- Plugin manager setup
- Custom plugin creation
- Service usage
- Event handling
- Configuration management
- Batch operations
Run examples:
npm run example npm run example:advanced
- Single Responsibility - Each plugin focuses on one specific functionality
- Loose Coupling - Communicate through services and events, avoid direct dependencies
- Configurable - Provide flexible configuration options
- Error Handling - Handle exceptions properly
- Clearly declare plugin dependencies
- Avoid circular dependencies
- Set reasonable plugin priorities
- Handle dependency status changes
- Lazy load non-critical plugins
- Use cache services reasonably
- Clean up resources promptly
- Monitor memory usage
- Implement complete error handling logic
- Provide meaningful error messages
- Support plugin degradation and recovery
- Log detailed debugging information
Problem: Plugin initialization failed
Solutions:
- Check if plugin dependencies are correctly registered
- Ensure plugin's
onInitializemethod doesn't throw exceptions - Verify plugin configuration is correct
Problem: Circular dependency detected
Solutions:
- Redesign plugin architecture to avoid circular dependencies
- Use event communication instead of direct dependencies
- Consider extracting common dependencies into independent plugins
Problem: Service not found
Solutions:
- Ensure the service-providing plugin is activated
- Check if service registration name is correct
- Verify plugin loading order
Problem: Memory usage continues to grow
Solutions:
- Clean up all resources in plugin's
onDisposemethod - Remove event listeners
- Clear timers and async operations
- Set reasonable size limits when using cache plugins
// Set log level to debug const logger = new ConsoleLogger(); logger.setLevel("debug"); // Or use built-in logger plugin await pluginManager.updatePluginConfig("logger", { logLevel: "debug", });
// Listen for all plugin events [ "registered", "loaded", "activated", "deactivated", "unloaded", "config-changed", ].forEach((event) => { pluginManager.on(`plugin:${event}`, (data) => { console.log(`Plugin event [${event}]:`, data); }); });
// Get plugin dependency information const plugin = pluginManager.getPlugin("my-plugin"); if (plugin) { console.log("Dependencies:", plugin.metadata.dependencies); console.log("Status:", plugin.status); } // Check all plugin statuses const allPlugins = pluginManager.getAllPlugins(); for (const [name, plugin] of allPlugins) { console.log(`${name}: ${plugin.status}`); }
// Only load plugins when needed if (someCondition) { await pluginManager.loadPlugin("optional-plugin"); await pluginManager.activatePlugin("optional-plugin"); }
// Use batch operations instead of individual calls await pluginManager.activateAll(); // Instead of multiple activatePlugin calls
// Avoid frequent event sending class MyPlugin extends BasePlugin { private eventBuffer: any[] = []; private flushEvents() { if (this.eventBuffer.length > 0) { this.emit("batch-events", this.eventBuffer); this.eventBuffer = []; } } }
// Set reasonable limits when using cache plugins await pluginManager.registerPlugin(cachePlugin, { maxSize: 1000, // Limit cache entries ttl: 300000, // Set expiration time }); // Listen for memory warnings pluginManager.on("system:memory-warning", () => { // Clean up non-essential resources });
# Create new plugin directory mkdir my-plugin cd my-plugin # Create plugin file cat > index.ts << 'EOF' import { BasePlugin } from 'plugin-system'; export class MyPlugin extends BasePlugin { constructor() { super({ name: 'my-plugin', version: '1.0.0', description: 'My custom plugin', author: 'Your Name' }); } protected async onInitialize(): Promise<void> { // Initialization logic } protected async onActivate(): Promise<void> { // Activation logic } protected async onDeactivate(): Promise<void> { // Deactivation logic } protected async onDispose(): Promise<void> { // Cleanup logic } } EOF
import { describe, it, expect, beforeEach, vi } from "vitest"; import { PluginManager } from "plugin-system"; import { MyPlugin } from "./my-plugin"; describe("MyPlugin", () => { let pluginManager: PluginManager; let plugin: MyPlugin; beforeEach(() => { pluginManager = new PluginManager(mockLogger); plugin = new MyPlugin(); }); it("should initialize correctly", async () => { await pluginManager.registerPlugin(plugin); await pluginManager.loadPlugin("my-plugin"); expect(plugin.status).toBe(PluginStatus.LOADED); }); });
// Recommended production configuration const pluginManager = new PluginManager(productionLogger); // Set up error handling pluginManager.on("plugin:error", (error) => { // Send to monitoring system monitoring.reportError(error); }); // Enable health checks setInterval(() => { const health = getSystemHealth(); if (!health.healthy) { // Trigger alerts alerting.sendAlert(health); } }, 30000);
FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY dist ./dist COPY plugins ./plugins EXPOSE 3000 CMD ["node", "dist/index.js"]
# .env file
PLUGIN_LOG_LEVEL=info
PLUGIN_CACHE_SIZE=1000
PLUGIN_DB_HOST=localhost
PLUGIN_DB_PORT=5432- Initial release
- Basic plugin system functionality
- Built-in logger and cache plugins
- Complete TypeScript support
MIT License
Issues and Pull Requests are welcome!
- Fork the project
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone the project git clone https://github.com/your-org/plugin-system.git cd plugin-system # Install dependencies npm install # Run tests npm test # Run examples npm run example npm run example:advanced # Build project npm run build